feat(gfx): surface anim channel to the compositor via RenderObject.AnimState
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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<string>());
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,21 @@ using System.Linq;
|
||||
|
||||
namespace Age.Engine.Model;
|
||||
|
||||
/// <summary>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 <see cref="GfxState.AnimClockDurationTicks"/>. Generation bumps
|
||||
/// on each anim-start — the compositor re-triggers its wall-clock tween when it changes.</summary>
|
||||
public readonly record struct AnimState(bool Enabled, bool Normalized, long TX, long TY, long TZ,
|
||||
long DurationTicks, long Generation);
|
||||
|
||||
/// <summary>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 <see cref="GfxState.SnapshotVisibleObjects"/>; 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").</summary>
|
||||
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);
|
||||
|
||||
/// <summary>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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user