Split GfxState retained objects
This commit is contained in:
@@ -164,7 +164,9 @@ of that state through the sealed partial class.
|
|||||||
`engine/Age.Engine/Model/GfxState.Contracts.cs` owns its public render, transition, diagnostic, persistence,
|
`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
|
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
|
resource/color-key state, created/reloadable classification, movie stop-time metadata, render-target/tile
|
||||||
configuration, and surface lifecycle operations.
|
configuration, and surface lifecycle operations. `engine/Age.Engine/Model/GfxState.RetainedObjects.cs` owns the
|
||||||
|
retained-object record and registry/index, range-transform state, object creation/query/clone/erase and draw
|
||||||
|
binding, plus numeric-glyph object generation.
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -607,6 +607,11 @@ do not mix mechanical moves with semantic changes.
|
|||||||
`engine/Age.Engine/Model/GfxState.Surfaces.cs`. Reset, persistence, retained-object sampling, and transition
|
`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.
|
teardown retain direct access through the partial class; runtime validation remains green.
|
||||||
|
|
||||||
|
The third bounded `GfxState` split moved the retained-object record and registry/index, range-transform
|
||||||
|
state and operations, object creation/query/clone/erase and draw binding, and numeric-glyph object generation
|
||||||
|
into `engine/Age.Engine/Model/GfxState.RetainedObjects.cs`. Cross-domain reset/persistence, animation, and
|
||||||
|
presentation sampling retain direct access through the sealed 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.
|
||||||
@@ -978,7 +983,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 first two `GfxState` domains separated, move retained-object ownership next, preserving public
|
isolated and the first three `GfxState` domains separated, move animation ownership next, preserving public types,
|
||||||
types, commands, and generated output.
|
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.
|
||||||
|
|||||||
379
engine/Age.Engine/Model/GfxState.RetainedObjects.cs
Normal file
379
engine/Age.Engine/Model/GfxState.RetainedObjects.cs
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
namespace Age.Engine.Model;
|
||||||
|
|
||||||
|
public sealed partial class GfxState
|
||||||
|
{
|
||||||
|
public sealed class GfxObject
|
||||||
|
{
|
||||||
|
// The native numbered-save record contains several full transform matrices and reserved fields
|
||||||
|
// beyond the port's semantic model. Preserve the decoded bytes so a native load/save round trip
|
||||||
|
// does not erase state that the port has not modeled yet; known fields are patched by the codec.
|
||||||
|
public byte[]? NativePersistenceRecord;
|
||||||
|
public (long X, long Y, long Z) V18, V24, V16c;
|
||||||
|
public long Color = 0xffffffff; // native gfx_object_init_default obj+0x60: identity packed ARGB
|
||||||
|
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
|
||||||
|
// Op 0x202 one-shot packed-color channel: current +0x60, target +0x64, delay +0x38,
|
||||||
|
// duration +0x4c. It shares obj+0x34's start timestamp with the one-shot matrix channels.
|
||||||
|
public long OneShotColorTarget = -1, ColorDelayMs, ColorDurationMs;
|
||||||
|
public bool OneShotColorEnabled;
|
||||||
|
// Once op 0x202 arms this object's color channel, its sampled/current ARGB is consumed as D3D
|
||||||
|
// opacity + multiplicative modulation even after the target commits. This distinguishes ADV chrome
|
||||||
|
// fades from a static mode-0 0x203 such as a CG initialized with 0x00ffffff (opaque identity).
|
||||||
|
public bool OneShotColorBlend;
|
||||||
|
// A mode-0 static color written after draw-texture binds an object consumes packed alpha as
|
||||||
|
// opacity. FIELD uses this to suppress a bound idle unit, and CALLBACK_SETTING uses it when
|
||||||
|
// rebuilding the ADV backing after CONFIG. A later draw-texture rebind clears the latch.
|
||||||
|
public bool PostBindStaticColorBlend;
|
||||||
|
|
||||||
|
// ---- src-rect / spritesheet-cell channel (ops 0x239 one-shot cell, 0x231 looping animation).
|
||||||
|
// Native obj+0x238 is the TOTAL FRAME COUNT and +0x23c is the COLUMN COUNT. Each frame keeps
|
||||||
|
// draw-texture's original source-rect size; those operands are not a grid to divide it by. ----
|
||||||
|
public long SrcFrameCount = 1, SrcColumns = 1, SrcCell, SrcPeriod, SrcStart = -1;
|
||||||
|
public bool SrcAnim;
|
||||||
|
// ---- animated color/glow channel (op 0x232). Interpolator COLOR channel: period obj+0x220,
|
||||||
|
// start obj+0x20c, target obj+0x240 — PING-PONG (distinct from static 0x202/0x203). ----
|
||||||
|
public long ColorPeriod, ColorStart = -1, ColorTarget;
|
||||||
|
public bool ColorAnim;
|
||||||
|
// draw-texture bind (gfx_object_bind_draw): the surface to draw + its source rect + the visible flag.
|
||||||
|
// Native gfx_object_init_default zeroes obj+4. Querying an object created by a geometry/animation
|
||||||
|
// setter therefore returns slot 0 even before draw-texture binds it; only an absent object returns -1.
|
||||||
|
public int SourceSlot;
|
||||||
|
public (int X, int Y, int W, int H) SrcRect;
|
||||||
|
public bool Visible;
|
||||||
|
|
||||||
|
// ---- independent one-shot matrix channels (gfx_object_apply_transform_channels@0x472f00). ----
|
||||||
|
// Scale: current obj+0x6c, target obj+0xac, delay obj+0x3c, duration obj+0x50.
|
||||||
|
public (double X, double Y, double Z) ScaleCurrent = (1, 1, 1), ScaleTarget = (1, 1, 1);
|
||||||
|
public long ScaleDelayMs, ScaleDurationMs;
|
||||||
|
public bool ScaleEnabled;
|
||||||
|
// Translation: current obj+0x16c, target obj+0x1ac, delay obj+0x44, duration obj+0x58.
|
||||||
|
public (double X, double Y, double Z) TranslationCurrent, TranslationTarget;
|
||||||
|
public long TranslationDelayMs, TranslationDurationMs;
|
||||||
|
public bool TranslationEnabled;
|
||||||
|
public (double X, double Y, double Z, double Angle) RotationCurrent;
|
||||||
|
public (double X, double Y, double Z, double Angle) RotationTarget;
|
||||||
|
public long RotationDelayMs, RotationDurationMs;
|
||||||
|
public bool RotationChannelEnabled;
|
||||||
|
// Op 0x242 writes obj+0x2d0. Bit 0 detaches the finite one-shot group from the blocking-dirty
|
||||||
|
// service and protects it from op 0x243's global force-completion request until natural completion.
|
||||||
|
public long OneShotAnimationControlFlags;
|
||||||
|
// Shared matrix-channel start timestamp obj+0x34, seeded from retained-gfx owner+0xb550
|
||||||
|
// (EngineCtx+0x51b64).
|
||||||
|
public long OneShotStartMs = -1;
|
||||||
|
|
||||||
|
// Op 0x234 is a separate cyclic rotation channel (period obj+0x228, axis obj+0x244..0x24c).
|
||||||
|
public long RotationPeriodMs;
|
||||||
|
public (long X, long Y, long Z) RotationAxis;
|
||||||
|
public bool RotationEnabled;
|
||||||
|
public long RotationStartMs = -1;
|
||||||
|
|
||||||
|
// Op 0x233 is a separate cyclic scale channel (period obj+0x224, target matrix obj+0x250).
|
||||||
|
public long ScaleCyclePeriodMs;
|
||||||
|
public (double X, double Y, double Z) ScaleCycleTarget = (1, 1, 1);
|
||||||
|
public bool ScaleCycleEnabled;
|
||||||
|
public long ScaleCycleStartMs = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- geometry/draw object store (V18/V24/draw bind, the compositor's input) ----
|
||||||
|
// Populated lazily by the geometry SET ops and draw-texture. Op 0x215 queries this same native map and
|
||||||
|
// returns the object's live source slot (obj+4), or -1 when the handle has not been drawn/bound yet.
|
||||||
|
private readonly Dictionary<long, GfxObject> _objects = new();
|
||||||
|
// Native composition is handle-ascending z order. Mutations maintain this small index so snapshots do
|
||||||
|
// not rebuild/sort a dictionary-sized LINQ buffer, while hot handle lookup remains O(1).
|
||||||
|
private readonly List<long> _orderedObjectHandles = new();
|
||||||
|
private readonly NumericGlyphStyle[] _numericGlyphStyles = new NumericGlyphStyle[11];
|
||||||
|
|
||||||
|
// Ops 0x229-0x22e address one embedded gfx-object record outside the ordinary object map. Its sampled
|
||||||
|
// matrix is post-multiplied onto only the selected handle range during native composition. FIELD uses
|
||||||
|
// this as its map camera while the surrounding dungeon UI remains screen-fixed.
|
||||||
|
private long _rangeTransformFirst, _rangeTransformCount;
|
||||||
|
private GfxObject _rangeTransform = new();
|
||||||
|
|
||||||
|
private readonly Dictionary<long, long> _fieldTable = new(); // ctx+0x46d14 (0x216); no family writer -> default 0
|
||||||
|
public long CurrentObject { get; private set; }
|
||||||
|
/// <summary>EngineCtx+0x14e08, selected by op 0x80 and used by op 0x1d9 when its slot is zero.</summary>
|
||||||
|
public int DefaultObjectSlot { 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
|
||||||
|
{
|
||||||
|
get { foreach (var kv in _objects) yield return (kv.Key, kv.Value.SourceSlot); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public GfxObject GetOrCreate(long handle)
|
||||||
|
{
|
||||||
|
// Locked: called from the VM thread (directly by geometry/animation ops and inside BindDraw/anim
|
||||||
|
// ops) while the main-thread compositor enumerates _objects in SnapshotVisibleObjects. _lock is re-entrant
|
||||||
|
// (Monitor) so the callers that already hold it are fine.
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (!_objects.TryGetValue(handle, out var o))
|
||||||
|
{
|
||||||
|
o = new GfxObject();
|
||||||
|
_objects[handle] = o;
|
||||||
|
InsertOrderedHandle(handle);
|
||||||
|
}
|
||||||
|
CurrentObject = handle;
|
||||||
|
MarkRetainedMutation();
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetDefaultObjectSlot(int slot)
|
||||||
|
{
|
||||||
|
lock (_lock) DefaultObjectSlot = slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void SetObjectAnchor(long handle, (long X, long Y, long Z) anchor)
|
||||||
|
{
|
||||||
|
lock (_lock) GetOrCreate(handle).V18 = anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetObjectPosition(long handle, (long X, long Y, long Z) position)
|
||||||
|
{
|
||||||
|
lock (_lock) GetOrCreate(handle).V24 = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Op 0x229: reset the embedded range transform, select [first, first+count), and set its
|
||||||
|
/// anchor/pivot. This does not create or mutate an ordinary retained object.</summary>
|
||||||
|
public void SetRangeTransform(long first, long count, (long X, long Y, long Z) anchor)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_rangeTransformFirst = first;
|
||||||
|
_rangeTransformCount = System.Math.Max(0, count);
|
||||||
|
_rangeTransform = new GfxObject { V18 = anchor };
|
||||||
|
MarkRetainedMutation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Op 0x22a: immediately replace the embedded range transform's current scale.</summary>
|
||||||
|
public void SetRangeScaleCurrent((long X, long Y, long Z) percent)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_rangeTransform.ScaleCurrent = (percent.X / 100.0, percent.Y / 100.0, percent.Z / 100.0);
|
||||||
|
MarkRetainedMutation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Op 0x22c: immediately replace the embedded range transform's current translation.</summary>
|
||||||
|
public void SetRangeTranslationCurrent((long X, long Y, long Z) translation)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_rangeTransform.TranslationCurrent = translation;
|
||||||
|
MarkRetainedMutation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Op 0x22d: arm the range transform's delayed one-shot scale target.</summary>
|
||||||
|
public void SetRangeScaleChannel(long delayMs, long durationMs, (long X, long Y, long Z) percent)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_rangeTransform.ScaleDelayMs = delayMs;
|
||||||
|
_rangeTransform.ScaleDurationMs = durationMs;
|
||||||
|
_rangeTransform.ScaleTarget = (percent.X / 100.0, percent.Y / 100.0, percent.Z / 100.0);
|
||||||
|
_rangeTransform.ScaleEnabled = durationMs > 0;
|
||||||
|
_rangeTransform.OneShotStartMs = -1;
|
||||||
|
MarkRetainedMutation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
bool destinationIsNew = !_objects.ContainsKey(destinationHandle);
|
||||||
|
_objects[destinationHandle] = CloneState(s);
|
||||||
|
if (destinationIsNew) InsertOrderedHandle(destinationHandle);
|
||||||
|
CurrentObject = destinationHandle;
|
||||||
|
MarkRetainedMutation();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GfxObject? TryGet(long handle) => _objects.TryGetValue(handle, out var o) ? o : null;
|
||||||
|
|
||||||
|
/// <summary>Op 0x215: look up <paramref name="handle"/> in the retained gfx-object map and return obj+4,
|
||||||
|
/// the live source-surface slot written by draw-texture, or -1 when absent/unbound.</summary>
|
||||||
|
public int QuerySlot(long handle)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
return _objects.TryGetValue(handle, out var o) ? o.SourceSlot : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Rebind one retained object from one source surface to another. Used by SC0000's bounded
|
||||||
|
/// movie site to reproduce the native warm-engine slot assignment before the following static loaders
|
||||||
|
/// reuse the port's cold-bootstrap slot.</summary>
|
||||||
|
public void RemapObjectSurface(long handle, int fromSlot, int toSlot)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
if (_objects.TryGetValue(handle, out var obj) && obj.SourceSlot == fromSlot)
|
||||||
|
{
|
||||||
|
obj.SourceSlot = toSlot;
|
||||||
|
MarkRetainedMutation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public long QueryField(long idx) => _fieldTable.TryGetValue(idx, out var v) ? v : 0;
|
||||||
|
|
||||||
|
public void Release(long handle)
|
||||||
|
{
|
||||||
|
lock (_lock) // re-entrant: EraseRange already holds _lock
|
||||||
|
{
|
||||||
|
if (_objects.Remove(handle))
|
||||||
|
{
|
||||||
|
int index = _orderedObjectHandles.BinarySearch(handle);
|
||||||
|
if (index >= 0) _orderedObjectHandles.RemoveAt(index);
|
||||||
|
MarkRetainedMutation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Op 0x1f7 semantics (native gfx_object_erase_range @0x47d8b0): erase handles in
|
||||||
|
/// [handle, handle+count) when count>1, else just <paramref name="handle"/>. It is a teardown/erase,
|
||||||
|
/// NOT a create — objects are created lazily by the geometry SET ops (gfx_object_get_or_create).</summary>
|
||||||
|
public void EraseRange(long handle, long count)
|
||||||
|
{
|
||||||
|
// Retained-object cleanup (native gfx_object_erase): removes the object from the map, so it stops
|
||||||
|
// compositing next frame. Faithful to the engine (the render loop iterates the retained-object map).
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (count > 1) for (long i = handle; i < handle + count; i++) Release(i);
|
||||||
|
else Release(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Op 0x1f6: clear every retained gfx-object record without releasing surface resources.</summary>
|
||||||
|
public void ClearRetainedObjects()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_objects.Clear();
|
||||||
|
_orderedObjectHandles.Clear();
|
||||||
|
CurrentObject = 0;
|
||||||
|
MarkRetainedMutation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GfxObject CloneState(GfxObject s)
|
||||||
|
=> new()
|
||||||
|
{
|
||||||
|
NativePersistenceRecord = s.NativePersistenceRecord?.ToArray(),
|
||||||
|
V18 = s.V18, V24 = s.V24, V16c = s.V16c,
|
||||||
|
Color = s.Color, HasColor = s.HasColor, StaticColorMode = s.StaticColorMode,
|
||||||
|
OneShotColorTarget = s.OneShotColorTarget, ColorDelayMs = s.ColorDelayMs,
|
||||||
|
ColorDurationMs = s.ColorDurationMs, OneShotColorEnabled = s.OneShotColorEnabled,
|
||||||
|
OneShotColorBlend = s.OneShotColorBlend, PostBindStaticColorBlend = s.PostBindStaticColorBlend,
|
||||||
|
SrcFrameCount = s.SrcFrameCount, SrcColumns = s.SrcColumns, 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,
|
||||||
|
OneShotAnimationControlFlags = s.OneShotAnimationControlFlags,
|
||||||
|
OneShotStartMs = s.OneShotStartMs,
|
||||||
|
RotationPeriodMs = s.RotationPeriodMs, RotationAxis = s.RotationAxis,
|
||||||
|
RotationEnabled = s.RotationEnabled, RotationStartMs = s.RotationStartMs,
|
||||||
|
ScaleCyclePeriodMs = s.ScaleCyclePeriodMs, ScaleCycleTarget = s.ScaleCycleTarget,
|
||||||
|
ScaleCycleEnabled = s.ScaleCycleEnabled, ScaleCycleStartMs = s.ScaleCycleStartMs,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
var o = GetOrCreate(handle);
|
||||||
|
o.SourceSlot = slot; o.SrcRect = (sx, sy, w, h); o.V24 = (dstX, dstY, 0); o.Visible = true;
|
||||||
|
o.PostBindStaticColorBlend = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Op 0x13a: replace one of eleven safely isolated handler-addressable styles.
|
||||||
|
/// Native physically clears ten records; its admitted index 10 aliases return-stack storage.</summary>
|
||||||
|
public bool RegisterNumericGlyphStyle(int styleIndex, int surfaceSlot, int atlasX, int atlasY,
|
||||||
|
int digitWidth, int digitHeight)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if ((uint)styleIndex >= (uint)_numericGlyphStyles.Length) return false;
|
||||||
|
_numericGlyphStyles[styleIndex] =
|
||||||
|
new NumericGlyphStyle(surfaceSlot, atlasX, atlasY, digitWidth, digitHeight);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Op 0x23b: erase a destination handle range, split a signed 32-bit value into decimal
|
||||||
|
/// digits, and bind one retained object per displayed atlas cell. Returns false for an invalid or
|
||||||
|
/// unregistered style, matching the native handler's script-error path.</summary>
|
||||||
|
public bool DrawDecimalGlyphs(long baseHandle, int styleIndex, int value, int x, int y,
|
||||||
|
int digitCapacity, int flags)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if ((uint)styleIndex >= (uint)_numericGlyphStyles.Length
|
||||||
|
|| !_numericGlyphStyles[styleIndex].Registered) return false;
|
||||||
|
|
||||||
|
NumericGlyphStyle style = _numericGlyphStyles[styleIndex];
|
||||||
|
EraseRange(baseHandle, digitCapacity);
|
||||||
|
|
||||||
|
int remaining = value;
|
||||||
|
int handleOffset = 0;
|
||||||
|
int digitCount = 1;
|
||||||
|
for (int n = remaining / 10; n != 0; n /= 10) digitCount++;
|
||||||
|
|
||||||
|
for (int slot = digitCapacity - 1; slot >= 0; slot--)
|
||||||
|
{
|
||||||
|
int digit = remaining % 10;
|
||||||
|
int drawX;
|
||||||
|
bool draw;
|
||||||
|
|
||||||
|
if ((flags & 0x2) != 0)
|
||||||
|
{
|
||||||
|
draw = slot == digitCapacity - 1 || remaining != 0;
|
||||||
|
drawX = unchecked(x + style.DigitWidth * slot
|
||||||
|
- (digitCapacity - digitCount) * style.DigitWidth / 2);
|
||||||
|
}
|
||||||
|
else if ((flags & 0x4) != 0)
|
||||||
|
{
|
||||||
|
digitCount--;
|
||||||
|
draw = slot == digitCapacity - 1 || remaining != 0;
|
||||||
|
drawX = unchecked(x + style.DigitWidth * digitCount);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
draw = (flags & 0x1) != 0 || slot == digitCapacity - 1 || remaining != 0;
|
||||||
|
drawX = unchecked(x + style.DigitWidth * slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (draw)
|
||||||
|
{
|
||||||
|
int srcX = unchecked(style.AtlasX + digit * style.DigitWidth);
|
||||||
|
BindDraw(baseHandle + handleOffset, style.SurfaceSlot,
|
||||||
|
srcX, style.AtlasY, style.DigitWidth, style.DigitHeight, drawX, y);
|
||||||
|
handleOffset++;
|
||||||
|
}
|
||||||
|
remaining /= 10;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InsertOrderedHandle(long handle)
|
||||||
|
{
|
||||||
|
int index = _orderedObjectHandles.BinarySearch(handle);
|
||||||
|
if (index < 0) _orderedObjectHandles.Insert(~index, handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,97 +24,7 @@ public sealed partial class GfxState
|
|||||||
public required MovieMaskTransitionRequest Request;
|
public required MovieMaskTransitionRequest Request;
|
||||||
public bool Completed;
|
public bool Completed;
|
||||||
}
|
}
|
||||||
public sealed class GfxObject
|
|
||||||
{
|
|
||||||
// The native numbered-save record contains several full transform matrices and reserved fields
|
|
||||||
// beyond the port's semantic model. Preserve the decoded bytes so a native load/save round trip
|
|
||||||
// does not erase state that the port has not modeled yet; known fields are patched by the codec.
|
|
||||||
public byte[]? NativePersistenceRecord;
|
|
||||||
public (long X, long Y, long Z) V18, V24, V16c;
|
|
||||||
public long Color = 0xffffffff; // native gfx_object_init_default obj+0x60: identity packed ARGB
|
|
||||||
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
|
|
||||||
// Op 0x202 one-shot packed-color channel: current +0x60, target +0x64, delay +0x38,
|
|
||||||
// duration +0x4c. It shares obj+0x34's start timestamp with the one-shot matrix channels.
|
|
||||||
public long OneShotColorTarget = -1, ColorDelayMs, ColorDurationMs;
|
|
||||||
public bool OneShotColorEnabled;
|
|
||||||
// Once op 0x202 arms this object's color channel, its sampled/current ARGB is consumed as D3D
|
|
||||||
// opacity + multiplicative modulation even after the target commits. This distinguishes ADV chrome
|
|
||||||
// fades from a static mode-0 0x203 such as a CG initialized with 0x00ffffff (opaque identity).
|
|
||||||
public bool OneShotColorBlend;
|
|
||||||
// A mode-0 static color written after draw-texture binds an object consumes packed alpha as
|
|
||||||
// opacity. FIELD uses this to suppress a bound idle unit, and CALLBACK_SETTING uses it when
|
|
||||||
// rebuilding the ADV backing after CONFIG. A later draw-texture rebind clears the latch.
|
|
||||||
public bool PostBindStaticColorBlend;
|
|
||||||
|
|
||||||
// ---- src-rect / spritesheet-cell channel (ops 0x239 one-shot cell, 0x231 looping animation).
|
|
||||||
// Native obj+0x238 is the TOTAL FRAME COUNT and +0x23c is the COLUMN COUNT. Each frame keeps
|
|
||||||
// draw-texture's original source-rect size; those operands are not a grid to divide it by. ----
|
|
||||||
public long SrcFrameCount = 1, SrcColumns = 1, SrcCell, SrcPeriod, SrcStart = -1;
|
|
||||||
public bool SrcAnim;
|
|
||||||
// ---- animated color/glow channel (op 0x232). Interpolator COLOR channel: period obj+0x220,
|
|
||||||
// start obj+0x20c, target obj+0x240 — PING-PONG (distinct from static 0x202/0x203). ----
|
|
||||||
public long ColorPeriod, ColorStart = -1, ColorTarget;
|
|
||||||
public bool ColorAnim;
|
|
||||||
// draw-texture bind (gfx_object_bind_draw): the surface to draw + its source rect + the visible flag.
|
|
||||||
// Native gfx_object_init_default zeroes obj+4. Querying an object created by a geometry/animation
|
|
||||||
// setter therefore returns slot 0 even before draw-texture binds it; only an absent object returns -1.
|
|
||||||
public int SourceSlot;
|
|
||||||
public (int X, int Y, int W, int H) SrcRect;
|
|
||||||
public bool Visible;
|
|
||||||
|
|
||||||
// ---- independent one-shot matrix channels (gfx_object_apply_transform_channels@0x472f00). ----
|
|
||||||
// Scale: current obj+0x6c, target obj+0xac, delay obj+0x3c, duration obj+0x50.
|
|
||||||
public (double X, double Y, double Z) ScaleCurrent = (1, 1, 1), ScaleTarget = (1, 1, 1);
|
|
||||||
public long ScaleDelayMs, ScaleDurationMs;
|
|
||||||
public bool ScaleEnabled;
|
|
||||||
// Translation: current obj+0x16c, target obj+0x1ac, delay obj+0x44, duration obj+0x58.
|
|
||||||
public (double X, double Y, double Z) TranslationCurrent, TranslationTarget;
|
|
||||||
public long TranslationDelayMs, TranslationDurationMs;
|
|
||||||
public bool TranslationEnabled;
|
|
||||||
public (double X, double Y, double Z, double Angle) RotationCurrent;
|
|
||||||
public (double X, double Y, double Z, double Angle) RotationTarget;
|
|
||||||
public long RotationDelayMs, RotationDurationMs;
|
|
||||||
public bool RotationChannelEnabled;
|
|
||||||
// Op 0x242 writes obj+0x2d0. Bit 0 detaches the finite one-shot group from the blocking-dirty
|
|
||||||
// service and protects it from op 0x243's global force-completion request until natural completion.
|
|
||||||
public long OneShotAnimationControlFlags;
|
|
||||||
// Shared matrix-channel start timestamp obj+0x34, seeded from retained-gfx owner+0xb550
|
|
||||||
// (EngineCtx+0x51b64).
|
|
||||||
public long OneShotStartMs = -1;
|
|
||||||
|
|
||||||
// Op 0x234 is a separate cyclic rotation channel (period obj+0x228, axis obj+0x244..0x24c).
|
|
||||||
public long RotationPeriodMs;
|
|
||||||
public (long X, long Y, long Z) RotationAxis;
|
|
||||||
public bool RotationEnabled;
|
|
||||||
public long RotationStartMs = -1;
|
|
||||||
|
|
||||||
// Op 0x233 is a separate cyclic scale channel (period obj+0x224, target matrix obj+0x250).
|
|
||||||
public long ScaleCyclePeriodMs;
|
|
||||||
public (double X, double Y, double Z) ScaleCycleTarget = (1, 1, 1);
|
|
||||||
public bool ScaleCycleEnabled;
|
|
||||||
public long ScaleCycleStartMs = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- geometry/draw object store (V18/V24/draw bind, the compositor's input) ----
|
|
||||||
// Populated lazily by the geometry SET ops and draw-texture. Op 0x215 queries this same native map and
|
|
||||||
// returns the object's live source slot (obj+4), or -1 when the handle has not been drawn/bound yet.
|
|
||||||
private readonly Dictionary<long, GfxObject> _objects = new();
|
|
||||||
// Native composition is handle-ascending z order. Mutations maintain this small index so snapshots do
|
|
||||||
// not rebuild/sort a dictionary-sized LINQ buffer, while hot handle lookup remains O(1).
|
|
||||||
private readonly List<long> _orderedObjectHandles = new();
|
|
||||||
private readonly NumericGlyphStyle[] _numericGlyphStyles = new NumericGlyphStyle[11];
|
|
||||||
|
|
||||||
// Ops 0x229-0x22e address one embedded gfx-object record outside the ordinary object map. Its sampled
|
|
||||||
// matrix is post-multiplied onto only the selected handle range during native composition. FIELD uses
|
|
||||||
// this as its map camera while the surrounding dungeon UI remains screen-fixed.
|
|
||||||
private long _rangeTransformFirst, _rangeTransformCount;
|
|
||||||
private GfxObject _rangeTransform = new();
|
|
||||||
|
|
||||||
private readonly Dictionary<long, long> _fieldTable = new(); // ctx+0x46d14 (0x216); no family writer -> default 0
|
|
||||||
public long CurrentObject { get; private set; }
|
|
||||||
/// <summary>EngineCtx+0x14e08, selected by op 0x80 and used by op 0x1d9 when its slot is zero.</summary>
|
|
||||||
public int DefaultObjectSlot { 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. ----
|
||||||
@@ -130,171 +40,6 @@ public sealed partial class GfxState
|
|||||||
|
|
||||||
private void MarkRetainedMutation() => _mutationGeneration++;
|
private void MarkRetainedMutation() => _mutationGeneration++;
|
||||||
|
|
||||||
/// <summary>Live geometry objects and the surface slot they draw from — for the CLI gfx oracle.</summary>
|
|
||||||
public IEnumerable<(long Handle, int Slot)> Objects
|
|
||||||
{
|
|
||||||
get { foreach (var kv in _objects) yield return (kv.Key, kv.Value.SourceSlot); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public GfxObject GetOrCreate(long handle)
|
|
||||||
{
|
|
||||||
// Locked: called from the VM thread (directly by geometry/animation ops and inside BindDraw/anim
|
|
||||||
// ops) while the main-thread compositor enumerates _objects in SnapshotVisibleObjects. _lock is re-entrant
|
|
||||||
// (Monitor) so the callers that already hold it are fine.
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
if (!_objects.TryGetValue(handle, out var o))
|
|
||||||
{
|
|
||||||
o = new GfxObject();
|
|
||||||
_objects[handle] = o;
|
|
||||||
InsertOrderedHandle(handle);
|
|
||||||
}
|
|
||||||
CurrentObject = handle;
|
|
||||||
MarkRetainedMutation();
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetDefaultObjectSlot(int slot)
|
|
||||||
{
|
|
||||||
lock (_lock) DefaultObjectSlot = slot;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void SetObjectAnchor(long handle, (long X, long Y, long Z) anchor)
|
|
||||||
{
|
|
||||||
lock (_lock) GetOrCreate(handle).V18 = anchor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetObjectPosition(long handle, (long X, long Y, long Z) position)
|
|
||||||
{
|
|
||||||
lock (_lock) GetOrCreate(handle).V24 = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Op 0x229: reset the embedded range transform, select [first, first+count), and set its
|
|
||||||
/// anchor/pivot. This does not create or mutate an ordinary retained object.</summary>
|
|
||||||
public void SetRangeTransform(long first, long count, (long X, long Y, long Z) anchor)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
_rangeTransformFirst = first;
|
|
||||||
_rangeTransformCount = System.Math.Max(0, count);
|
|
||||||
_rangeTransform = new GfxObject { V18 = anchor };
|
|
||||||
MarkRetainedMutation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Op 0x22a: immediately replace the embedded range transform's current scale.</summary>
|
|
||||||
public void SetRangeScaleCurrent((long X, long Y, long Z) percent)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
_rangeTransform.ScaleCurrent = (percent.X / 100.0, percent.Y / 100.0, percent.Z / 100.0);
|
|
||||||
MarkRetainedMutation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Op 0x22c: immediately replace the embedded range transform's current translation.</summary>
|
|
||||||
public void SetRangeTranslationCurrent((long X, long Y, long Z) translation)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
_rangeTransform.TranslationCurrent = translation;
|
|
||||||
MarkRetainedMutation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Op 0x22d: arm the range transform's delayed one-shot scale target.</summary>
|
|
||||||
public void SetRangeScaleChannel(long delayMs, long durationMs, (long X, long Y, long Z) percent)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
_rangeTransform.ScaleDelayMs = delayMs;
|
|
||||||
_rangeTransform.ScaleDurationMs = durationMs;
|
|
||||||
_rangeTransform.ScaleTarget = (percent.X / 100.0, percent.Y / 100.0, percent.Z / 100.0);
|
|
||||||
_rangeTransform.ScaleEnabled = durationMs > 0;
|
|
||||||
_rangeTransform.OneShotStartMs = -1;
|
|
||||||
MarkRetainedMutation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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;
|
|
||||||
bool destinationIsNew = !_objects.ContainsKey(destinationHandle);
|
|
||||||
_objects[destinationHandle] = CloneState(s);
|
|
||||||
if (destinationIsNew) InsertOrderedHandle(destinationHandle);
|
|
||||||
CurrentObject = destinationHandle;
|
|
||||||
MarkRetainedMutation();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public GfxObject? TryGet(long handle) => _objects.TryGetValue(handle, out var o) ? o : null;
|
|
||||||
|
|
||||||
/// <summary>Op 0x215: look up <paramref name="handle"/> in the retained gfx-object map and return obj+4,
|
|
||||||
/// the live source-surface slot written by draw-texture, or -1 when absent/unbound.</summary>
|
|
||||||
public int QuerySlot(long handle)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
return _objects.TryGetValue(handle, out var o) ? o.SourceSlot : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Rebind one retained object from one source surface to another. Used by SC0000's bounded
|
|
||||||
/// movie site to reproduce the native warm-engine slot assignment before the following static loaders
|
|
||||||
/// reuse the port's cold-bootstrap slot.</summary>
|
|
||||||
public void RemapObjectSurface(long handle, int fromSlot, int toSlot)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
if (_objects.TryGetValue(handle, out var obj) && obj.SourceSlot == fromSlot)
|
|
||||||
{
|
|
||||||
obj.SourceSlot = toSlot;
|
|
||||||
MarkRetainedMutation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public long QueryField(long idx) => _fieldTable.TryGetValue(idx, out var v) ? v : 0;
|
|
||||||
|
|
||||||
public void Release(long handle)
|
|
||||||
{
|
|
||||||
lock (_lock) // re-entrant: EraseRange already holds _lock
|
|
||||||
{
|
|
||||||
if (_objects.Remove(handle))
|
|
||||||
{
|
|
||||||
int index = _orderedObjectHandles.BinarySearch(handle);
|
|
||||||
if (index >= 0) _orderedObjectHandles.RemoveAt(index);
|
|
||||||
MarkRetainedMutation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Op 0x1f7 semantics (native gfx_object_erase_range @0x47d8b0): erase handles in
|
|
||||||
/// [handle, handle+count) when count>1, else just <paramref name="handle"/>. It is a teardown/erase,
|
|
||||||
/// NOT a create — objects are created lazily by the geometry SET ops (gfx_object_get_or_create).</summary>
|
|
||||||
public void EraseRange(long handle, long count)
|
|
||||||
{
|
|
||||||
// Retained-object cleanup (native gfx_object_erase): removes the object from the map, so it stops
|
|
||||||
// compositing next frame. Faithful to the engine (the render loop iterates the retained-object map).
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
if (count > 1) for (long i = handle; i < handle + count; i++) Release(i);
|
|
||||||
else Release(handle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Op 0x1f6: clear every retained gfx-object record without releasing surface resources.</summary>
|
|
||||||
public void ClearRetainedObjects()
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
_objects.Clear();
|
|
||||||
_orderedObjectHandles.Clear();
|
|
||||||
CurrentObject = 0;
|
|
||||||
MarkRetainedMutation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Native scene_context_init_reset ownership boundary used by opcode 0x9: discard
|
/// <summary>Native scene_context_init_reset ownership boundary used by opcode 0x9: discard
|
||||||
/// retained objects, command/query state, surfaces, transitions, render-target selection, and the
|
/// retained objects, command/query state, surfaces, transitions, render-target selection, and the
|
||||||
@@ -384,34 +129,6 @@ public sealed partial class GfxState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GfxObject CloneState(GfxObject s)
|
|
||||||
=> new()
|
|
||||||
{
|
|
||||||
NativePersistenceRecord = s.NativePersistenceRecord?.ToArray(),
|
|
||||||
V18 = s.V18, V24 = s.V24, V16c = s.V16c,
|
|
||||||
Color = s.Color, HasColor = s.HasColor, StaticColorMode = s.StaticColorMode,
|
|
||||||
OneShotColorTarget = s.OneShotColorTarget, ColorDelayMs = s.ColorDelayMs,
|
|
||||||
ColorDurationMs = s.ColorDurationMs, OneShotColorEnabled = s.OneShotColorEnabled,
|
|
||||||
OneShotColorBlend = s.OneShotColorBlend, PostBindStaticColorBlend = s.PostBindStaticColorBlend,
|
|
||||||
SrcFrameCount = s.SrcFrameCount, SrcColumns = s.SrcColumns, 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,
|
|
||||||
OneShotAnimationControlFlags = s.OneShotAnimationControlFlags,
|
|
||||||
OneShotStartMs = s.OneShotStartMs,
|
|
||||||
RotationPeriodMs = s.RotationPeriodMs, RotationAxis = s.RotationAxis,
|
|
||||||
RotationEnabled = s.RotationEnabled, RotationStartMs = s.RotationStartMs,
|
|
||||||
ScaleCyclePeriodMs = s.ScaleCyclePeriodMs, ScaleCycleTarget = s.ScaleCycleTarget,
|
|
||||||
ScaleCycleEnabled = s.ScaleCycleEnabled, ScaleCycleStartMs = s.ScaleCycleStartMs,
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly object _lock = new();
|
private readonly object _lock = new();
|
||||||
|
|
||||||
@@ -801,87 +518,6 @@ public sealed partial class GfxState
|
|||||||
=> new(t.CommandKey, t.TargetSlot, t.RangeAStart, t.RangeACount, t.RangeBStart, t.RangeBCount,
|
=> new(t.CommandKey, t.TargetSlot, t.RangeAStart, t.RangeACount, t.RangeBStart, t.RangeBCount,
|
||||||
t.DelayMs, t.DurationMs, t.StartMs, TransitionProgress(t, nowMs), t.Forced);
|
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)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
var o = GetOrCreate(handle);
|
|
||||||
o.SourceSlot = slot; o.SrcRect = (sx, sy, w, h); o.V24 = (dstX, dstY, 0); o.Visible = true;
|
|
||||||
o.PostBindStaticColorBlend = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Op 0x13a: replace one of eleven safely isolated handler-addressable styles.
|
|
||||||
/// Native physically clears ten records; its admitted index 10 aliases return-stack storage.</summary>
|
|
||||||
public bool RegisterNumericGlyphStyle(int styleIndex, int surfaceSlot, int atlasX, int atlasY,
|
|
||||||
int digitWidth, int digitHeight)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
if ((uint)styleIndex >= (uint)_numericGlyphStyles.Length) return false;
|
|
||||||
_numericGlyphStyles[styleIndex] =
|
|
||||||
new NumericGlyphStyle(surfaceSlot, atlasX, atlasY, digitWidth, digitHeight);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Op 0x23b: erase a destination handle range, split a signed 32-bit value into decimal
|
|
||||||
/// digits, and bind one retained object per displayed atlas cell. Returns false for an invalid or
|
|
||||||
/// unregistered style, matching the native handler's script-error path.</summary>
|
|
||||||
public bool DrawDecimalGlyphs(long baseHandle, int styleIndex, int value, int x, int y,
|
|
||||||
int digitCapacity, int flags)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
if ((uint)styleIndex >= (uint)_numericGlyphStyles.Length
|
|
||||||
|| !_numericGlyphStyles[styleIndex].Registered) return false;
|
|
||||||
|
|
||||||
NumericGlyphStyle style = _numericGlyphStyles[styleIndex];
|
|
||||||
EraseRange(baseHandle, digitCapacity);
|
|
||||||
|
|
||||||
int remaining = value;
|
|
||||||
int handleOffset = 0;
|
|
||||||
int digitCount = 1;
|
|
||||||
for (int n = remaining / 10; n != 0; n /= 10) digitCount++;
|
|
||||||
|
|
||||||
for (int slot = digitCapacity - 1; slot >= 0; slot--)
|
|
||||||
{
|
|
||||||
int digit = remaining % 10;
|
|
||||||
int drawX;
|
|
||||||
bool draw;
|
|
||||||
|
|
||||||
if ((flags & 0x2) != 0)
|
|
||||||
{
|
|
||||||
draw = slot == digitCapacity - 1 || remaining != 0;
|
|
||||||
drawX = unchecked(x + style.DigitWidth * slot
|
|
||||||
- (digitCapacity - digitCount) * style.DigitWidth / 2);
|
|
||||||
}
|
|
||||||
else if ((flags & 0x4) != 0)
|
|
||||||
{
|
|
||||||
digitCount--;
|
|
||||||
draw = slot == digitCapacity - 1 || remaining != 0;
|
|
||||||
drawX = unchecked(x + style.DigitWidth * digitCount);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
draw = (flags & 0x1) != 0 || slot == digitCapacity - 1 || remaining != 0;
|
|
||||||
drawX = unchecked(x + style.DigitWidth * slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (draw)
|
|
||||||
{
|
|
||||||
int srcX = unchecked(style.AtlasX + digit * style.DigitWidth);
|
|
||||||
BindDraw(baseHandle + handleOffset, style.SurfaceSlot,
|
|
||||||
srcX, style.AtlasY, style.DigitWidth, style.DigitHeight, drawX, y);
|
|
||||||
handleOffset++;
|
|
||||||
}
|
|
||||||
remaining /= 10;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Op 0x1fd: immediately replace the object's current scale matrix. Script operands are
|
/// <summary>Op 0x1fd: immediately replace the object's current scale matrix. Script operands are
|
||||||
/// integer percentages; native divides them by 100 before matrix4_make_scale at obj+0x6c.</summary>
|
/// integer percentages; native divides them by 100 before matrix4_make_scale at obj+0x6c.</summary>
|
||||||
@@ -1335,11 +971,6 @@ public sealed partial class GfxState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InsertOrderedHandle(long handle)
|
|
||||||
{
|
|
||||||
int index = _orderedObjectHandles.BinarySearch(handle);
|
|
||||||
if (index < 0) _orderedObjectHandles.Insert(~index, handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static (long Packed, ColorTransitionState State) SampleOneShotColor(GfxObject o, long nowMs)
|
private static (long Packed, ColorTransitionState State) SampleOneShotColor(GfxObject o, long nowMs)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user