From bcb95f3bb59594927142839cc96b7509f83fd477 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Tue, 7 Jul 2026 23:39:18 -0400 Subject: [PATCH] feat(gfx): surface anim channel to the compositor via RenderObject.AnimState Co-Authored-By: Claude Opus 4.8 --- engine/Age.Engine.Tests/GfxAnimationTests.cs | 24 ++++++++++++++++++++ engine/Age.Engine/Model/GfxState.cs | 16 +++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/engine/Age.Engine.Tests/GfxAnimationTests.cs b/engine/Age.Engine.Tests/GfxAnimationTests.cs index a2b558a..6b15b11 100644 --- a/engine/Age.Engine.Tests/GfxAnimationTests.cs +++ b/engine/Age.Engine.Tests/GfxAnimationTests.cs @@ -92,4 +92,28 @@ public class GfxAnimationTests Assert.Equal(400, vm.Gfx.AnimClockDurationTicks); Assert.Equal(1, vm.Gfx.AnimClockGeneration); } + + [Fact] + public void SnapshotCarriesAnimStateForVisibleObject() + { + var t = T(); + // make object 0xA visible via set/draw-texture, then anim-start it toward (800,600,0) over 30 ticks. + var scene = ScriptAssembler.Assemble(t, "ANIM", new List<(int, Operand[])> + { + MovGI(1, 0xA), MovGI(2, 4), MovGI(7, 0x25), MovGI(3, 800), MovGI(4, 600), MovGI(5, 0), MovGI(6, 0), + (0x1f9, new[] { G(7), G(2), I(0) }), // set-texture resId 0x25 -> slot 4 + (0x1fb, new[] { G(1), G(2), I(0), I(0), G(3), G(4), G(5), G(6) }), // draw-texture: object 0xA visible + MovGI(8, 30), + (0x234, new[] { G(1), G(8), G(3), G(4), G(5) }), // anim-start(0xA, dur=30, (800,600,0)) + Exit(), + }, System.Array.Empty()); + var vm = new VirtualMachine(scene, t, new RecordingHost()); + vm.Run(); + var vis = vm.Gfx.SnapshotVisibleObjects(); + Assert.Single(vis); + Assert.True(vis[0].Anim.Enabled); + Assert.Equal((800L, 600L, 0L), (vis[0].Anim.TX, vis[0].Anim.TY, vis[0].Anim.TZ)); + Assert.Equal(30, vis[0].Anim.DurationTicks); + Assert.Equal(1, vis[0].Anim.Generation); + } } diff --git a/engine/Age.Engine/Model/GfxState.cs b/engine/Age.Engine/Model/GfxState.cs index 4ae6631..1435a42 100644 --- a/engine/Age.Engine/Model/GfxState.cs +++ b/engine/Age.Engine/Model/GfxState.cs @@ -2,12 +2,21 @@ using System.Linq; namespace Age.Engine.Model; +/// Per-object animation channel snapshot for the compositor (cluster 0x21c-0x243). Enabled = the +/// object has an active anim channel; (TX,TY,TZ) = the transform target it animates toward; Normalized = the +/// 0x21e ~percent variant; DurationTicks = the object's own duration (anim-start op2). The GLOBAL clock timebase +/// (duration + generation) is read separately off . Generation bumps +/// on each anim-start — the compositor re-triggers its wall-clock tween when it changes. +public readonly record struct AnimState(bool Enabled, bool Normalized, long TX, long TY, long TZ, + long DurationTicks, long Generation); + /// A renderable view of one visible gfx object — the host composites these in ascending-handle order /// (= the engine's z-order) each frame. Built by ; the surface /// resId/colorkey are resolved from the object's live source slot at snapshot time (see docs/engine-re.md, /// "The full gfx render model"). public readonly record struct RenderObject(long Handle, long SurfaceResId, long ColorKey, - int SrcX, int SrcY, int W, int H, int DstX, int DstY); + int SrcX, int SrcY, int W, int H, int DstX, int DstY, + AnimState Anim); /// Host-agnostic model of the AGE native gfx command-buffer (reversed in /// docs/engine-re.md, gfx op-contract table). One registry maps an object handle to a GfxObject — the @@ -171,7 +180,10 @@ public sealed class GfxState if (!o.Visible) continue; var (resId, ck) = _surfaces.TryGetValue(o.SourceSlot, out var s) ? s : (0L, 0L); list.Add(new RenderObject(kv.Key, resId, ck, o.SrcRect.X, o.SrcRect.Y, o.SrcRect.W, o.SrcRect.H, - (int)o.V24.X, (int)o.V24.Y)); + (int)o.V24.X, (int)o.V24.Y, + new AnimState(o.AnimEnabled, o.AnimNormalized, + o.AnimTarget.X, o.AnimTarget.Y, o.AnimTarget.Z, + o.AnimDurationTicks, o.AnimGeneration))); } return list; }