Match native surface persistence policy
This commit is contained in:
@@ -1750,9 +1750,9 @@ diagnostic/extended-mode surface.
|
|||||||
#### Layout-3 restore mechanics and port correspondence (2026-07-24)
|
#### Layout-3 restore mechanics and port correspondence (2026-07-24)
|
||||||
|
|
||||||
The port tracks the native global banks separately at runtime, captures the marked frame chain, serializes
|
The port tracks the native global banks separately at runtime, captures the marked frame chain, serializes
|
||||||
live surfaces and retained objects, and reloads host textures from the 20-byte surface records. Full load
|
live surfaces and retained objects, and applies the 20-byte surface records' explicit reload flags. Full
|
||||||
first replaces each serialized mutable bank prefix while preserving initialization-authored cells beyond
|
load first replaces each serialized mutable bank prefix while preserving initialization-authored cells
|
||||||
its count, restores history/gfx and retained audio, unwinds the obsolete managed call chain, runs
|
beyond its count, restores history/gfx and retained audio, unwinds the obsolete managed call chain, runs
|
||||||
`CALLBACK_LOAD.BIN` when
|
`CALLBACK_LOAD.BIN` when
|
||||||
the mounted script provider resolves it, starts the saved root at its `0xae` rendezvous, recursively
|
the mounted script provider resolves it, starts the saved root at its `0xae` rendezvous, recursively
|
||||||
reconstructs child frames, resumes parents after their saved T2 call sites,
|
reconstructs child frames, resumes parents after their saved T2 call sites,
|
||||||
@@ -1775,6 +1775,31 @@ tracks both lifecycles through `0xbf`/`0xc2` and `0xb4`/`0xb6`, serializes them,
|
|||||||
track plus saved SFX resources. The new EngineCtx fields and function annotations are applied to the
|
track plus saved SFX resources. The new EngineCtx fields and function annotations are applied to the
|
||||||
saved `/v2` image.
|
saved `/v2` image.
|
||||||
|
|
||||||
|
The surface records have a related preservation boundary. Resource id is at `+0x00`, packed color key at
|
||||||
|
`+0x04`, `+0x08` is a reload flag rather than a general presence bit, `+0x0c` remains unknown, and
|
||||||
|
`+0x10` marks a blank/mutable created surface. `gfx_surface_record_tables_init@0x472700` zeroes both
|
||||||
|
1,000-record tables and initializes every resource id to `-1`.
|
||||||
|
`gfx_surface_create_blank@0x477370` writes resource `-1` and created flag one;
|
||||||
|
`gfx_surface_load_asset@0x477c40` writes resource/color and clears the created flag. Neither operation
|
||||||
|
changes the reload flag. The layout-3 serializer copies all 20,000 live record bytes verbatim, rather
|
||||||
|
than deriving reloadability from the presence of an asset.
|
||||||
|
|
||||||
|
The restore loop calls `gfx_surface_load_asset` only when the saved reload flag is one and resource id is
|
||||||
|
nonnegative. Otherwise the already-initialized surface survives. The optional all-surface release is
|
||||||
|
separately gated by both `set:CreateObject` and `set:AutoFreeTex`; the registered defaults at
|
||||||
|
`engine_settings_register_defaults@0x46be30` are one and zero respectively, matching Himegari's active
|
||||||
|
path. Opcode `0x259` is also a real lifecycle operation:
|
||||||
|
`op_0x259_script_entry_clear_surface_persistence_flags@0x417660` clears record `+0x08` and `+0x0c`
|
||||||
|
across both native tables at every script entry.
|
||||||
|
Installed slot 15 records resource `0x3383` with reload flag zero. SYSTEM4 loads that atlas before the
|
||||||
|
save UI, and BUNKI cuts its reusable choice-box corners, borders, and winged top ornament from it. The
|
||||||
|
port formerly released all 1,000 host surfaces unconditionally, so post-load BUNKI retained its black
|
||||||
|
backing but lost the atlas decoration. Restoration now preserves live surfaces and overlays only flagged
|
||||||
|
reload records by default, while `VmOptions.CreateObject && VmOptions.AutoFreeTextures` reproduces the
|
||||||
|
native all-release branch. Port-authored saves emit the exact resource/reload/created fields, including
|
||||||
|
`-1` for unused records, and opcode `0x259` clears the modeled reload policy without releasing textures.
|
||||||
|
The corrected native functions are renamed/commented in the saved `/v2` image.
|
||||||
|
|
||||||
### Opcode `0xae` continues numbered-save stack restoration (2026-07-20)
|
### Opcode `0xae` continues numbered-save stack restoration (2026-07-20)
|
||||||
|
|
||||||
Opcode `0xae` is the load-side rendezvous paired with serialized script-frame state. Its handler,
|
Opcode `0xae` is the load-side rendezvous paired with serialized script-frame state. Its handler,
|
||||||
|
|||||||
@@ -924,6 +924,11 @@ The setter get-or-creates the object and writes the complete operand. During ret
|
|||||||
- **grounding:** source=investigation, confidence=high
|
- **grounding:** source=investigation, confidence=high
|
||||||
- **evidence:** Ghidra /v2: op_0x24e_handler@0x425070 writes operand 1 directly to EngineCtx.gfx_animation_service_flags at +0x51b80. The mapped field is also read by op 0x243: bit 1 suppresses its force-complete/clock-reset request.
|
- **evidence:** Ghidra /v2: op_0x24e_handler@0x425070 writes operand 1 directly to EngineCtx.gfx_animation_service_flags at +0x51b80. The mapped field is also read by op 0x243: bit 1 suppresses its force-complete/clock-reset request.
|
||||||
|
|
||||||
|
### 0x259 `script-entry` (script-entry, argc 0)
|
||||||
|
- **summary:** zero-arg script/prologue entry; clears surface-record persistence fields +0x08 and +0x0c across both 1,000-record native tables before the declaration chain continues
|
||||||
|
- **grounding:** source=investigation, confidence=high
|
||||||
|
- **evidence:** Ghidra /v2: op_0x259_script_entry_clear_surface_persistence_flags@0x417660 loops over both 20,000-byte surface-record tables (1,000 records x 20 bytes), writing zero to record +0x08 and +0x0c. SC0000 offset 0x0 and every observed script entry begins with this opcode; 0x258 continues the declaration chain. Port clears its modeled +0x08 reload policy; +0x0c remains opaque/unmodeled.
|
||||||
|
|
||||||
## input
|
## input
|
||||||
|
|
||||||
### 0x86 `set-cursor-resource` (u0041B210, argc 1)
|
### 0x86 `set-cursor-resource` (u0041B210, argc 1)
|
||||||
@@ -1149,11 +1154,6 @@ Port status (2026-07-24): implemented through the same profile-lifetime setting
|
|||||||
- **summary:** 2 imm; runs in a chain right after script-entry 0x259, enumerating ids — prologue declaration/registration?
|
- **summary:** 2 imm; runs in a chain right after script-entry 0x259, enumerating ids — prologue declaration/registration?
|
||||||
- **grounding:** source=harness, confidence=low, noop_headless=True
|
- **grounding:** source=harness, confidence=low, noop_headless=True
|
||||||
|
|
||||||
### 0x259 `script-entry` (u00416410, argc 0)
|
|
||||||
- **summary:** zero-arg; the first instruction of a script (offset 0), opens the decl chain that 0x258 continues — script/prologue entry marker, structural
|
|
||||||
- **grounding:** source=harness, confidence=low, noop_headless=True
|
|
||||||
- **evidence:** SC0000 offset 0x0 = op 0x259 (argc 0); 0x258's summary names it 'script-entry 0x259'; VM treats it as no-op (default stub) across all 279 CLEAN A0 scenes
|
|
||||||
|
|
||||||
## unknown
|
## unknown
|
||||||
|
|
||||||
### 0x2 `exit` (exit, argc 0)
|
### 0x2 `exit` (exit, argc 0)
|
||||||
|
|||||||
@@ -3597,6 +3597,37 @@ then confirmed that CHMENU, unit management, SELSTAGE, and FORT audio populate c
|
|||||||
visible load-specific discrepancy is the reusable choice-box frame losing its top winged flourish after
|
visible load-specific discrepancy is the reusable choice-box frame losing its top winged flourish after
|
||||||
restoration; that is the next independent investigation slice.
|
restoration; that is the next independent investigation slice.
|
||||||
|
|
||||||
|
### Persistence implementation step 9 — preserve flag-zero system surfaces (2026-07-24)
|
||||||
|
|
||||||
|
The post-load choice-box discrepancy came from the graphics half of the same ownership rule. BUNKI builds
|
||||||
|
the common modal frame from SYSTEM4's texture slot 15: resource `0x3383` supplies its corners, borders,
|
||||||
|
selection bar, and winged top ornament. The installed layout-3 record retains that resource id but stores
|
||||||
|
zero at record `+0x08`.
|
||||||
|
|
||||||
|
Native RE confirms `+0x08` is a reload flag, not general surface presence.
|
||||||
|
`save_data_deserialize_and_begin_restore@0x40fd10` calls `gfx_surface_load_asset` only for flag-one
|
||||||
|
records. Flag-zero system surfaces remain live unless a separate all-1,000 release path is enabled by
|
||||||
|
both `set:CreateObject` and `set:AutoFreeTex`. The port instead released every host surface and cleared
|
||||||
|
the complete managed surface map before applying only flag-one records. That removed slot 15, leaving
|
||||||
|
BUNKI's black backing but none of its atlas decoration.
|
||||||
|
|
||||||
|
Full load now preserves the current surface registry, overlays explicitly reloadable saved records, and
|
||||||
|
still replaces retained objects and the range transform. Regressions retain an initialized flag-zero
|
||||||
|
slot across a synthetic full load, prove that it is not redundantly reopened, and pin the installed
|
||||||
|
slot-15 resource/reload-flag oracle. The `/v2` deserializer annotation records the corrected ownership
|
||||||
|
boundary and the program is saved. Manual slot-000 acceptance confirms the stage-entry and later BUNKI
|
||||||
|
choices retain the complete winged frame.
|
||||||
|
|
||||||
|
The follow-up fidelity pass closes the two deferred policy gaps. `VmOptions` now carries the native
|
||||||
|
`CreateObject`/`AutoFreeTex` settings with Himegari's registered defaults (`true`/`false`), and enables
|
||||||
|
the all-1,000 host/model release only when both are true. Surface snapshots carry a named per-slot reload
|
||||||
|
bit; ordinary texture loads no longer manufacture it. Native encoding initializes unused resource ids to
|
||||||
|
`-1`, preserves resource/color/reload/created fields, and decoding distinguishes stored records from the
|
||||||
|
subset that must actually be reopened. Opcode `0x259`, formerly treated as a structural marker, is now
|
||||||
|
mapped and implemented as the script-entry clear of record `+0x08`/`+0x0c` (the port models `+0x08`).
|
||||||
|
Regressions cover native-default preservation, opt-in all-release, flag-for-flag port-authored output,
|
||||||
|
script-entry clearing, and the installed all-zero reload-bit oracle.
|
||||||
|
|
||||||
## Data-semantics sidebar: focused append EBINIT inspection (2026-07-24)
|
## Data-semantics sidebar: focused append EBINIT inspection (2026-07-24)
|
||||||
|
|
||||||
The static INIT surface now accepts a universal packed script id for focused append inspection.
|
The static INIT surface now accepts a universal packed script id for focused append inspection.
|
||||||
|
|||||||
@@ -477,7 +477,10 @@ the `0xae` rendezvous. The real `SAVE.BIN` script is covered end to end for list
|
|||||||
read-only installed-save gate now continues through `CALLBACK_LOAD`, reconstructs
|
read-only installed-save gate now continues through `CALLBACK_LOAD`, reconstructs
|
||||||
`SYSTEM4.BIN → FORT.BIN`, and reaches FORT's `CHMENU` gameplay poll. Full restoration replaces only the
|
`SYSTEM4.BIN → FORT.BIN`, and reaches FORT's `CHMENU` gameplay poll. Full restoration replaces only the
|
||||||
serialized mutable bank prefixes (preserving initialized unit/stage/string definitions), restores the
|
serialized mutable bank prefixes (preserving initialized unit/stage/string definitions), restores the
|
||||||
retained BGM/SFX state, and rebuilds graphics/history/frame state. JSON inspection/export,
|
retained BGM/SFX state, preserves initialized flag-zero system surfaces while overlaying explicit saved
|
||||||
|
reload records, reproduces the native configuration-gated all-surface release when explicitly enabled,
|
||||||
|
and writes native per-slot reload/created metadata rather than treating every texture as reloadable.
|
||||||
|
Opcode `0x259` supplies its real script-entry reload-policy clear. JSON inspection/export,
|
||||||
namespaced mod data, and migrations remain additive extended-mode work rather than 1.0 compatibility
|
namespaced mod data, and migrations remain additive extended-mode work rather than 1.0 compatibility
|
||||||
requirements.
|
requirements.
|
||||||
|
|
||||||
|
|||||||
@@ -252,11 +252,27 @@ Retained graphics then uses:
|
|||||||
... native allocation slack
|
... native allocation slack
|
||||||
```
|
```
|
||||||
|
|
||||||
The 1,000 surface records preserve resource reload state; mapped fields include resource id at `+0x00`,
|
The 1,000 surface records preserve the native 20-byte metadata cells verbatim:
|
||||||
packed color key at `+0x04`, and presence at `+0x08`. A retained `0x2d4` record is structurally complete
|
|
||||||
but not every internal graphics field is semantically named. The native allocation is larger than the
|
| Offset | Size | Meaning |
|
||||||
records actually written (`0x2e1 + object_count * 0x2d8` DWORDs in the graphics sizing term), leaving
|
|---:|---:|---|
|
||||||
zero/slack bytes after the meaningful range record.
|
| `+0x00` | 4 | signed resource id; `-1` means no reloadable asset |
|
||||||
|
| `+0x04` | 4 | packed color key |
|
||||||
|
| `+0x08` | 4 | reload-on-restore flag |
|
||||||
|
| `+0x0c` | 4 | unknown lifecycle field; cleared with `+0x08` at script entry |
|
||||||
|
| `+0x10` | 4 | blank/mutable created-surface flag |
|
||||||
|
|
||||||
|
Initialization writes resource id `-1` to every otherwise-zero record. Ordinary asset load, blank-surface
|
||||||
|
creation, and release do not synthesize or clear the reload flag; opcode `0x259` clears `+0x08` and
|
||||||
|
`+0x0c` across both live/saved tables at script entry. During normal layout-3 restoration AGE reopens only
|
||||||
|
records whose reload flag is one and whose resource id is nonnegative. Flag-zero records describe
|
||||||
|
surfaces expected to remain live across the load. The installed file is decisive: all 1,000 reload flags
|
||||||
|
are zero, while SYSTEM4's reusable choice-frame atlas remains recorded as resource `0x3383` in slot 15.
|
||||||
|
A separate configuration-gated path can release all 1,000 surfaces before this loop, but Himegari's
|
||||||
|
registered `CreateObject=1`, `AutoFreeTex=0` defaults leave it inactive. A retained `0x2d4`
|
||||||
|
record is structurally complete but not every internal graphics field is semantically named. The native
|
||||||
|
allocation is larger than the records actually written (`0x2e1 + object_count * 0x2d8` DWORDs in the
|
||||||
|
graphics sizing term), leaving zero/slack bytes after the meaningful range record.
|
||||||
|
|
||||||
The installed `SAVE00.DAT` validates the complete layout-3 decode: cutoff 1, global-bank counts
|
The installed `SAVE00.DAT` validates the complete layout-3 decode: cutoff 1, global-bank counts
|
||||||
`[402459,1,789,1,1,1]`, current BGM id `0x18`, retained SFX ids `0x3321` (channel 1) and
|
`[402459,1,789,1,1,1]`, current BGM id `0x18`, retained SFX ids `0x3321` (channel 1) and
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Age.Engine.Model;
|
using Age.Engine.Model;
|
||||||
using Age.Engine.Persistence;
|
using Age.Engine.Persistence;
|
||||||
|
using System.Buffers.Binary;
|
||||||
|
|
||||||
public class NativeNumberedSaveCodecTests
|
public class NativeNumberedSaveCodecTests
|
||||||
{
|
{
|
||||||
@@ -142,5 +143,14 @@ public class NativeNumberedSaveCodecTests
|
|||||||
Assert.Equal(402459, state.IntegerGlobals.Count);
|
Assert.Equal(402459, state.IntegerGlobals.Count);
|
||||||
Assert.Equal(789, state.StringGlobals.Count);
|
Assert.Equal(789, state.StringGlobals.Count);
|
||||||
Assert.Equal(NativeNumberedSaveState.GfxRecordSize, state.RangeTransformRecord.Length);
|
Assert.Equal(NativeNumberedSaveState.GfxRecordSize, state.RangeTransformRecord.Length);
|
||||||
|
ReadOnlySpan<byte> systemChoiceAtlas = state.SurfaceRecords.AsSpan(15 * 20, 20);
|
||||||
|
Assert.Equal(0x3383, BinaryPrimitives.ReadInt32LittleEndian(systemChoiceAtlas));
|
||||||
|
Assert.Equal(0, BinaryPrimitives.ReadInt32LittleEndian(systemChoiceAtlas[8..]));
|
||||||
|
Assert.All(
|
||||||
|
Enumerable.Range(0, 1000),
|
||||||
|
slot => Assert.Equal(
|
||||||
|
0,
|
||||||
|
BinaryPrimitives.ReadInt32LittleEndian(
|
||||||
|
state.SurfaceRecords.AsSpan(slot * 20 + 8))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,12 @@ public class NumberedSaveVmTests
|
|||||||
vm.GlobalFloats[0] = BitConverter.SingleToInt32Bits(2.5f);
|
vm.GlobalFloats[0] = BitConverter.SingleToInt32Bits(2.5f);
|
||||||
vm.GlobalStrings[4] = "姫狩り";
|
vm.GlobalStrings[4] = "姫狩り";
|
||||||
vm.Gfx.SetSurface(3, 0x1234, 0xff00ff);
|
vm.Gfx.SetSurface(3, 0x1234, 0xff00ff);
|
||||||
|
vm.Gfx.SetSurface(5, 0x5678, 0);
|
||||||
|
vm.Gfx.SetSurfaceReloadOnRestore(5, true);
|
||||||
|
vm.Gfx.CreateSurface(4);
|
||||||
|
vm.Gfx.SetSurface(7, 0x7777, 0);
|
||||||
|
vm.Gfx.SetSurfaceReloadOnRestore(7, true);
|
||||||
|
vm.Gfx.ReleaseSurfaceRange(7, 1);
|
||||||
vm.Gfx.BindDraw(100, 3, 1, 2, 30, 40, 50, 60);
|
vm.Gfx.BindDraw(100, 3, 1, 2, 30, 40, 50, 60);
|
||||||
|
|
||||||
vm.Run();
|
vm.Run();
|
||||||
@@ -53,6 +59,15 @@ public class NumberedSaveVmTests
|
|||||||
Assert.Equal("姫狩り", state.StringGlobals[4]);
|
Assert.Equal("姫狩り", state.StringGlobals[4]);
|
||||||
Assert.Equal(0x77u, state.Frames.Single().ScriptId);
|
Assert.Equal(0x77u, state.Frames.Single().ScriptId);
|
||||||
Assert.Contains(state.GfxObjects, item => item.Handle == 100);
|
Assert.Contains(state.GfxObjects, item => item.Handle == 100);
|
||||||
|
Assert.Equal(0x1234, ReadSurfaceField(state, 3, 0));
|
||||||
|
Assert.Equal(0, ReadSurfaceField(state, 3, 8));
|
||||||
|
Assert.Equal(-1, ReadSurfaceField(state, 4, 0));
|
||||||
|
Assert.Equal(1, ReadSurfaceField(state, 4, 0x10));
|
||||||
|
Assert.Equal(0x5678, ReadSurfaceField(state, 5, 0));
|
||||||
|
Assert.Equal(1, ReadSurfaceField(state, 5, 8));
|
||||||
|
Assert.Equal(-1, ReadSurfaceField(state, 6, 0));
|
||||||
|
Assert.Equal(-1, ReadSurfaceField(state, 7, 0));
|
||||||
|
Assert.Equal(1, ReadSurfaceField(state, 7, 8));
|
||||||
var restoredHistory = new AdvTextHistory();
|
var restoredHistory = new AdvTextHistory();
|
||||||
NativeTextHistoryCodec.DecodeInto(file.HistoryTail, restoredHistory);
|
NativeTextHistoryCodec.DecodeInto(file.HistoryTail, restoredHistory);
|
||||||
Assert.Equal("保存", restoredHistory.Records.Single().Text);
|
Assert.Equal("保存", restoredHistory.Records.Single().Text);
|
||||||
@@ -144,6 +159,7 @@ public class NumberedSaveVmTests
|
|||||||
vm.Globals[0x124] = 777;
|
vm.Globals[0x124] = 777;
|
||||||
vm.GlobalStrings[0] = "stale";
|
vm.GlobalStrings[0] = "stale";
|
||||||
vm.GlobalStrings[1] = "static-unit-name";
|
vm.GlobalStrings[1] = "static-unit-name";
|
||||||
|
vm.Gfx.SetSurface(15, 0x3383, 0);
|
||||||
|
|
||||||
vm.Run();
|
vm.Run();
|
||||||
|
|
||||||
@@ -163,6 +179,12 @@ public class NumberedSaveVmTests
|
|||||||
Assert.Equal(Enumerable.Range(0, 10), host.SfxReleases);
|
Assert.Equal(Enumerable.Range(0, 10), host.SfxReleases);
|
||||||
Assert.Equal("履歴復帰", liveHistory.Records.Single().Text);
|
Assert.Equal("履歴復帰", liveHistory.Records.Single().Text);
|
||||||
Assert.Equal(3, vm.Gfx.QuerySlot(100));
|
Assert.Equal(3, vm.Gfx.QuerySlot(100));
|
||||||
|
Assert.Contains(
|
||||||
|
vm.Gfx.CapturePersistenceSnapshot().Surfaces,
|
||||||
|
item => item.Slot == 15 && item.ResourceId == 0x3383);
|
||||||
|
Assert.Contains(host.Textures, item => item == (0x1234, 3));
|
||||||
|
Assert.DoesNotContain(host.Textures, item => item.Slot == 15);
|
||||||
|
Assert.Empty(host.ReleasedSurfaceRanges);
|
||||||
RenderObject restoredObject = Assert.Single(vm.Gfx.SnapshotVisibleObjects());
|
RenderObject restoredObject = Assert.Single(vm.Gfx.SnapshotVisibleObjects());
|
||||||
Assert.Equal(0x1234, restoredObject.SurfaceResId);
|
Assert.Equal(0x1234, restoredObject.SurfaceResId);
|
||||||
Assert.Equal((50, 60), (restoredObject.DstX, restoredObject.DstY));
|
Assert.Equal((50, 60), (restoredObject.DstX, restoredObject.DstY));
|
||||||
@@ -182,6 +204,74 @@ public class NumberedSaveVmTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DataOnlyLoadReleasesAllSurfacesWhenBothNativeSettingsEnableIt()
|
||||||
|
{
|
||||||
|
string root = NewTemporaryDirectory();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var store = new DirectoryNativeDatStore(root, Identity);
|
||||||
|
Script loader = ScriptAssembler.Assemble(Table, "LOAD_DATA_ONLY.BIN",
|
||||||
|
[
|
||||||
|
(0x19f, [new Operand(LocalInt, 0), new Operand(Immediate, 1)]),
|
||||||
|
(0x2, Array.Empty<Operand>()),
|
||||||
|
], []);
|
||||||
|
NativeNumberedSaveState state = NativeNumberedSaveCodec.Empty(
|
||||||
|
[new NativeSavedScriptFrame(-1, 0, [], -1, -1)]) with
|
||||||
|
{
|
||||||
|
SurfaceRecords = NativeSurfaceRecords(3, 0x1234, 0xff00ff),
|
||||||
|
};
|
||||||
|
store.SaveNumberedFile(
|
||||||
|
1, NativeNumberedSaveCodec.Encode(state), [],
|
||||||
|
NativeSystemTime.FromLocalDateTime(DateTime.Now), 0);
|
||||||
|
var host = new RecordingHost();
|
||||||
|
var vm = new VirtualMachine(
|
||||||
|
loader, Table, host,
|
||||||
|
new VmOptions(CreateObject: true, AutoFreeTextures: true),
|
||||||
|
nativeDatStore: store);
|
||||||
|
vm.Gfx.SetSurface(15, 0x3383, 0);
|
||||||
|
|
||||||
|
vm.Run();
|
||||||
|
|
||||||
|
Assert.Contains((0, 1000), host.ReleasedSurfaceRanges);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
vm.Gfx.CapturePersistenceSnapshot().Surfaces,
|
||||||
|
item => item.Slot == 15);
|
||||||
|
Assert.Contains(
|
||||||
|
vm.Gfx.CapturePersistenceSnapshot().Surfaces,
|
||||||
|
item => item.Slot == 3
|
||||||
|
&& item.ResourceId == 0x1234
|
||||||
|
&& item.ReloadOnRestore);
|
||||||
|
Assert.Contains(host.Textures, item => item == (0x1234, 3));
|
||||||
|
Assert.Equal("exit", vm.HaltReason);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Directory.Delete(root, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ScriptEntryClearsReloadPolicyWithoutReleasingTheSurface()
|
||||||
|
{
|
||||||
|
Script script = ScriptAssembler.Assemble(Table, "ENTRY.BIN",
|
||||||
|
[
|
||||||
|
(0x259, Array.Empty<Operand>()),
|
||||||
|
(0x2, Array.Empty<Operand>()),
|
||||||
|
], []);
|
||||||
|
var vm = new VirtualMachine(script, Table, new RecordingHost());
|
||||||
|
vm.Gfx.SetSurface(15, 0x3383, 0);
|
||||||
|
vm.Gfx.SetSurfaceReloadOnRestore(15, true);
|
||||||
|
|
||||||
|
vm.Run();
|
||||||
|
|
||||||
|
GfxSurfacePersistenceState surface = Assert.Single(
|
||||||
|
vm.Gfx.CapturePersistenceSnapshot().Surfaces);
|
||||||
|
Assert.Equal(15, surface.Slot);
|
||||||
|
Assert.Equal(0x3383, surface.ResourceId);
|
||||||
|
Assert.False(surface.ReloadOnRestore);
|
||||||
|
}
|
||||||
|
|
||||||
private static Script WithPackedId(Script source, uint packedId)
|
private static Script WithPackedId(Script source, uint packedId)
|
||||||
=> new()
|
=> new()
|
||||||
{
|
{
|
||||||
@@ -225,6 +315,8 @@ public class NumberedSaveVmTests
|
|||||||
private static byte[] NativeSurfaceRecords(int slot, int resourceId, int colorKey)
|
private static byte[] NativeSurfaceRecords(int slot, int resourceId, int colorKey)
|
||||||
{
|
{
|
||||||
byte[] result = new byte[NativeNumberedSaveState.SurfaceRecordsSize];
|
byte[] result = new byte[NativeNumberedSaveState.SurfaceRecordsSize];
|
||||||
|
for (int index = 0; index < 1000; index++)
|
||||||
|
BinaryPrimitives.WriteInt32LittleEndian(result.AsSpan(index * 20), -1);
|
||||||
int at = slot * 20;
|
int at = slot * 20;
|
||||||
BinaryPrimitives.WriteInt32LittleEndian(result.AsSpan(at), resourceId);
|
BinaryPrimitives.WriteInt32LittleEndian(result.AsSpan(at), resourceId);
|
||||||
BinaryPrimitives.WriteInt32LittleEndian(result.AsSpan(at + 4), unchecked((int)(0xff000000u | (uint)colorKey)));
|
BinaryPrimitives.WriteInt32LittleEndian(result.AsSpan(at + 4), unchecked((int)(0xff000000u | (uint)colorKey)));
|
||||||
@@ -232,6 +324,10 @@ public class NumberedSaveVmTests
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int ReadSurfaceField(NativeNumberedSaveState state, int slot, int offset)
|
||||||
|
=> BinaryPrimitives.ReadInt32LittleEndian(
|
||||||
|
state.SurfaceRecords.AsSpan(slot * 20 + offset));
|
||||||
|
|
||||||
private static byte[] NativeGfxRecord(int sourceSlot)
|
private static byte[] NativeGfxRecord(int sourceSlot)
|
||||||
{
|
{
|
||||||
byte[] result = new byte[NativeNumberedSaveState.GfxRecordSize];
|
byte[] result = new byte[NativeNumberedSaveState.GfxRecordSize];
|
||||||
|
|||||||
@@ -56,8 +56,11 @@ public sealed record GfxDiagnosticSnapshot(
|
|||||||
BlockingGfxObjectDiagnostic? BlockingRangeTransform,
|
BlockingGfxObjectDiagnostic? BlockingRangeTransform,
|
||||||
IReadOnlyList<BlockingGfxObjectDiagnostic> BlockingObjects);
|
IReadOnlyList<BlockingGfxObjectDiagnostic> BlockingObjects);
|
||||||
|
|
||||||
|
public readonly record struct GfxSurfacePersistenceState(
|
||||||
|
int Slot, long ResourceId, long ColorKey, bool Created, bool ReloadOnRestore);
|
||||||
|
|
||||||
public sealed record GfxPersistenceSnapshot(
|
public sealed record GfxPersistenceSnapshot(
|
||||||
IReadOnlyList<(int Slot, long ResourceId, long ColorKey, bool Created)> Surfaces,
|
IReadOnlyList<GfxSurfacePersistenceState> Surfaces,
|
||||||
IReadOnlyList<(long Handle, GfxState.GfxObject Object)> Objects,
|
IReadOnlyList<(long Handle, GfxState.GfxObject Object)> Objects,
|
||||||
long RangeFirst,
|
long RangeFirst,
|
||||||
long RangeCount,
|
long RangeCount,
|
||||||
@@ -383,6 +386,7 @@ public sealed class GfxState
|
|||||||
_fieldTable.Clear();
|
_fieldTable.Clear();
|
||||||
_surfaces.Clear();
|
_surfaces.Clear();
|
||||||
_createdSurfaces.Clear();
|
_createdSurfaces.Clear();
|
||||||
|
_reloadableSurfaces.Clear();
|
||||||
_movieStopTimesMs.Clear();
|
_movieStopTimesMs.Clear();
|
||||||
_surfaceTransitions.Clear();
|
_surfaceTransitions.Clear();
|
||||||
CurrentObject = 0;
|
CurrentObject = 0;
|
||||||
@@ -405,11 +409,18 @@ public sealed class GfxState
|
|||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
var surfaces = _surfaces
|
var surfaces = _surfaces.Keys
|
||||||
.Select(pair => (
|
.Concat(_reloadableSurfaces)
|
||||||
pair.Key, pair.Value.ResId, pair.Value.ColorKey,
|
.Distinct()
|
||||||
_createdSurfaces.Contains(pair.Key)))
|
.Select(slot =>
|
||||||
.OrderBy(item => item.Key)
|
{
|
||||||
|
bool active = _surfaces.TryGetValue(slot, out var value);
|
||||||
|
return new GfxSurfacePersistenceState(
|
||||||
|
slot, active ? value.ResId : -1, active ? value.ColorKey : 0,
|
||||||
|
active && _createdSurfaces.Contains(slot),
|
||||||
|
_reloadableSurfaces.Contains(slot));
|
||||||
|
})
|
||||||
|
.OrderBy(item => item.Slot)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
var objects = _orderedObjectHandles
|
var objects = _orderedObjectHandles
|
||||||
.Select(handle => (handle, CloneState(_objects[handle])))
|
.Select(handle => (handle, CloneState(_objects[handle])))
|
||||||
@@ -427,10 +438,13 @@ public sealed class GfxState
|
|||||||
{
|
{
|
||||||
_surfaces.Clear();
|
_surfaces.Clear();
|
||||||
_createdSurfaces.Clear();
|
_createdSurfaces.Clear();
|
||||||
foreach (var (slot, resourceId, colorKey, created) in snapshot.Surfaces)
|
_reloadableSurfaces.Clear();
|
||||||
|
foreach (GfxSurfacePersistenceState surface in snapshot.Surfaces)
|
||||||
{
|
{
|
||||||
_surfaces[slot] = (resourceId, colorKey);
|
if (surface.ResourceId >= 0 || surface.Created)
|
||||||
if (created) _createdSurfaces.Add(slot);
|
_surfaces[surface.Slot] = (surface.ResourceId, surface.ColorKey);
|
||||||
|
if (surface.Created) _createdSurfaces.Add(surface.Slot);
|
||||||
|
if (surface.ReloadOnRestore) _reloadableSurfaces.Add(surface.Slot);
|
||||||
}
|
}
|
||||||
_objects.Clear();
|
_objects.Clear();
|
||||||
_orderedObjectHandles.Clear();
|
_orderedObjectHandles.Clear();
|
||||||
@@ -481,6 +495,9 @@ public sealed class GfxState
|
|||||||
// Created surfaces have real pixels but no asset resource id. Keep their class separate from both
|
// 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.
|
// loaded textures and truly surfaceless objects because native mode-0 consumes packed alpha differently.
|
||||||
private readonly HashSet<int> _createdSurfaces = new();
|
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
|
// 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.
|
// 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, long?> _movieStopTimesMs = new();
|
||||||
@@ -496,6 +513,24 @@ public sealed class GfxState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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
|
/// <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>
|
/// deliberately distinguishes a movie surface with unavailable metadata from an empty movie slot.</summary>
|
||||||
public void SetMovieStopTime(int slot, long? stopTimeMs)
|
public void SetMovieStopTime(int slot, long? stopTimeMs)
|
||||||
|
|||||||
@@ -17,13 +17,17 @@ internal static class NativeGfxPersistenceCodec
|
|||||||
{
|
{
|
||||||
GfxPersistenceSnapshot snapshot = gfx.CapturePersistenceSnapshot();
|
GfxPersistenceSnapshot snapshot = gfx.CapturePersistenceSnapshot();
|
||||||
byte[] surfaces = new byte[NativeNumberedSaveState.SurfaceRecordsSize];
|
byte[] surfaces = new byte[NativeNumberedSaveState.SurfaceRecordsSize];
|
||||||
foreach (var (slot, resourceId, colorKey, created) in snapshot.Surfaces)
|
for (int slot = 0; slot < SurfaceCount; slot++)
|
||||||
|
WriteInt(surfaces, slot * SurfaceRecordSize, -1);
|
||||||
|
foreach (GfxSurfacePersistenceState surface in snapshot.Surfaces)
|
||||||
{
|
{
|
||||||
if ((uint)slot >= SurfaceCount || created || resourceId < 0) continue;
|
if ((uint)surface.Slot >= SurfaceCount) continue;
|
||||||
int at = slot * SurfaceRecordSize;
|
int at = surface.Slot * SurfaceRecordSize;
|
||||||
WriteInt(surfaces, at, unchecked((int)resourceId));
|
WriteInt(surfaces, at,
|
||||||
WriteInt(surfaces, at + 4, PackNativeColorKey(colorKey));
|
surface.Created ? -1 : unchecked((int)surface.ResourceId));
|
||||||
WriteInt(surfaces, at + 8, 1);
|
WriteInt(surfaces, at + 4, PackNativeColorKey(surface.ColorKey));
|
||||||
|
WriteInt(surfaces, at + 8, surface.ReloadOnRestore ? 1 : 0);
|
||||||
|
WriteInt(surfaces, at + 0x10, surface.Created ? 1 : 0);
|
||||||
}
|
}
|
||||||
NativeSavedGfxObject[] objects = snapshot.Objects
|
NativeSavedGfxObject[] objects = snapshot.Objects
|
||||||
.Select(item => new NativeSavedGfxObject(item.Handle, EncodeObject(item.Object)))
|
.Select(item => new NativeSavedGfxObject(item.Handle, EncodeObject(item.Object)))
|
||||||
@@ -35,15 +39,20 @@ internal static class NativeGfxPersistenceCodec
|
|||||||
|
|
||||||
public static GfxPersistenceSnapshot Decode(NativeNumberedSaveState state)
|
public static GfxPersistenceSnapshot Decode(NativeNumberedSaveState state)
|
||||||
{
|
{
|
||||||
var surfaces = new List<(int Slot, long ResourceId, long ColorKey, bool Created)>();
|
var surfaces = new List<GfxSurfacePersistenceState>();
|
||||||
for (int slot = 0; slot < SurfaceCount; slot++)
|
for (int slot = 0; slot < SurfaceCount; slot++)
|
||||||
{
|
{
|
||||||
int at = slot * SurfaceRecordSize;
|
int at = slot * SurfaceRecordSize;
|
||||||
int resourceId = ReadInt(state.SurfaceRecords, at);
|
int resourceId = ReadInt(state.SurfaceRecords, at);
|
||||||
int present = ReadInt(state.SurfaceRecords, at + 8);
|
bool reload = ReadInt(state.SurfaceRecords, at + 8) == 1;
|
||||||
if (present == 1 && resourceId >= 0)
|
bool created = ReadInt(state.SurfaceRecords, at + 0x10) == 1;
|
||||||
surfaces.Add((slot, unchecked((uint)resourceId),
|
if (resourceId >= 0 || reload || created)
|
||||||
UnpackNativeColorKey(ReadInt(state.SurfaceRecords, at + 4)), false));
|
surfaces.Add(new GfxSurfacePersistenceState(
|
||||||
|
slot,
|
||||||
|
resourceId < 0 ? resourceId : unchecked((uint)resourceId),
|
||||||
|
UnpackNativeColorKey(ReadInt(state.SurfaceRecords, at + 4)),
|
||||||
|
created,
|
||||||
|
reload));
|
||||||
}
|
}
|
||||||
var objects = state.GfxObjects
|
var objects = state.GfxObjects
|
||||||
.Select(item => (item.Handle, DecodeObject(item.Record)))
|
.Select(item => (item.Handle, DecodeObject(item.Record)))
|
||||||
|
|||||||
@@ -177,12 +177,20 @@ public static class NativeNumberedSaveCodec
|
|||||||
=> new(
|
=> new(
|
||||||
0, 0, new int[NativeNumberedSaveState.SoundEffectChannelCount],
|
0, 0, new int[NativeNumberedSaveState.SoundEffectChannelCount],
|
||||||
new byte[NativeNumberedSaveState.ResourceRecordsSize],
|
new byte[NativeNumberedSaveState.ResourceRecordsSize],
|
||||||
new byte[NativeNumberedSaveState.SurfaceRecordsSize],
|
EmptySurfaceRecords(),
|
||||||
frames, Array.Empty<int>(), Array.Empty<int>(), Array.Empty<string>(),
|
frames, Array.Empty<int>(), Array.Empty<int>(), Array.Empty<string>(),
|
||||||
Array.Empty<int>(), Array.Empty<int>(), Array.Empty<int>(),
|
Array.Empty<int>(), Array.Empty<int>(), Array.Empty<int>(),
|
||||||
Array.Empty<NativeSavedGfxObject>(), 0, 0,
|
Array.Empty<NativeSavedGfxObject>(), 0, 0,
|
||||||
new byte[NativeNumberedSaveState.GfxRecordSize]);
|
new byte[NativeNumberedSaveState.GfxRecordSize]);
|
||||||
|
|
||||||
|
private static byte[] EmptySurfaceRecords()
|
||||||
|
{
|
||||||
|
byte[] result = new byte[NativeNumberedSaveState.SurfaceRecordsSize];
|
||||||
|
for (int slot = 0; slot < 1000; slot++)
|
||||||
|
WriteInt(result, slot * 20, -1);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private static void WriteFrame(Span<byte> payload, int offset, NativeSavedScriptFrame frame, bool terminal)
|
private static void WriteFrame(Span<byte> payload, int offset, NativeSavedScriptFrame frame, bool terminal)
|
||||||
{
|
{
|
||||||
if (frame.ReturnIndices.Count > FrameReturnCapacity)
|
if (frame.ReturnIndices.Count > FrameReturnCapacity)
|
||||||
|
|||||||
@@ -896,12 +896,24 @@ public sealed class VirtualMachine
|
|||||||
}
|
}
|
||||||
|
|
||||||
GfxPersistenceSnapshot gfxSnapshot = NativeGfxPersistenceCodec.Decode(state);
|
GfxPersistenceSnapshot gfxSnapshot = NativeGfxPersistenceCodec.Decode(state);
|
||||||
for (int slot = 0; slot < 1000; slot++) _host.ReleaseSurface(slot);
|
bool releaseAllSurfaces = _o.CreateObject && _o.AutoFreeTextures;
|
||||||
Gfx.RestorePersistenceSnapshot(gfxSnapshot);
|
if (releaseAllSurfaces)
|
||||||
foreach (var (slot, resourceId, colorKey, created) in gfxSnapshot.Surfaces)
|
|
||||||
{
|
{
|
||||||
if (!created) _host.SetTexture(resourceId, slot, colorKey);
|
Gfx.ReleaseSurfaceRange(0, 1000);
|
||||||
|
_host.ReleaseSurfaceRange(0, 1000);
|
||||||
}
|
}
|
||||||
|
var restoredSurfaces = Gfx.CapturePersistenceSnapshot().Surfaces
|
||||||
|
.ToDictionary(item => item.Slot);
|
||||||
|
foreach (GfxSurfacePersistenceState surface in gfxSnapshot.Surfaces
|
||||||
|
.Where(item => item.ReloadOnRestore && item.ResourceId >= 0))
|
||||||
|
restoredSurfaces[surface.Slot] = surface;
|
||||||
|
Gfx.RestorePersistenceSnapshot(gfxSnapshot with
|
||||||
|
{
|
||||||
|
Surfaces = restoredSurfaces.Values.OrderBy(item => item.Slot).ToArray(),
|
||||||
|
});
|
||||||
|
foreach (GfxSurfacePersistenceState surface in gfxSnapshot.Surfaces
|
||||||
|
.Where(item => item.ReloadOnRestore && item.ResourceId >= 0))
|
||||||
|
_host.SetTexture(surface.ResourceId, surface.Slot, surface.ColorKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ReplaceDensePrefix<T>(
|
private static void ReplaceDensePrefix<T>(
|
||||||
@@ -1025,6 +1037,8 @@ public sealed class VirtualMachine
|
|||||||
var a = ins.Args;
|
var a = ins.Args;
|
||||||
switch (_t.Label(op))
|
switch (_t.Label(op))
|
||||||
{
|
{
|
||||||
|
case "script-entry":
|
||||||
|
Gfx.ClearSurfaceReloadPolicies(); return pc + 1;
|
||||||
case "add": Write(a[0], Read(a[1]) + Read(a[2])); return pc + 1;
|
case "add": Write(a[0], Read(a[1]) + Read(a[2])); return pc + 1;
|
||||||
case "sub": Write(a[0], Read(a[1]) - Read(a[2])); return pc + 1;
|
case "sub": Write(a[0], Read(a[1]) - Read(a[2])); return pc + 1;
|
||||||
case "mul": Write(a[0], Read(a[1]) * Read(a[2])); return pc + 1;
|
case "mul": Write(a[0], Read(a[1]) * Read(a[2])); return pc + 1;
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ namespace Age.Engine.Vm;
|
|||||||
/// post-exit bytecode can be explored. Leave false for native-faithful execution.</param>
|
/// post-exit bytecode can be explored. Leave false for native-faithful execution.</param>
|
||||||
/// <param name="NativeStringCodePage">Encoding used when an opcode measures the engine's byte-string
|
/// <param name="NativeStringCodePage">Encoding used when an opcode measures the engine's byte-string
|
||||||
/// representation. SYS4 defaults to CP932; another container frontend can select its own code page.</param>
|
/// representation. SYS4 defaults to CP932; another container frontend can select its own code page.</param>
|
||||||
|
/// <param name="CreateObject">Native set:CreateObject profile setting. Together with
|
||||||
|
/// <paramref name="AutoFreeTextures"/>, controls the optional all-surface release before numbered load.</param>
|
||||||
|
/// <param name="AutoFreeTextures">Native set:AutoFreeTex profile setting. Himegari defaults this off,
|
||||||
|
/// so initialized system textures survive a numbered load unless an explicit reload record replaces them.</param>
|
||||||
public sealed record VmOptions(int EmitCap = 2, long MaxSteps = 2_000_000, int CallDepthCap = 64,
|
public sealed record VmOptions(int EmitCap = 2, long MaxSteps = 2_000_000, int CallDepthCap = 64,
|
||||||
bool HaltAtWaitForInput = false, bool IgnoreExitRequests = false,
|
bool HaltAtWaitForInput = false, bool IgnoreExitRequests = false,
|
||||||
int NativeStringCodePage = 932);
|
int NativeStringCodePage = 932, bool CreateObject = true,
|
||||||
|
bool AutoFreeTextures = false);
|
||||||
|
|||||||
@@ -77,6 +77,5 @@ INFERRED: dict[int, dict] = {
|
|||||||
0x23b: dict(name='draw-decimal-glyphs', category='draw', noop=False, confidence='high', source='investigation', summary='Draw an integer as decimal glyph objects from a style registered by opcode 0x13a.'),
|
0x23b: dict(name='draw-decimal-glyphs', category='draw', noop=False, confidence='high', source='investigation', summary='Draw an integer as decimal glyph objects from a style registered by opcode 0x13a.'),
|
||||||
0x23f: dict(name='query-surface-stop-time-ms', category='draw', noop=False, confidence='high', source='investigation', summary='(out_stop_time_ms)(surface_slot) — query the DirectShow stop position retained by a loaded movie surface, convert seconds to integer milliseconds by truncating toward zero, and write -1 when the movie slot is empty. Port-only host decoder failure is modeled as an explicitly completed, zero-duration movie.'),
|
0x23f: dict(name='query-surface-stop-time-ms', category='draw', noop=False, confidence='high', source='investigation', summary='(out_stop_time_ms)(surface_slot) — query the DirectShow stop position retained by a loaded movie surface, convert seconds to integer milliseconds by truncating toward zero, and write -1 when the movie slot is empty. Port-only host decoder failure is modeled as an explicitly completed, zero-duration movie.'),
|
||||||
0x258: dict(name='decl?', category='marker', noop=True, confidence='low', source='harness', summary='2 imm; runs in a chain right after script-entry 0x259, enumerating ids — prologue declaration/registration?'),
|
0x258: dict(name='decl?', category='marker', noop=True, confidence='low', source='harness', summary='2 imm; runs in a chain right after script-entry 0x259, enumerating ids — prologue declaration/registration?'),
|
||||||
0x259: dict(name='script-entry', category='marker', noop=True, confidence='low', source='harness', summary='zero-arg; the first instruction of a script (offset 0), opens the decl chain that 0x258 continues — script/prologue entry marker, structural'),
|
|
||||||
0x2c5: dict(name='byte-string-length', category='compute', noop=False, confidence='high', source='investigation', summary="Write the resolved NUL-terminated engine string's raw byte length."),
|
0x2c5: dict(name='byte-string-length', category='compute', noop=False, confidence='high', source='investigation', summary="Write the resolved NUL-terminated engine string's raw byte length."),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6735,19 +6735,19 @@ observed_types = ["imm"]
|
|||||||
|
|
||||||
[[opcode]]
|
[[opcode]]
|
||||||
op = 0x259
|
op = 0x259
|
||||||
label = "u00416410"
|
label = "script-entry"
|
||||||
argc = 0
|
argc = 0
|
||||||
abi_source = "kelebek+decode-validated"
|
abi_source = "kelebek+decode-validated"
|
||||||
|
|
||||||
[opcode.semantics]
|
[opcode.semantics]
|
||||||
name = "script-entry"
|
name = "script-entry"
|
||||||
category = "marker"
|
category = "draw"
|
||||||
summary = "zero-arg; the first instruction of a script (offset 0), opens the decl chain that 0x258 continues — script/prologue entry marker, structural"
|
summary = "zero-arg script/prologue entry; clears surface-record persistence fields +0x08 and +0x0c across both 1,000-record native tables before the declaration chain continues"
|
||||||
noop_headless = true
|
noop_headless = false
|
||||||
source = "harness"
|
source = "investigation"
|
||||||
confidence = "low"
|
confidence = "high"
|
||||||
depends_on = []
|
depends_on = []
|
||||||
evidence = "SC0000 offset 0x0 = op 0x259 (argc 0); 0x258's summary names it 'script-entry 0x259'; VM treats it as no-op (default stub) across all 279 CLEAN A0 scenes"
|
evidence = "Ghidra /v2: op_0x259_script_entry_clear_surface_persistence_flags@0x417660 loops over both 20,000-byte surface-record tables (1,000 records x 20 bytes), writing zero to record +0x08 and +0x0c. SC0000 offset 0x0 and every observed script entry begins with this opcode; 0x258 continues the declaration chain. Port clears its modeled +0x08 reload policy; +0x0c remains opaque/unmodeled."
|
||||||
|
|
||||||
[[opcode]]
|
[[opcode]]
|
||||||
op = 0x2bd
|
op = 0x2bd
|
||||||
|
|||||||
Reference in New Issue
Block a user