Implement ADV foreground transition lifecycle
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
using Age.Engine.Model;
|
||||
|
||||
namespace Age.Engine.Hosting;
|
||||
public interface IHost
|
||||
{
|
||||
@@ -5,6 +7,14 @@ public interface IHost
|
||||
void WaitForInput();
|
||||
void Sleep(long duration);
|
||||
void FrameYield();
|
||||
// Native 0x1c7/0x1cc query two distinct ADV skip channels. Headless and non-interactive
|
||||
// hosts default to normal playback; the Godot host supplies the live interactive values.
|
||||
bool IsMessageSkipActive => false;
|
||||
bool IsAdvReadSkipActive => false;
|
||||
// Normal playback reaches op 0x21c and parks until a queued 0x223 transition completes. The
|
||||
// read/message-skip branch reaches op 0x20c and presents the completed endpoint immediately.
|
||||
void WaitForForegroundTransition(GfxState gfx) { }
|
||||
void PresentFrame(GfxState gfx) { }
|
||||
void CreateTexture(int slot, int width, int height);
|
||||
void SetTexture(long resourceId, int slot);
|
||||
void DrawTexture(int slot, int srcX, int srcY, int width, int height, int dstX, int dstY);
|
||||
|
||||
@@ -14,6 +14,12 @@ public readonly record struct RotationCycleState(bool Enabled, long PeriodMs,
|
||||
double AxisX, double AxisY, double AxisZ,
|
||||
double AngleDegrees = 0);
|
||||
|
||||
/// <summary>Sampled op-0x223 type-0 surface transition. Range A is already present in normal z-order;
|
||||
/// the compositor draws range B over it with <paramref name="Progress"/> to form the native crossfade.</summary>
|
||||
public readonly record struct SurfaceTransitionState(long CommandKey, int TargetSlot,
|
||||
long RangeAStart, int RangeACount, long RangeBStart, int RangeBCount,
|
||||
long DelayMs, long DurationMs, long StartMs, double Progress, bool Forced);
|
||||
|
||||
/// <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,
|
||||
@@ -25,7 +31,8 @@ public readonly record struct RotationCycleState(bool Enabled, long PeriodMs,
|
||||
public readonly record struct RenderObject(long Handle, long SurfaceResId, long ColorKey,
|
||||
int SrcX, int SrcY, int W, int H, int DstX, int DstY,
|
||||
TransformState Transform, RotationCycleState Rotation,
|
||||
int Alpha, long Tint, int TintStrength, BlendKind Blend);
|
||||
int Alpha, long Tint, int TintStrength, BlendKind Blend,
|
||||
SurfaceTransitionState? SurfaceTransition = null);
|
||||
|
||||
/// <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
|
||||
@@ -35,12 +42,20 @@ public readonly record struct RenderObject(long Handle, long SurfaceResId, long
|
||||
/// the query ops read back, which is all the bytecode geometry math needs.</summary>
|
||||
public sealed class GfxState
|
||||
{
|
||||
private sealed class SurfaceTransition
|
||||
{
|
||||
public long CommandKey, RangeAStart, RangeBStart, DelayMs, DurationMs;
|
||||
public int TargetSlot, RangeACount, RangeBCount;
|
||||
public long StartMs = -1;
|
||||
public bool Forced;
|
||||
}
|
||||
public sealed class GfxObject
|
||||
{
|
||||
public (long X, long Y, long Z) V18, V24, V16c;
|
||||
public long Field64, Field68, Field6c;
|
||||
public long Color;
|
||||
public bool HasColor; // true once op 0x202/0x203 set a color/alpha modulation on this object
|
||||
public long StaticColorMode; // op 0x203 operand 2 -> obj+0x30; mode 2 is transition alpha/identity
|
||||
|
||||
// ---- src-rect / spritesheet-cell channel (ops 0x239 static cell, 0x231 animate). Interpolator
|
||||
// SRC-RECT SCROLL channel: period obj+0x230, start obj+0x21c, grid obj+0x238/0x23c. ----
|
||||
@@ -115,6 +130,37 @@ public sealed class GfxState
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Op 0x21d: clone the native 0x2d4-byte retained-object record from source to destination.</summary>
|
||||
public bool CloneObject(long sourceHandle, long destinationHandle)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_objects.TryGetValue(sourceHandle, out var s)) return false;
|
||||
_objects[destinationHandle] = new GfxObject
|
||||
{
|
||||
V18 = s.V18, V24 = s.V24, V16c = s.V16c,
|
||||
Field64 = s.Field64, Field68 = s.Field68, Field6c = s.Field6c,
|
||||
Color = s.Color, HasColor = s.HasColor, StaticColorMode = s.StaticColorMode,
|
||||
SrcGridW = s.SrcGridW, SrcGridH = s.SrcGridH, SrcCell = s.SrcCell,
|
||||
SrcPeriod = s.SrcPeriod, SrcStart = s.SrcStart, SrcAnim = s.SrcAnim,
|
||||
ColorPeriod = s.ColorPeriod, ColorStart = s.ColorStart, ColorTarget = s.ColorTarget,
|
||||
ColorAnim = s.ColorAnim, SourceSlot = s.SourceSlot, SrcRect = s.SrcRect, Visible = s.Visible,
|
||||
ScaleCurrent = s.ScaleCurrent, ScaleTarget = s.ScaleTarget,
|
||||
ScaleDelayMs = s.ScaleDelayMs, ScaleDurationMs = s.ScaleDurationMs, ScaleEnabled = s.ScaleEnabled,
|
||||
TranslationCurrent = s.TranslationCurrent, TranslationTarget = s.TranslationTarget,
|
||||
TranslationDelayMs = s.TranslationDelayMs, TranslationDurationMs = s.TranslationDurationMs,
|
||||
TranslationEnabled = s.TranslationEnabled,
|
||||
RotationCurrent = s.RotationCurrent, RotationTarget = s.RotationTarget,
|
||||
RotationDelayMs = s.RotationDelayMs, RotationDurationMs = s.RotationDurationMs,
|
||||
RotationChannelEnabled = s.RotationChannelEnabled, MatrixStartMs = s.MatrixStartMs,
|
||||
RotationPeriodMs = s.RotationPeriodMs, RotationAxis = s.RotationAxis,
|
||||
RotationEnabled = s.RotationEnabled, RotationStartMs = s.RotationStartMs,
|
||||
};
|
||||
CurrentObject = destinationHandle;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Op 0x1a2: retain the operand's current value in the separate descriptor registry. This does
|
||||
/// not populate the retained-object map used by op 0x215.</summary>
|
||||
public void Register(long handle) { lock (_lock) { _operandRegistry.Add(handle); } }
|
||||
@@ -158,6 +204,7 @@ public sealed class GfxState
|
||||
|
||||
// ---- surfaces (image buffers per slot): ctx+0x52bd4[slot], from create/set-texture ----
|
||||
private readonly Dictionary<int, (long ResId, long ColorKey)> _surfaces = new();
|
||||
private readonly Dictionary<int, SurfaceTransition> _surfaceTransitions = new();
|
||||
public void SetSurface(int slot, long resId, long colorKey) { lock (_lock) { _surfaces[slot] = (resId, colorKey); } }
|
||||
|
||||
/// <summary>Ops 0x202/0x203: record a packed 0xAARRGGBB color/alpha modulation on the object and mark it
|
||||
@@ -167,6 +214,27 @@ public sealed class GfxState
|
||||
lock (_lock) { var o = GetOrCreate(handle); o.Color = packed; o.HasColor = true; }
|
||||
}
|
||||
|
||||
/// <summary>Resolve 0x202/0x203's native negative sentinels against obj+0x60 (the current static
|
||||
/// packed color): negative alpha preserves its alpha byte; negative RGB preserves its RGB bytes.</summary>
|
||||
public void SetObjectColorResolved(long handle, long alpha, long rgb)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var o = GetOrCreate(handle);
|
||||
long current = o.HasColor ? o.Color : 0xffffffff;
|
||||
long resolvedAlpha = alpha < 0 ? (current >> 24) & 0xff : System.Math.Min(alpha, 0xff);
|
||||
long resolvedRgb = rgb < 0 ? current & 0xffffff : rgb & 0xffffff;
|
||||
o.Color = PackColor(resolvedAlpha, resolvedRgb);
|
||||
o.HasColor = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetStaticObjectColorResolved(long handle, long mode, long alpha, long rgb)
|
||||
{
|
||||
SetObjectColorResolved(handle, alpha, rgb);
|
||||
lock (_lock) GetOrCreate(handle).StaticColorMode = mode;
|
||||
}
|
||||
|
||||
/// <summary>Ops 0x239 (static cell, period=0) / 0x231 (animate, period>0): set the spritesheet grid +
|
||||
/// the visible cell. When animated, the interpolator ping-pongs the cell across the grid over the period.</summary>
|
||||
public void SetSrcRect(long handle, long gridW, long gridH, long cell, long period)
|
||||
@@ -191,6 +259,73 @@ public sealed class GfxState
|
||||
}
|
||||
public void ClearSurface(int slot) { lock (_lock) { _surfaces[slot] = (0, 0); } } // create-texture (blank)
|
||||
|
||||
/// <summary>Op 0x223: queue a type-0 timed alpha transition into a target surface slot.</summary>
|
||||
public void QueueSurfaceAlphaTransition(long commandKey, int targetSlot,
|
||||
long rangeAStart, int rangeACount, long rangeBStart, int rangeBCount,
|
||||
long delayMs, long durationMs)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_surfaceTransitions[targetSlot] = new SurfaceTransition
|
||||
{
|
||||
CommandKey = commandKey, TargetSlot = targetSlot,
|
||||
RangeAStart = rangeAStart, RangeACount = System.Math.Max(0, rangeACount),
|
||||
RangeBStart = rangeBStart, RangeBCount = System.Math.Max(0, rangeBCount),
|
||||
DelayMs = System.Math.Max(0, delayMs), DurationMs = System.Math.Max(0, durationMs),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Start every pending foreground transition at the native present boundary.</summary>
|
||||
public int StartForegroundTransitions(long nowMs)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
int started = 0;
|
||||
foreach (var t in _surfaceTransitions.Values)
|
||||
if (t.StartMs < 0) { t.StartMs = nowMs; started++; }
|
||||
return started;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasActiveForegroundTransitions(long nowMs)
|
||||
{
|
||||
lock (_lock)
|
||||
return _surfaceTransitions.Values.Any(t => TransitionProgress(t, nowMs) < 1.0);
|
||||
}
|
||||
|
||||
/// <summary>Click completion affects only type-0 foreground transitions, never ambient object channels.</summary>
|
||||
public int CompleteForegroundTransitions(long nowMs)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
int completed = 0;
|
||||
foreach (var t in _surfaceTransitions.Values)
|
||||
if (!t.Forced && TransitionProgress(t, nowMs) < 1.0) { t.Forced = true; completed++; }
|
||||
return completed;
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<SurfaceTransitionState> SnapshotForegroundTransitions(long nowMs)
|
||||
{
|
||||
lock (_lock)
|
||||
return _surfaceTransitions.Values.Select(t => SampleTransition(t, nowMs)).ToList();
|
||||
}
|
||||
|
||||
private static double TransitionProgress(SurfaceTransition t, long nowMs)
|
||||
{
|
||||
if (t.Forced) return 1.0;
|
||||
if (t.StartMs < 0) return 0.0;
|
||||
long elapsed = nowMs - t.StartMs - t.DelayMs;
|
||||
if (elapsed <= 0) return 0.0;
|
||||
if (t.DurationMs <= 0) return 1.0;
|
||||
return System.Math.Clamp(elapsed / (double)t.DurationMs, 0.0, 1.0);
|
||||
}
|
||||
|
||||
private static SurfaceTransitionState SampleTransition(SurfaceTransition t, long nowMs)
|
||||
=> new(t.CommandKey, t.TargetSlot, t.RangeAStart, t.RangeACount, t.RangeBStart, t.RangeBCount,
|
||||
t.DelayMs, t.DurationMs, t.StartMs, TransitionProgress(t, nowMs), t.Forced);
|
||||
|
||||
/// <summary>draw-texture bind (gfx_object_bind_draw): object <paramref name="handle"/> draws surface
|
||||
/// <paramref name="slot"/>'s rect at (dstX,dstY) and becomes visible.</summary>
|
||||
public void BindDraw(long handle, int slot, int sx, int sy, int w, int h, int dstX, int dstY)
|
||||
@@ -289,7 +424,17 @@ public sealed class GfxState
|
||||
if (o.HasColor)
|
||||
{
|
||||
var (a, r, g, b) = BlendMath.UnpackArgb(o.Color);
|
||||
strength = a; tint = ((long)r << 16) | ((long)g << 8) | (long)b; blend = BlendKind.Alpha;
|
||||
tint = ((long)r << 16) | ((long)g << 8) | (long)b;
|
||||
if (o.StaticColorMode == 2)
|
||||
{
|
||||
// Native transition-source mode: 0xffffffff is opaque identity modulation,
|
||||
// not a request to replace every texel with white.
|
||||
alpha = a; strength = 0; blend = BlendKind.Alpha;
|
||||
}
|
||||
else
|
||||
{
|
||||
strength = a; blend = BlendKind.Alpha;
|
||||
}
|
||||
}
|
||||
if (o.ColorAnim)
|
||||
{
|
||||
@@ -342,6 +487,8 @@ public sealed class GfxState
|
||||
cycleAngle = ((elapsed % o.RotationPeriodMs) * 360) / o.RotationPeriodMs;
|
||||
}
|
||||
|
||||
SurfaceTransitionState? transition = _surfaceTransitions.TryGetValue(o.SourceSlot, out var st)
|
||||
? SampleTransition(st, nowMs) : null;
|
||||
list.Add(new RenderObject(kv.Key, resId, ck, srcX, srcY, w, h,
|
||||
(int)o.V24.X, (int)o.V24.Y,
|
||||
new TransformState(scale.X, scale.Y, scale.Z,
|
||||
@@ -351,7 +498,7 @@ public sealed class GfxState
|
||||
new RotationCycleState(o.RotationEnabled, o.RotationPeriodMs,
|
||||
o.RotationAxis.X, o.RotationAxis.Y,
|
||||
o.RotationAxis.Z, cycleAngle),
|
||||
alpha, tint, strength, blend));
|
||||
alpha, tint, strength, blend, transition));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -277,6 +277,11 @@ public sealed class VirtualMachine
|
||||
_host.WaitForInput(); return pc + 1;
|
||||
case "sleep": // 0xc8 (duration) — pause the host duration ms; headless hosts no-op (parity). Frame pacing.
|
||||
_host.Sleep(Read(a[0])); return pc + 1;
|
||||
case "get-message-skip": // 0x1c7: Ctrl/message fast-forward run-state bit
|
||||
Write(a[0], _host.IsMessageSkipActive ? 1 : 0); return pc + 1;
|
||||
case "get-adv-read-skip-state": // 0x1cc: per-message read/click skip service state
|
||||
case "get-adv-service-state": // compatibility with pre-recovery generated tables
|
||||
Write(a[0], _host.IsAdvReadSkipActive ? 1 : 0); return pc + 1;
|
||||
case "end-text-line": case "set-font":
|
||||
case "comment": case "display-furigana": case "dev_ukn":
|
||||
return pc + 1;
|
||||
@@ -362,10 +367,12 @@ public sealed class VirtualMachine
|
||||
Gfx.EraseRange(Read(a[0]), Read(a[1])); return pc + 1;
|
||||
case "gfx-elem-release": // 0x1fa (surface slot)
|
||||
Gfx.ClearSurface((int)Read(a[0])); return pc + 1;
|
||||
case "clone-gfx-object": // 0x21d (source handle)(destination handle)
|
||||
Gfx.CloneObject(Read(a[0]), Read(a[1])); return pc + 1;
|
||||
case "gfx-blit-color": // 0x202 (handle)(x)(y)(alpha)(color) — static alpha/tint (anim interp deferred)
|
||||
Gfx.SetObjectColor(Read(a[0]), GfxState.PackColor(Read(a[3]), Read(a[4]))); return pc + 1;
|
||||
Gfx.SetObjectColorResolved(Read(a[0]), Read(a[3]), Read(a[4])); return pc + 1;
|
||||
case "gfx-draw-color": // 0x203 (handle)(v)(alpha)(color) — static alpha/tint
|
||||
Gfx.SetObjectColor(Read(a[0]), GfxState.PackColor(Read(a[2]), Read(a[3]))); return pc + 1;
|
||||
Gfx.SetStaticObjectColorResolved(Read(a[0]), Read(a[1]), Read(a[2]), Read(a[3])); return pc + 1;
|
||||
// ---- sprite transform / animation cluster (docs/engine-re.md "0x21c-0x243 ... ANIMATION") ----
|
||||
case "set-anim-transform-abs": // 0x220 (handle)(delay)(duration)(tx)(ty)(tz)
|
||||
Gfx.SetTranslationChannel(Read(a[0]), Read(a[1]), Read(a[2]),
|
||||
@@ -382,7 +389,13 @@ public sealed class VirtualMachine
|
||||
Gfx.SetAnimClock(Read(a[0])); return pc + 1;
|
||||
case "reset-anim-clock": // 0x243: reset the separate global animation-service clock
|
||||
Gfx.ResetAnimClock(); return pc + 1;
|
||||
case "mark-frame-yield": // 0x21c: host already yields after every completed opcode
|
||||
case "queue-surface-alpha-transition": // 0x223: target surface crossfade over two object ranges
|
||||
Gfx.QueueSurfaceAlphaTransition(Read(a[0]), (int)Read(a[1]), Read(a[2]), (int)Read(a[3]),
|
||||
Read(a[4]), (int)Read(a[5]), Read(a[6]), Read(a[7])); return pc + 1;
|
||||
case "present-frame": // 0x20c: read/message-skip path snaps a queued transition to its endpoint
|
||||
_host.PresentFrame(Gfx); return pc + 1;
|
||||
case "mark-frame-yield": // 0x21c: normal foreground-transition scheduler/resume boundary
|
||||
_host.WaitForForegroundTransition(Gfx); return pc + 1;
|
||||
case "clear-gfx-command-queue": // 0x224: retained compositor does not use this native queue
|
||||
return pc + 1;
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user