Fix ADV and BUNKI animation polish
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Age.Engine.Hosting;
|
||||
using Age.Engine.Model;
|
||||
using Age.Engine.Sys4;
|
||||
using Age.Engine.Vm;
|
||||
@@ -106,6 +108,22 @@ public class AdvTextOpsTests
|
||||
Assert.Single(host.WaitIndicators));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WaitIndicatorTerminalFrameIsExclusive()
|
||||
{
|
||||
var config = new AdvWaitIndicatorConfig(
|
||||
1, 385, 140, 12, 0, 0, 30, 27, 12, 48);
|
||||
|
||||
int[] frames = Enumerable.Range(0, 24)
|
||||
.Select(tick => config.FrameAt(tick * 48L))
|
||||
.ToArray();
|
||||
|
||||
Assert.Equal(
|
||||
Enumerable.Range(0, 12).Concat(Enumerable.Range(0, 12)),
|
||||
frames);
|
||||
Assert.DoesNotContain(12, frames);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WaitIndicatorToggleAndTextLayoutPublicationReachHost()
|
||||
{
|
||||
|
||||
@@ -72,5 +72,16 @@ public class GfxRangeTransformTests
|
||||
Assert.NotNull(vm.Gfx.SnapshotVisibleObjects().Single().RangeTransform);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BunkiAnimationPublicationExcludesStablePopupHandles()
|
||||
{
|
||||
var animationRange = new GfxHandleRange(0, 60000);
|
||||
|
||||
Assert.True(animationRange.Contains(59999));
|
||||
Assert.False(animationRange.Contains(60000));
|
||||
Assert.False(animationRange.Contains(60050));
|
||||
Assert.True(GfxHandleRange.All.Contains(60050));
|
||||
}
|
||||
|
||||
private static Operand I(long v) => new(0, v);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,19 @@ public class Transform2DMathTests
|
||||
Assert.Equal(0, allocated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanvasAxisDecompositionPreservesBunkiPopupScale()
|
||||
{
|
||||
var affine = Transform2DMath.Build(new TransformState(
|
||||
0.05, 0.65, 1, 0, 0, 0, 400, 300, 0));
|
||||
|
||||
var (rotation, scaleX, scaleY) = affine.DecomposeCanvasAxes();
|
||||
|
||||
Assert.Equal(0, rotation, 12);
|
||||
Assert.Equal(0.05, scaleX, 12);
|
||||
Assert.Equal(0.65, scaleY, 12);
|
||||
}
|
||||
|
||||
private static double Next(Random random, double minimum, double maximum)
|
||||
=> minimum + random.NextDouble() * (maximum - minimum);
|
||||
|
||||
|
||||
@@ -6,7 +6,17 @@ namespace Age.Engine.Hosting;
|
||||
public readonly record struct AdvWaitIndicatorConfig(
|
||||
int LayoutSlot, int X, int Y, int SurfaceSlot,
|
||||
int SourceX, int SourceY, int CellWidth, int CellHeight,
|
||||
int TerminalFrame, long FramePeriodMs);
|
||||
int TerminalFrame, long FramePeriodMs)
|
||||
{
|
||||
/// <summary>Select the current atlas frame. TerminalFrame is the exclusive native upper bound,
|
||||
/// so SYSTEM4's value 12 addresses the twelve cells 0 through 11.</summary>
|
||||
public int FrameAt(long elapsedMs)
|
||||
{
|
||||
int frameCount = System.Math.Max(1, TerminalFrame);
|
||||
long period = System.Math.Max(1, FramePeriodMs);
|
||||
return (int)(System.Math.Max(0, elapsedMs) / period % frameCount);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct AdvAutoWaitState(
|
||||
bool Enabled, bool VoicePending, long PostVoiceDelayMs, long UnvoicedDelayMs);
|
||||
|
||||
@@ -84,6 +84,15 @@ public readonly record struct RenderObject(long Handle, long SurfaceResId, long
|
||||
Affine2D? RangeTransform = null,
|
||||
bool TimeVarying = false);
|
||||
|
||||
/// <summary>The retained handle interval selected by an op-0x222 backbuffer publication.</summary>
|
||||
public readonly record struct GfxHandleRange(long First, long Count)
|
||||
{
|
||||
public static GfxHandleRange All => new(0, long.MaxValue);
|
||||
|
||||
public bool Contains(long handle)
|
||||
=> Count > 0 && handle >= First && (ulong)(handle - First) < (ulong)Count;
|
||||
}
|
||||
|
||||
/// <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
|
||||
/// native retained-gfx owner+0x408 map (EngineCtx+0x46a1c) that op 0x215 queries and the geometry get/set ops share. Each object carries a
|
||||
|
||||
@@ -31,6 +31,20 @@ public readonly record struct Affine2D(double XX, double XY, double YX, double Y
|
||||
inverse = new(xx, xy, yx, yy, -(TX * xx + TY * yx), -(TX * xy + TY * yy));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Decompose the projected axes for a Godot-style canvas item. AGE's ordinary retained
|
||||
/// transforms contain scale/rotation but no shear; the determinant preserves reflected Y axes.</summary>
|
||||
public (double RotationRadians, double ScaleX, double ScaleY) DecomposeCanvasAxes()
|
||||
{
|
||||
double scaleX = System.Math.Sqrt(XX * XX + XY * XY);
|
||||
double scaleY = System.Math.Sqrt(YX * YX + YY * YY);
|
||||
double determinant = XX * YY - XY * YX;
|
||||
if (determinant < 0) scaleY = -scaleY;
|
||||
double rotation = scaleX > 1e-12
|
||||
? System.Math.Atan2(XY, XX)
|
||||
: System.Math.Atan2(-YX, YY);
|
||||
return (rotation, scaleX, scaleY);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Exact 2D projection of AGE's row-vector retained-object matrix. Native call order is anchored
|
||||
|
||||
Reference in New Issue
Block a user