Split GfxState surface operations

This commit is contained in:
gamer147
2026-08-02 21:37:53 -04:00
parent 3291f86f38
commit 18339c799b
4 changed files with 138 additions and 117 deletions

View File

@@ -160,6 +160,12 @@ transitions, diagnostic snapshots, frame/completion publication, and mask teardo
control, and blocking BGM fades; presentation/input retains the message-skip, reset, and frame-pulse consumers control, and blocking BGM fades; presentation/input retains the message-skip, reset, and frame-pulse consumers
of that state through the sealed partial class. of that state through the sealed partial class.
`engine/Age.Engine/Model/GfxState.cs` retains cross-domain retained-graphics coordination.
`engine/Age.Engine/Model/GfxState.Contracts.cs` owns its public render, transition, diagnostic, persistence,
animation, numeric-glyph, and handle-range contracts. `engine/Age.Engine/Model/GfxState.Surfaces.cs` owns surface
resource/color-key state, created/reloadable classification, movie stop-time metadata, render-target/tile
configuration, and surface lifecycle operations.
The disposable `build/page-map-<SCENE>.jsonl` files are produced by editor/development Godot runs and map The disposable `build/page-map-<SCENE>.jsonl` files are produced by editor/development Godot runs and map
runtime ADV page ordinals to their authoritative script offsets for `tools/locate_page.py`. Packaged exports runtime ADV page ordinals to their authoritative script offsets for `tools/locate_page.py`. Packaged exports
have no repository output tree and write their automatic maps below `user://diagnostics/page-maps` instead. have no repository output tree and write their automatic maps below `user://diagnostics/page-maps` instead.

View File

@@ -601,6 +601,12 @@ do not mix mechanical moves with semantic changes.
`GfxState` remains the same sealed runtime type, and every moved declaration is textually unchanged; runtime `GfxState` remains the same sealed runtime type, and every moved declaration is textually unchanged; runtime
validation remains green. validation remains green.
The second bounded `GfxState` split converted the sealed runtime type to a sealed partial class and moved
surface resource/color-key state, created/reloadable classification, movie stop-time metadata,
render-target/tile configuration, and surface lifecycle operations into
`engine/Age.Engine/Model/GfxState.Surfaces.cs`. Reset, persistence, retained-object sampling, and transition
teardown retain direct access through the partial class; runtime validation remains green.
**Gate:** no externally visible behavior or command changes; generated artifacts are byte-identical where **Gate:** no externally visible behavior or command changes; generated artifacts are byte-identical where
deterministic, and the corresponding engine, Python, Godot, and corpus validations remain green after deterministic, and the corresponding engine, Python, Godot, and corpus validations remain green after
each domain move. each domain move.
@@ -972,7 +978,7 @@ layer's rendering diverges from ADV; save layout.
## 8. Immediate next step ## 8. Immediate next step
Continue step 2 of the **codebase consolidation** maintenance slice: behavior-neutral physical splits backed Continue step 2 of the **codebase consolidation** maintenance slice: behavior-neutral physical splits backed
by the tracked launcher and layered validation driver. With the planned `Main` and `GodotAdvHost` domains by the tracked launcher and layered validation driver. With the planned `Main` and `GodotAdvHost` domains
isolated and the `GfxState` public contract layer separated, move `GfxState` surface ownership next, preserving isolated and the first two `GfxState` domains separated, move retained-object ownership next, preserving public
public types, commands, and generated output. types, commands, and generated output.
Concrete playthrough blockers may still preempt this bounded maintenance work; the consolidation effort does Concrete playthrough blockers may still preempt this bounded maintenance work; the consolidation effort does
not replace Phase B gameplay validation or the open cross-platform gates. not replace Phase B gameplay validation or the open cross-platform gates.

View File

@@ -0,0 +1,123 @@
namespace Age.Engine.Model;
public sealed partial class GfxState
{
/// <summary>The D3D render target selected by op 0x20d. -1 denotes the main backbuffer.</summary>
public int CurrentRenderTargetSlot { get; private set; } = -1;
/// <summary>Legacy mode-1 surface tile edge selected by op 0x248. The portable backend retains
/// the native process-global value for parity but stores every surface as one contiguous image.</summary>
public int TiledSurfaceEdgeLength { get; private set; }
// ---- surfaces (image buffers per slot): ctx+0x52bd4[slot], from create/set-texture ----
private readonly Dictionary<int, (long ResId, long ColorKey)> _surfaces = new();
// Created surfaces have real pixels but no asset resource id. Keep their class separate from both
// loaded textures and truly surfaceless objects because native mode-0 consumes packed alpha differently.
private readonly HashSet<int> _createdSurfaces = new();
// Native surface record +0x08. Ordinary create/load/release workers leave this bit unchanged;
// the numbered-save restore loop consults it to decide which asset-backed surfaces to reopen.
private readonly HashSet<int> _reloadableSurfaces = new();
// A separate entry models the native CMovieToTexture object attached to a surface. A null value means
// the movie object exists but its host decoder supplied no usable IMediaPosition stop time.
private readonly Dictionary<int, long?> _movieStopTimesMs = new();
public void SetTiledSurfaceEdgeLength(long edgeLength)
{
// Native stores the complete operand in one signed dword. It does not invalidate or rebuild
// already-created mode-1 surfaces, nor does it mark the retained compositor dirty.
lock (_lock) TiledSurfaceEdgeLength = unchecked((int)edgeLength);
}
public void SetSurface(int slot, long resId, long colorKey)
{
lock (_lock)
{
_surfaces[slot] = (resId, colorKey);
_createdSurfaces.Remove(slot);
_movieStopTimesMs.Remove(slot);
MarkRetainedMutation();
}
}
/// <summary>Set the native surface-record +0x08 reload policy. This is separate from loading a
/// texture because AGE's ordinary create/load/release workers preserve the existing bit.</summary>
public void SetSurfaceReloadOnRestore(int slot, bool reload)
{
lock (_lock)
{
if (reload) _reloadableSurfaces.Add(slot);
else _reloadableSurfaces.Remove(slot);
}
}
/// <summary>Opcode 0x259 script-entry lifecycle: clear record +0x08 for every surface.
/// Native also clears the adjacent unknown +0x0c field, which the port does not otherwise model.</summary>
public void ClearSurfaceReloadPolicies()
{
lock (_lock) _reloadableSurfaces.Clear();
}
/// <summary>Op 0x236 handoff: retain the initialized movie graph's IMediaPosition stop time. Null
/// deliberately distinguishes a movie surface with unavailable metadata from an empty movie slot.</summary>
public void SetMovieStopTime(int slot, long? stopTimeMs)
{
lock (_lock) _movieStopTimesMs[slot] = stopTimeMs;
}
/// <summary>Op 0x23f query. False means no movie object occupies the slot; true with a null value
/// means the movie exists but its stop-time query failed or returned unusable metadata.</summary>
public bool TryGetMovieStopTime(int slot, out long? stopTimeMs)
{
lock (_lock) return _movieStopTimesMs.TryGetValue(slot, out stopTimeMs);
}
/// <summary>Op 0x20d: select a surface as the D3D render target; values at or above 1000 restore the
/// device backbuffer in the native engine.</summary>
public void SelectRenderTarget(long slot)
{
lock (_lock) CurrentRenderTargetSlot = slot is >= 0 and < 1000 ? (int)slot : -1;
}
/// <summary>Op 0x23d: release the transient surface range while retaining system-owned low slots.</summary>
public void ReleaseSurfaceRange(int firstSlot, int count)
{
lock (_lock)
{
int end = checked(firstSlot + count);
for (int slot = firstSlot; slot < end; slot++)
{
_surfaces.Remove(slot);
_createdSurfaces.Remove(slot);
_movieStopTimesMs.Remove(slot);
_surfaceTransitions.Remove(slot);
_movieMaskTransitions.Remove(slot);
}
if (CurrentRenderTargetSlot >= firstSlot && CurrentRenderTargetSlot < end)
CurrentRenderTargetSlot = -1;
MarkRetainedMutation();
}
}
public void CreateSurface(int slot)
{
lock (_lock)
{
_surfaces[slot] = (0, -1); // create-texture: real mutable pixels, no asset id or color key
_createdSurfaces.Add(slot);
_movieStopTimesMs.Remove(slot);
MarkRetainedMutation();
}
}
public void ClearSurface(int slot)
{
lock (_lock)
{
_surfaces.Remove(slot);
_createdSurfaces.Remove(slot);
_movieStopTimesMs.Remove(slot);
_surfaceTransitions.Remove(slot);
_movieMaskTransitions.Remove(slot);
MarkRetainedMutation();
}
}
}

View File

@@ -10,7 +10,7 @@ namespace Age.Engine.Model;
/// slot (returned by 0x215) and three 3-vectors: V18 (set 0x217 / get 0x218, anchor), V24 (set 0x219 / /// slot (returned by 0x215) and three 3-vectors: V18 (set 0x217 / get 0x218, anchor), V24 (set 0x219 /
/// get 0x21a, position), V16c (set 0x1ff). The native DirectDraw workers are NOT modelled — only the data /// get 0x21a, position), V16c (set 0x1ff). The native DirectDraw workers are NOT modelled — only the data
/// the query ops read back, which is all the bytecode geometry math needs.</summary> /// the query ops read back, which is all the bytecode geometry math needs.</summary>
public sealed class GfxState public sealed partial class GfxState
{ {
private sealed class SurfaceTransition private sealed class SurfaceTransition
{ {
@@ -115,11 +115,6 @@ public sealed class GfxState
public long CurrentObject { get; private set; } public long CurrentObject { get; private set; }
/// <summary>EngineCtx+0x14e08, selected by op 0x80 and used by op 0x1d9 when its slot is zero.</summary> /// <summary>EngineCtx+0x14e08, selected by op 0x80 and used by op 0x1d9 when its slot is zero.</summary>
public int DefaultObjectSlot { get; private set; } public int DefaultObjectSlot { get; private set; }
/// <summary>The D3D render target selected by op 0x20d. -1 denotes the main backbuffer.</summary>
public int CurrentRenderTargetSlot { get; private set; } = -1;
/// <summary>Legacy mode-1 surface tile edge selected by op 0x248. The portable backend retains
/// the native process-global value for parity but stores every surface as one contiguous image.</summary>
public int TiledSurfaceEdgeLength { get; private set; }
// ---- Separate global animation service clock (op 0x238; ctx+0x51b7c total / +0x51b78 elapsed). // ---- Separate global animation service clock (op 0x238; ctx+0x51b7c total / +0x51b78 elapsed).
// Retained for its opcode family; 0x21e scale and 0x220 translation use frame-time directly instead. ---- // Retained for its opcode family; 0x21e scale and 0x220 translation use frame-time directly instead. ----
@@ -165,12 +160,6 @@ public sealed class GfxState
lock (_lock) DefaultObjectSlot = slot; lock (_lock) DefaultObjectSlot = slot;
} }
public void SetTiledSurfaceEdgeLength(long edgeLength)
{
// Native stores the complete operand in one signed dword. It does not invalidate or rebuild
// already-created mode-1 surfaces, nor does it mark the retained compositor dirty.
lock (_lock) TiledSurfaceEdgeLength = unchecked((int)edgeLength);
}
public void SetObjectAnchor(long handle, (long X, long Y, long Z) anchor) public void SetObjectAnchor(long handle, (long X, long Y, long Z) anchor)
{ {
@@ -426,88 +415,8 @@ public sealed class GfxState
private readonly object _lock = new(); private readonly object _lock = new();
// ---- surfaces (image buffers per slot): ctx+0x52bd4[slot], from create/set-texture ----
private readonly Dictionary<int, (long ResId, long ColorKey)> _surfaces = new();
// Created surfaces have real pixels but no asset resource id. Keep their class separate from both
// loaded textures and truly surfaceless objects because native mode-0 consumes packed alpha differently.
private readonly HashSet<int> _createdSurfaces = new();
// Native surface record +0x08. Ordinary create/load/release workers leave this bit unchanged;
// the numbered-save restore loop consults it to decide which asset-backed surfaces to reopen.
private readonly HashSet<int> _reloadableSurfaces = new();
// A separate entry models the native CMovieToTexture object attached to a surface. A null value means
// the movie object exists but its host decoder supplied no usable IMediaPosition stop time.
private readonly Dictionary<int, long?> _movieStopTimesMs = new();
private readonly Dictionary<int, SurfaceTransition> _surfaceTransitions = new(); private readonly Dictionary<int, SurfaceTransition> _surfaceTransitions = new();
private readonly Dictionary<int, MovieMaskTransition> _movieMaskTransitions = new(); private readonly Dictionary<int, MovieMaskTransition> _movieMaskTransitions = new();
public void SetSurface(int slot, long resId, long colorKey)
{
lock (_lock)
{
_surfaces[slot] = (resId, colorKey);
_createdSurfaces.Remove(slot);
_movieStopTimesMs.Remove(slot);
MarkRetainedMutation();
}
}
/// <summary>Set the native surface-record +0x08 reload policy. This is separate from loading a
/// texture because AGE's ordinary create/load/release workers preserve the existing bit.</summary>
public void SetSurfaceReloadOnRestore(int slot, bool reload)
{
lock (_lock)
{
if (reload) _reloadableSurfaces.Add(slot);
else _reloadableSurfaces.Remove(slot);
}
}
/// <summary>Opcode 0x259 script-entry lifecycle: clear record +0x08 for every surface.
/// Native also clears the adjacent unknown +0x0c field, which the port does not otherwise model.</summary>
public void ClearSurfaceReloadPolicies()
{
lock (_lock) _reloadableSurfaces.Clear();
}
/// <summary>Op 0x236 handoff: retain the initialized movie graph's IMediaPosition stop time. Null
/// deliberately distinguishes a movie surface with unavailable metadata from an empty movie slot.</summary>
public void SetMovieStopTime(int slot, long? stopTimeMs)
{
lock (_lock) _movieStopTimesMs[slot] = stopTimeMs;
}
/// <summary>Op 0x23f query. False means no movie object occupies the slot; true with a null value
/// means the movie exists but its stop-time query failed or returned unusable metadata.</summary>
public bool TryGetMovieStopTime(int slot, out long? stopTimeMs)
{
lock (_lock) return _movieStopTimesMs.TryGetValue(slot, out stopTimeMs);
}
/// <summary>Op 0x20d: select a surface as the D3D render target; values at or above 1000 restore the
/// device backbuffer in the native engine.</summary>
public void SelectRenderTarget(long slot)
{
lock (_lock) CurrentRenderTargetSlot = slot is >= 0 and < 1000 ? (int)slot : -1;
}
/// <summary>Op 0x23d: release the transient surface range while retaining system-owned low slots.</summary>
public void ReleaseSurfaceRange(int firstSlot, int count)
{
lock (_lock)
{
int end = checked(firstSlot + count);
for (int slot = firstSlot; slot < end; slot++)
{
_surfaces.Remove(slot);
_createdSurfaces.Remove(slot);
_movieStopTimesMs.Remove(slot);
_surfaceTransitions.Remove(slot);
_movieMaskTransitions.Remove(slot);
}
if (CurrentRenderTargetSlot >= firstSlot && CurrentRenderTargetSlot < end)
CurrentRenderTargetSlot = -1;
MarkRetainedMutation();
}
}
/// <summary>Ops 0x202/0x203: record a packed 0xAARRGGBB color/alpha modulation on the object and mark it /// <summary>Ops 0x202/0x203: record a packed 0xAARRGGBB color/alpha modulation on the object and mark it
/// HasColor so the compositor applies alpha+tint (vs the opaque default).</summary> /// HasColor so the compositor applies alpha+tint (vs the opaque default).</summary>
@@ -603,29 +512,6 @@ public sealed class GfxState
o.ColorAnim = true; o.ColorAnim = true;
} }
} }
public void CreateSurface(int slot)
{
lock (_lock)
{
_surfaces[slot] = (0, -1); // create-texture: real mutable pixels, no asset id or color key
_createdSurfaces.Add(slot);
_movieStopTimesMs.Remove(slot);
MarkRetainedMutation();
}
}
public void ClearSurface(int slot)
{
lock (_lock)
{
_surfaces.Remove(slot);
_createdSurfaces.Remove(slot);
_movieStopTimesMs.Remove(slot);
_surfaceTransitions.Remove(slot);
_movieMaskTransitions.Remove(slot);
MarkRetainedMutation();
}
}
/// <summary>Op 0x223: queue a type-0 timed alpha transition into a target surface slot.</summary> /// <summary>Op 0x223: queue a type-0 timed alpha transition into a target surface slot.</summary>
public void QueueSurfaceAlphaTransition(long commandKey, int targetSlot, public void QueueSurfaceAlphaTransition(long commandKey, int targetSlot,