feat(gfx): GfxState animation-channel model (per-object transform + global clock)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
45
engine/Age.Engine.Tests/GfxAnimationTests.cs
Normal file
45
engine/Age.Engine.Tests/GfxAnimationTests.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System.Collections.Generic;
|
||||
using Age.Engine.Model;
|
||||
using Age.Engine.Sys4;
|
||||
using Age.Engine.Vm;
|
||||
using Xunit;
|
||||
|
||||
public class GfxAnimationTests
|
||||
{
|
||||
// ---- Task 2: GfxState animation-channel data model (revised global-clock model, see engine-re.md) ----
|
||||
|
||||
[Fact]
|
||||
public void SetAnimTransform_RecordsTargetParamsAndEnables()
|
||||
{
|
||||
var g = new GfxState();
|
||||
g.SetAnimTransform(0x1000, p1: 7, p2: 9, target: (100, 100, 100), normalized: true);
|
||||
var o = g.TryGet(0x1000)!;
|
||||
Assert.Equal((100L, 100L, 100L), o.AnimTarget);
|
||||
Assert.Equal(7, o.AnimParam1);
|
||||
Assert.Equal(9, o.AnimParam2);
|
||||
Assert.True(o.AnimNormalized);
|
||||
Assert.True(o.AnimEnabled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StartAnim_SetsTargetDurationEnablesAndBumpsGeneration()
|
||||
{
|
||||
var g = new GfxState();
|
||||
g.StartAnim(0x1000, durationTicks: 30, target: (0, 0, 5));
|
||||
var o = g.TryGet(0x1000)!;
|
||||
Assert.Equal((0L, 0L, 5L), o.AnimTarget);
|
||||
Assert.Equal(30, o.AnimDurationTicks);
|
||||
Assert.True(o.AnimEnabled);
|
||||
Assert.Equal(1, o.AnimGeneration); // fresh object starts at 0, one anim-start -> 1
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetAnimClock_SetsGlobalDurationAndBumpsClockGeneration()
|
||||
{
|
||||
var g = new GfxState();
|
||||
var cg0 = g.AnimClockGeneration;
|
||||
g.SetAnimClock(400);
|
||||
Assert.Equal(400, g.AnimClockDurationTicks);
|
||||
Assert.Equal(cg0 + 1, g.AnimClockGeneration);
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,17 @@ public sealed class GfxState
|
||||
public int SourceSlot = -1;
|
||||
public (int X, int Y, int W, int H) SrcRect;
|
||||
public bool Visible;
|
||||
|
||||
// ---- animation channel (cluster 0x21c-0x243; see docs/engine-re.md "sprite transform / ANIMATION").
|
||||
// 0x21e/0x220 set the transform directly (worker gfx_anim_set_channel@0x47eaa0: obj+0x3c=p1, +0x50=p2,
|
||||
// +0xac=target, +0x68=enable); 0x234 anim-start animates toward a target over the GLOBAL clock (0x238).
|
||||
// Passive: recorded here, interpolated by the Godot compositor over wall-clock. ----
|
||||
public (long X, long Y, long Z) AnimTarget;
|
||||
public long AnimParam1, AnimParam2;
|
||||
public bool AnimNormalized; // 0x21e (operands ~percent, /_DAT_00571c28) vs 0x220 (absolute)
|
||||
public bool AnimEnabled; // obj+0x68
|
||||
public long AnimDurationTicks; // 0x234 anim-start op2 (this object's duration; maxed into the clock)
|
||||
public long AnimGeneration; // bumped by anim-start (0x234); the compositor's per-object re-trigger
|
||||
}
|
||||
|
||||
// ---- geometry/draw object store (V18/V24/draw bind, the compositor's input) ----
|
||||
@@ -43,6 +54,13 @@ public sealed class GfxState
|
||||
private readonly Dictionary<long, long> _fieldTable = new(); // ctx+0x46d14 (0x216); no family writer -> default 0
|
||||
public long CurrentObject { get; private set; }
|
||||
|
||||
// ---- GLOBAL animation clock (op 0x238 set-anim-clock; native ctx+0x51b7c total / +0x51b78 elapsed).
|
||||
// Non-blocking: the op only configures duration; the host advances elapsed per-frame and tweens all armed
|
||||
// objects over it (docs/engine-re.md, "anim_start/set_anim_clock decoded"). Generation bumps on each set so
|
||||
// the compositor resets its wall-clock elapsed. ----
|
||||
public long AnimClockDurationTicks { get; private set; }
|
||||
public long AnimClockGeneration { get; private set; }
|
||||
|
||||
/// <summary>Live geometry objects and the surface slot they draw from — for the CLI gfx oracle.</summary>
|
||||
public IEnumerable<(long Handle, int Slot)> Objects
|
||||
{
|
||||
@@ -106,6 +124,40 @@ public sealed class GfxState
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Op 0x21e/0x220 (set-anim-transform): record the transform target + two scalar params on the
|
||||
/// object and enable its animation channel. normalized = 0x21e (operands ~percent, /_DAT_00571c28);
|
||||
/// absolute = 0x220. Native worker gfx_anim_set_channel@0x47eaa0 sets obj+0x3c=p1, +0x50=p2, +0xac=target,
|
||||
/// +0x68=1.</summary>
|
||||
public void SetAnimTransform(long handle, long p1, long p2, (long X, long Y, long Z) target, bool normalized)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var o = GetOrCreate(handle);
|
||||
o.AnimParam1 = p1; o.AnimParam2 = p2; o.AnimTarget = target;
|
||||
o.AnimNormalized = normalized; o.AnimEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Op 0x234 (anim-start): animate the object toward <paramref name="target"/> over the global
|
||||
/// clock; <paramref name="durationTicks"/> is this object's duration (native label_1235a maxes them into
|
||||
/// the clock). Bumps AnimGeneration — the compositor's per-object re-trigger.</summary>
|
||||
public void StartAnim(long handle, long durationTicks, (long X, long Y, long Z) target)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var o = GetOrCreate(handle);
|
||||
o.AnimTarget = target; o.AnimDurationTicks = durationTicks;
|
||||
o.AnimEnabled = true; o.AnimGeneration++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Op 0x238 (set-anim-clock): set the GLOBAL animation duration (game ticks) and bump the clock
|
||||
/// generation so the host resets its wall-clock elapsed. Non-blocking (the render loop advances it).</summary>
|
||||
public void SetAnimClock(long durationTicks)
|
||||
{
|
||||
lock (_lock) { AnimClockDurationTicks = durationTicks; AnimClockGeneration++; }
|
||||
}
|
||||
|
||||
/// <summary>Visible objects in ascending-handle order (= the engine's z-order), each with its source
|
||||
/// surface (resId/colorkey) resolved from its live source slot — for the host per-frame compositor.</summary>
|
||||
public IReadOnlyList<RenderObject> SnapshotVisibleObjects()
|
||||
|
||||
Reference in New Issue
Block a user