diff --git a/docs/engine-re.md b/docs/engine-re.md index f35c110..c76b54b 100644 --- a/docs/engine-re.md +++ b/docs/engine-re.md @@ -1050,6 +1050,29 @@ Matching evidence: `build/native-adv-text-trace.jsonl` and Godot timeline captur non-overlapping name/dialogue bands at y=447–468, 478–500, and 507–530. A manual run progressed 14 pages: 11 clicks completed active reveals and 14 later clicks released 14 distinct waits through `0xe0c`. +#### ADV wait indicator -- ops `0x73` / `0x72` (2026-07-11) + +The small bat marker is a configured ADV-layout sprite, not a glyph or part of SO001. `SYSTEM4.BIN` +loads universal raw asset `0x337c` (`SO000.AGF`) into surface slot 12. SO000 is a 390x27 strip of thirteen +30x27 bat frames. It then executes op `0x73` with +`(layout=1, x=385, y=140, surface=12, src=0,0, cell=30x27, terminalFrame=12, period=48ms)`. +Layout 1 begins at screen y=430, so the marker lands at `(385,570)`, matching the original screenshot. + +Native `op_0x73_configure_wait_indicator@0x41e900` writes this descriptor through +`adv_text_configure_wait_indicator@0x44ff60` to layout fields `+0x3c..+0x60`; operand 10 configures the +animation period through `adv_indicator_set_frame_period@0x44d060`. `op_0x72_handler@0x41e690` later calls +the layout renderer with frame `-1` and arms the input-wait flags, which makes the configured animation +visible only after show-text has naturally or forcibly completed. The completion click remains consumed; +the following click releases the wait. + +**Port implementation:** the single-scene harness now injects SYSTEM4's exact SO000 surface-12 and op-`0x73` +configuration alongside its existing SO001 shortcut. The VM forwards all ten operands through the host ABI; +`GodotAdvHost` retains configurations by layout and resolves slot 0 to the active Phase-A layout. A separate +Godot atlas overlay advances the inclusive frames `0..12` on the 48 ms host clock only while `IsWaiting`, +then hides immediately when input releases the wait. Keeping this 30x27 overlay outside the software +backbuffer preserves the static-wait compositor optimization. Full SYSTEM4 replay remains Phase-B work; +the shortcut carries the same state in the meantime. + ### SC0000 native SFX / BGM-fade family — `0xb4`/`0xb5`/`0xb6`/`0xc2`/`0xd9` (2026-07-11) The three SFX opcodes are a retained channel lifecycle, not immediate fire-and-forget calls. Handler diff --git a/docs/opcode-reference.md b/docs/opcode-reference.md index aa7382a..b85c71a 100644 --- a/docs/opcode-reference.md +++ b/docs/opcode-reference.md @@ -5,6 +5,16 @@ ## adv +### 0x72 `wait-for-input` (wait-for-input, argc 1) +- **summary:** (layout_slot) - arm the ADV input wait after text reveal completes; activates the wait indicator configured for the selected text layout by op 0x73. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0x72_handler@0x41e690 fetches operand 1 and calls FUN_00453120(text_manager, layout_slot, -1, &state), then sets the input-wait run-state flags. FUN_00453120 resolves layout slot 0 as current and consumes the indicator descriptor at layout+0x3c configured by op 0x73. SYSTEM4 layout 1 uses SO000's bat strip; the click that completes show-text is consumed before this opcode is reached. + +### 0x73 `configure-adv-wait-indicator` (configure-adv-wait-indicator, argc 10) +- **summary:** (layout_slot)(dst_x)(dst_y)(surface_slot)(src_x)(src_y)(cell_w)(cell_h)(terminal_frame)(frame_period_ms) - configure the animated marker shown while the selected ADV layout waits for input. +- **grounding:** source=investigation, confidence=high +- **evidence:** Ghidra /v2: op_0x73_configure_wait_indicator@0x41e900 passes operands 1..9 to adv_text_configure_wait_indicator@0x44ff60 and operand 10 to adv_indicator_set_frame_period@0x44d060. The worker writes surface/source rect, layout-relative destination, enabled=1, and terminal/column values to the selected layout at +0x3c..+0x60. SYSTEM4 executes `set-texture 0x337c 0xc 0xff00` (raw id 0x337c = SO000.AGF, a 390x27 strip of thirteen 30x27 bat frames), then `0x73 1 385 140 12 0 0 30 27 12 48`; layout 1 begins at y=430, placing the 30x27 marker at screen (385,570), matching the original. Op 0x72 activates this descriptor only after reveal completion. + ### 0x7a `set-adv-text-cursor` (set-adv-text-cursor, argc 3) - **summary:** (layout_slot)(x)(y) - set the cursor in the selected ADV text layout's last 20-byte record. Slot 0 selects the current layout. - **grounding:** source=investigation, confidence=high @@ -524,14 +534,6 @@ op 0x90 (u0041BEB0, argc 7): `0x90 x y w h tgt_a tgt_b tgt_c`. Kelebek left it " - **summary:** — - **grounding:** source=kelebek, confidence=low -### 0x72 `wait-for-input` (wait-for-input, argc 1) -- **summary:** — -- **grounding:** source=kelebek, confidence=med - -### 0x73 `u0041AB30` (u0041AB30, argc 10) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0x75 `u0041AC30` (u0041AC30, argc 1) - **summary:** — - **grounding:** source=kelebek, confidence=low diff --git a/docs/phase-a-slice-plan.md b/docs/phase-a-slice-plan.md index 6ffed03..a44ed1a 100644 --- a/docs/phase-a-slice-plan.md +++ b/docs/phase-a-slice-plan.md @@ -1384,6 +1384,18 @@ visible. This AE001D path is a retained scaled/rotating texture; the later `0x23 was separately resolved by preserving draw-texture's 200x200 cell and interpreting `(8,4)` as total frames/columns rather than a grid divisor. +### SC0000 ADV wait indicator implemented (2026-07-11) + +The missing post-reveal bat is `SO000.AGF`, a 390x27 strip of thirteen 30x27 frames. SYSTEM4 loads raw +asset `0x337c` into surface slot 12 and configures layout 1 through op `0x73` as a marker at layout-relative +`(385,140)`, screen `(385,570)`, with terminal frame 12 and a 48 ms period. Native op `0x72` activates that +layout descriptor after reveal completion. The Phase-A bootstrap now injects SO000 into surface 12 and +forwards the exact op-`0x73` configuration through `IHost`; `WaitForInput` carries the selected layout and +starts the marker clock. Godot presents it as a separate 30x27 atlas overlay, advancing inclusive frames +0..12 every 48 ms only during the stable wait and hiding on release. This avoids recompositing the 800x600 +software backbuffer during otherwise static waits. Validation: engine **147/147**, zero-warning Godot build, +threaded `SELFTEST OK`, and a real page-1 capture showing the transparent bat at `(385,570)`. + ### A2b -- SC0000 post-movie AE001H spritesheet correction ✅ DONE (2026-07-11) The reported missing cave spirit was the `0x231` path, independent of the earlier AE001D retained glow. diff --git a/engine/Age.Engine.Tests/AdvTextOpsTests.cs b/engine/Age.Engine.Tests/AdvTextOpsTests.cs index 470e718..27afdf3 100644 --- a/engine/Age.Engine.Tests/AdvTextOpsTests.cs +++ b/engine/Age.Engine.Tests/AdvTextOpsTests.cs @@ -30,4 +30,27 @@ public class AdvTextOpsTests Assert.Equal((1, 75, 47), Assert.Single(host.TextCursors)); Assert.Equal((13, 1, 1, "speaker"), Assert.Single(host.SurfaceStrings)); } + + [Fact] + public void WaitIndicatorConfigurationReachesHost() + { + var table = OpcodeTableJson.Load(Paths.OpcodesJson); + var script = ScriptAssembler.Assemble(table, "WAITMARK", + new List<(int, Operand[])> + { + (0x73, new[] + { + new Operand(0, 1), new Operand(0, 385), new Operand(0, 140), new Operand(0, 12), + new Operand(0, 0), new Operand(0, 0), new Operand(0, 30), new Operand(0, 27), + new Operand(0, 12), new Operand(0, 48), + }), + (0x2, Array.Empty()), + }, Array.Empty()); + var host = new RecordingHost(); + + new VirtualMachine(script, table, host).Run(); + + Assert.Equal(new Age.Engine.Hosting.AdvWaitIndicatorConfig(1, 385, 140, 12, 0, 0, 30, 27, 12, 48), + Assert.Single(host.WaitIndicators)); + } } diff --git a/engine/Age.Engine.Tests/TestSupport.cs b/engine/Age.Engine.Tests/TestSupport.cs index 676e22c..280725e 100644 --- a/engine/Age.Engine.Tests/TestSupport.cs +++ b/engine/Age.Engine.Tests/TestSupport.cs @@ -16,6 +16,7 @@ internal sealed class RecordingHost : IHost public readonly List<(int Offset, string Text)> Lines = new(); public readonly List<(int Slot, int X, int Y)> TextCursors = new(); public readonly List<(int Surface, int X, int Y, string Text)> SurfaceStrings = new(); + public readonly List WaitIndicators = new(); public readonly List SleptDurations = new(); public readonly List<(long Resource, int Channel)> SfxLoads = new(); public readonly List SfxStarts = new(); @@ -26,6 +27,7 @@ internal sealed class RecordingHost : IHost public void SetAdvTextCursor(int layoutSlot, int x, int y) => TextCursors.Add((layoutSlot, x, y)); public void DrawStringToSurface(int surfaceSlot, int x, int y, string text) => SurfaceStrings.Add((surfaceSlot, x, y, text)); + public void ConfigureAdvWaitIndicator(AdvWaitIndicatorConfig config) => WaitIndicators.Add(config); public void WaitForInput() => Waits++; public void Sleep(long duration) => SleptDurations.Add(duration); public void FrameYield() { } diff --git a/engine/Age.Engine/Hosting/IHost.cs b/engine/Age.Engine/Hosting/IHost.cs index 9833477..ac4ae95 100644 --- a/engine/Age.Engine/Hosting/IHost.cs +++ b/engine/Age.Engine/Hosting/IHost.cs @@ -1,6 +1,12 @@ using Age.Engine.Model; namespace Age.Engine.Hosting; + +public readonly record struct AdvWaitIndicatorConfig( + int LayoutSlot, int X, int Y, int SurfaceSlot, + int SourceX, int SourceY, int CellWidth, int CellHeight, + int TerminalFrame, long FramePeriodMs); + public interface IHost { void ShowText(int offset, string text); @@ -8,7 +14,9 @@ public interface IHost // op 0x204 rasterizes a string into a numbered surface before 0x1fb binds that surface. void SetAdvTextCursor(int layoutSlot, int x, int y) { } void DrawStringToSurface(int surfaceSlot, int x, int y, string text) { } + void ConfigureAdvWaitIndicator(AdvWaitIndicatorConfig config) { } void WaitForInput(); + void WaitForInput(int layoutSlot) => WaitForInput(); void Sleep(long duration); void FrameYield(); // Native 0x1c7/0x1cc query two distinct ADV skip channels. Headless and non-interactive diff --git a/engine/Age.Engine/Vm/VirtualMachine.cs b/engine/Age.Engine/Vm/VirtualMachine.cs index 8e3e154..3764d7f 100644 --- a/engine/Age.Engine/Vm/VirtualMachine.cs +++ b/engine/Age.Engine/Vm/VirtualMachine.cs @@ -283,13 +283,19 @@ public sealed class VirtualMachine return pc + 1; case "set-adv-text-cursor": // 0x7a (layout slot, x, y); slot 0 means current natively _host.SetAdvTextCursor((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2])); return pc + 1; + case "configure-adv-wait-indicator": // 0x73: per-layout animated input-wait marker + _host.ConfigureAdvWaitIndicator(new AdvWaitIndicatorConfig( + (int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2]), (int)Read(a[3]), + (int)Read(a[4]), (int)Read(a[5]), (int)Read(a[6]), (int)Read(a[7]), + (int)Read(a[8]), Read(a[9]))); + return pc + 1; case "draw-string": // 0x204 (surface slot, x, y, string) _host.DrawStringToSurface((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2]), ReadStr(a[3])); return pc + 1; case "wait-for-input": // Faithful headless: no player => halt here rather than plow past every prompt (see VmOptions). if (_o.HaltAtWaitForInput) { HaltReason ??= "wait-for-input"; return HALT; } - _host.WaitForInput(); return pc + 1; + _host.WaitForInput((int)Read(a[0])); return pc + 1; case "sleep": // 0xc8 (duration) — pause the host duration ms; headless hosts no-op (parity). Frame pacing. _host.Sleep(Read(a[0])); return pc + 1; case "get-message-skip": // 0x1c7: Ctrl/message fast-forward run-state bit diff --git a/godot/GodotAdvHost.cs b/godot/GodotAdvHost.cs index 707bacd..df24b45 100644 --- a/godot/GodotAdvHost.cs +++ b/godot/GodotAdvHost.cs @@ -11,6 +11,7 @@ public sealed class GodotAdvHost : IHost private readonly string _scene; // e.g. "SC0000" — for section_base private readonly object _imageLock = new(); private readonly Dictionary _images = new(); // raw catalog id -> decoded pixels + private readonly Dictionary _surfaceResources = new(); // surface slot -> scene/raw resource id private readonly Dictionary _movieFrames = new(); private readonly Dictionary _movieBySurface = new(); private readonly HashSet _completedMovies = new(); @@ -27,8 +28,12 @@ public sealed class GodotAdvHost : IHost private readonly Dictionary _surfaceText = new(); private string _advText = ""; private int _advTextX = 100, _advTextY = 47; + private int _currentAdvLayout = 1; // SYSTEM4's ordinary SC0000 ADV layout private long _advTextStartedMs; private bool _advTextForceComplete; + private readonly Dictionary _waitIndicators = new(); + private int _activeWaitLayout; + private long _waitIndicatorStartedMs; private GfxState? _foregroundGfx; public volatile bool IsWaiting; public volatile bool IsTransitionWaiting; @@ -75,7 +80,12 @@ public sealed class GodotAdvHost : IHost public void SetAdvTextCursor(int layoutSlot, int x, int y) { - lock (_textLock) { _advTextX = x; _advTextY = y; } + lock (_textLock) + { + if (layoutSlot != 0) _currentAdvLayout = layoutSlot; + _advTextX = x; + _advTextY = y; + } _timeline?.Event("text-cursor", new() { ["slot"] = layoutSlot, ["x"] = x, ["y"] = y }); } @@ -99,15 +109,50 @@ public sealed class GodotAdvHost : IHost public bool TryGetSurfaceText(int surfaceSlot, out SurfaceTextDraw draw) { lock (_textLock) return _surfaceText.TryGetValue(surfaceSlot, out draw); } + public void ConfigureAdvWaitIndicator(AdvWaitIndicatorConfig config) + { + lock (_textLock) _waitIndicators[config.LayoutSlot] = config; + _timeline?.Event("wait-indicator-config", new() + { + ["layout"] = config.LayoutSlot, ["x"] = config.X, ["y"] = config.Y, + ["surface"] = config.SurfaceSlot, ["cell_w"] = config.CellWidth, + ["cell_h"] = config.CellHeight, ["terminal_frame"] = config.TerminalFrame, + ["period_ms"] = config.FramePeriodMs, + }); + } + + public AdvWaitIndicatorSnapshot? SnapshotAdvWaitIndicator() + { + if (!IsWaiting) return null; + AdvWaitIndicatorConfig config; + long resourceId; + lock (_textLock) + { + if (!_waitIndicators.TryGetValue(_activeWaitLayout, out config)) return null; + if (!_surfaceResources.TryGetValue(config.SurfaceSlot, out resourceId)) return null; + } + var asset = _res.ResolveTexture(_scene, resourceId); + var image = asset != null ? Decode(asset) : null; + if (asset == null || image == null || config.CellWidth <= 0 || config.CellHeight <= 0) return null; + int frames = System.Math.Max(1, config.TerminalFrame + 1); + long period = System.Math.Max(1, config.FramePeriodMs); + int frame = (int)((_clock.NowMs - _waitIndicatorStartedMs) / period % frames); + return new AdvWaitIndicatorSnapshot(image, asset.Name, asset.RawIndex, config, frame); + } + public volatile int Pages; // VM-thread page counter (incremented before IsWaiting so shot-gating can't race) - public void WaitForInput() + public void WaitForInput() => WaitForInput(0); + + public void WaitForInput(int layoutSlot) { Pages++; _main.CallDeferred("PageBreak"); // Publish retained mutations accumulated before the wait once. A static input wait is not itself a // reason to rebuild the 800x600 background every frame; ambient channels are queried separately. System.Threading.Interlocked.Exchange(ref _presentRequested, 1); + lock (_textLock) _activeWaitLayout = layoutSlot == 0 ? _currentAdvLayout : layoutSlot; + _waitIndicatorStartedMs = _clock.NowMs; IsWaiting = true; _timeline?.State("input-wait", new() { ["page"] = Pages }); _gate.Wait(); @@ -244,13 +289,18 @@ public sealed class GodotAdvHost : IHost public void CreateTexture(int slot, int width, int height) { lock (_textLock) _surfaceText.Remove(slot); + lock (_textLock) _surfaceResources.Remove(slot); _slotDims[slot] = (width, height); if (TraceOps) Godot.GD.Print($"[op] create-texture slot={slot} {width}x{height}"); } public void SetTexture(long resourceId, int slot) { - lock (_textLock) _surfaceText.Remove(slot); + lock (_textLock) + { + _surfaceText.Remove(slot); + _surfaceResources[slot] = resourceId; + } var asset = _res.ResolveTexture(_scene, resourceId); var image = asset != null ? Decode(asset) : null; _slotDims[slot] = image != null ? (image.Width, image.Height) : (0, 0); @@ -428,3 +478,5 @@ public sealed class GodotAdvHost : IHost } public readonly record struct SurfaceTextDraw(int X, int Y, string Text); +public readonly record struct AdvWaitIndicatorSnapshot( + RgbaImage Image, string Name, int AssetId, AdvWaitIndicatorConfig Config, int Frame); diff --git a/godot/Main.cs b/godot/Main.cs index 0f3e578..a3cd142 100644 --- a/godot/Main.cs +++ b/godot/Main.cs @@ -17,6 +17,10 @@ public partial class Main : Godot.Control private TextureRect _screenView = null!; // shows the composited screen backbuffer private Image _screen = null!; // 800x600 immediate-mode canvas private ImageTexture _screenTex = null!; + private TextureRect _waitIndicator = null!; + private ImageTexture? _waitIndicatorSheet; + private AtlasTexture? _waitIndicatorAtlas; + private int _waitIndicatorAssetId = -1; // One managed composition target for the entire frame. Layer helpers mutate it in place; only the // completed frame crosses the Godot Image boundary, avoiding a full GetData/SetData round-trip per layer. private readonly byte[] _screenPixels = new byte[ScreenWidth * ScreenHeight * 4]; @@ -72,6 +76,17 @@ public partial class Main : Godot.Control _screenView.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect); AddChild(_screenView); // added first -> draws behind the text/status labels + // Native ADV wait marker: a tiny independently animated atlas region. Keeping it separate from the + // 800x600 software backbuffer avoids recompositing the entire retained scene throughout static waits. + _waitIndicator = new TextureRect + { + Visible = false, + ExpandMode = TextureRect.ExpandModeEnum.IgnoreSize, + StretchMode = TextureRect.StretchModeEnum.Keep, + MouseFilter = MouseFilterEnum.Ignore, + }; + AddChild(_waitIndicator); + _text = new Label { AutowrapMode = TextServer.AutowrapMode.WordSmart, MouseFilter = MouseFilterEnum.Ignore }; _text.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect); AddChild(_text); @@ -181,6 +196,16 @@ public partial class Main : Godot.Control _host.SetTexture(systemChrome.RawIndex, 0x11); _vm.Gfx.SetSurface(0x11, systemChrome.RawIndex, 0); } + // SYSTEM4 also loads SO000 and configures op 0x73 before entering scene code. The Phase-A + // single-scene harness does not replay those graphics side effects, so inject their exact state + // alongside the existing SO001 bootstrap until Phase B runs the complete SYSTEM4 entrypoint. + if (!_selftest && resources.ResolveName("SO000.AGF") is { } waitIndicator) + { + _host.SetTexture(waitIndicator.RawIndex, 0x0c); + _vm.Gfx.SetSurface(0x0c, waitIndicator.RawIndex, 0xff00); + _host.ConfigureAdvWaitIndicator(new AdvWaitIndicatorConfig( + 1, 385, 140, 0x0c, 0, 0, 30, 27, 12, 48)); + } // --boot: run SYSTEM4's state prefix (INITCONFIG/INIT2/INIT) so the scene sees boot state — chiefly // INIT2's gfx handle array 0x62455.. (skips the UI scripts LOGO/OP/TITLE). State carries via globals. if (boot && !_selftest) @@ -230,6 +255,7 @@ public partial class Main : Godot.Control if (!_selftest && _vm != null && _host != null && _host.ShouldRecomposite(_vm.Gfx)) Recomposite(); // native publishes retained mutations only at present/service boundaries if (!_selftest && _host != null) UpdateAdvTextPresentation(); + if (!_selftest && _host != null) UpdateAdvWaitIndicatorPresentation(); // --shot-sequence: dump one PNG per frame across the opening so a time-based (paced) effect can be // verified as distinct frames, not just the final state. Captures after Recomposite; quits when full. if (_seqDir != null && _seqIdx < _seqFrames && !_done) @@ -397,6 +423,33 @@ public partial class Main : Godot.Control _text.Text = count == 0 ? "" : t.Text[..count]; } + private void UpdateAdvWaitIndicatorPresentation() + { + var snapshot = _host.SnapshotAdvWaitIndicator(); + if (snapshot == null) + { + _waitIndicator.Visible = false; + return; + } + + var s = snapshot.Value; + if (_waitIndicatorAssetId != s.AssetId) + { + var image = Image.CreateFromData(s.Image.Width, s.Image.Height, false, Image.Format.Rgba8, s.Image.Pixels); + _waitIndicatorSheet = ImageTexture.CreateFromImage(image); + _waitIndicatorAtlas = new AtlasTexture { Atlas = _waitIndicatorSheet }; + _waitIndicator.Texture = _waitIndicatorAtlas; + _waitIndicatorAssetId = s.AssetId; + } + + var c = s.Config; + _waitIndicatorAtlas!.Region = new Rect2( + c.SourceX + s.Frame * c.CellWidth, c.SourceY, c.CellWidth, c.CellHeight); + _waitIndicator.Position = new Vector2(c.X, 430 + c.Y); + _waitIndicator.Size = new Vector2(c.CellWidth, c.CellHeight); + _waitIndicator.Visible = true; + } + private static string ColorTimeline(Age.Engine.Model.ColorTransitionState? state) => state is { } c ? $" color=0x{c.Current:x8}->0x{c.Target:x8} colorProgress={c.Progress:0.000}" diff --git a/vm-map/opcodes.toml b/vm-map/opcodes.toml index 9f1c00b..4282d10 100644 --- a/vm-map/opcodes.toml +++ b/vm-map/opcodes.toml @@ -1014,83 +1014,83 @@ abi_source = "kelebek+decode-validated" [opcode.semantics] name = "wait-for-input" -category = "unknown" -summary = "" +category = "adv" +summary = "(layout_slot) - arm the ADV input wait after text reveal completes; activates the wait indicator configured for the selected text layout by op 0x73." noop_headless = false -source = "kelebek" -confidence = "med" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0x72_handler@0x41e690 fetches operand 1 and calls FUN_00453120(text_manager, layout_slot, -1, &state), then sets the input-wait run-state flags. FUN_00453120 resolves layout slot 0 as current and consumes the indicator descriptor at layout+0x3c configured by op 0x73. SYSTEM4 layout 1 uses SO000's bat strip; the click that completes show-text is consumed before this opcode is reached." [[opcode.semantics.args]] i = 1 -role = "" +role = "layout slot (0 = current)" observed_types = ["imm"] [[opcode]] op = 0x73 -label = "u0041AB30" +label = "configure-adv-wait-indicator" argc = 10 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u0041AB30" -category = "unknown" -summary = "" +name = "configure-adv-wait-indicator" +category = "adv" +summary = "(layout_slot)(dst_x)(dst_y)(surface_slot)(src_x)(src_y)(cell_w)(cell_h)(terminal_frame)(frame_period_ms) - configure the animated marker shown while the selected ADV layout waits for input." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "Ghidra /v2: op_0x73_configure_wait_indicator@0x41e900 passes operands 1..9 to adv_text_configure_wait_indicator@0x44ff60 and operand 10 to adv_indicator_set_frame_period@0x44d060. The worker writes surface/source rect, layout-relative destination, enabled=1, and terminal/column values to the selected layout at +0x3c..+0x60. SYSTEM4 executes `set-texture 0x337c 0xc 0xff00` (raw id 0x337c = SO000.AGF, a 390x27 strip of thirteen 30x27 bat frames), then `0x73 1 385 140 12 0 0 30 27 12 48`; layout 1 begins at y=430, placing the 30x27 marker at screen (385,570), matching the original. Op 0x72 activates this descriptor only after reveal completion." [[opcode.semantics.args]] i = 1 -role = "" +role = "layout slot (0 = current)" observed_types = ["imm"] [[opcode.semantics.args]] i = 2 -role = "" +role = "destination x, layout-relative" observed_types = ["imm"] [[opcode.semantics.args]] i = 3 -role = "" +role = "destination y, layout-relative" observed_types = ["imm"] [[opcode.semantics.args]] i = 4 -role = "" +role = "source surface slot" observed_types = ["imm"] [[opcode.semantics.args]] i = 5 -role = "" +role = "source x" observed_types = ["imm"] [[opcode.semantics.args]] i = 6 -role = "" +role = "source y" observed_types = ["imm"] [[opcode.semantics.args]] i = 7 -role = "" +role = "cell width" observed_types = ["imm"] [[opcode.semantics.args]] i = 8 -role = "" +role = "cell height" observed_types = ["imm"] [[opcode.semantics.args]] i = 9 -role = "" +role = "terminal frame index / sheet columns" observed_types = ["imm"] [[opcode.semantics.args]] i = 10 -role = "" +role = "frame period (milliseconds)" observed_types = ["imm"] [[opcode]]