From e1294307cd353ecf63c5d36de12de5ac863452ad Mon Sep 17 00:00:00 2001 From: gamer147 Date: Tue, 21 Jul 2026 19:44:53 -0400 Subject: [PATCH] Document BTL movie playback blockers --- docs/asset-resolution-re.md | 39 ++++++++++++++++++++++++++++++++++++ docs/opcode-reference.md | 6 +++--- docs/phase-b-framework.md | 19 +++++++++++++----- docs/platform-portability.md | 8 +++++++- vm-map/opcodes.toml | 6 +++--- 5 files changed, 66 insertions(+), 12 deletions(-) diff --git a/docs/asset-resolution-re.md b/docs/asset-resolution-re.md index 8091517..1a1c795 100644 --- a/docs/asset-resolution-re.md +++ b/docs/asset-resolution-re.md @@ -326,6 +326,45 @@ unrendered. The real-scene trace remains a separate extracted-present test because the current `Paths.Scripts()` test bootstrap still locates its root `*.BIN` fixtures there; migrating that test/bootstrap path is unrelated to movie asset loading and was not folded into this slice. +### BTL combat-effect movie resolution and decoder boundary (diagnosed 2026-07-21) + +BTL's movie call at `BTL.BIN@0x2b21` is the same non-modal opcode `0x236`, but its table supplies universal +raw SYS4INI ids because `BTL.BIN` has no `SC####` manifest section. The first accepted combat run reached +five such ids: + +| Raw id | Catalog asset | MPEG size | Sequence size | +|---:|---|---:|---:| +| `0x2bd8` | `MVB958.AGF` | 40,964 bytes | 280x352 | +| `0x2af1` | `MVB001.AGF` | 90,116 bytes | 280x352 | +| `0x2bde` | `MVB955.AGF` | 22,532 bytes | 280x352 | +| `0x2af5` | `MVB004.AGF` | 69,636 bytes | 280x352 | +| `0x2bca` | `MVB914.AGF` | 133,124 bytes | 400x400 | + +All five archive payloads begin with MPEG program-stream pack code `00 00 01 BA`; none is a corrupt still +AGF. `GodotAdvHost.PlayMovieToSurface` currently calls only `ResourceMap.Resolve(BTL,id)`, which must return +null because the catalog builds local ranges only for `SC####.BIN`. Texture and voice resolution already +use the required ordering: active SC manifest first, then a type-checked universal raw fallback. Non-modal +movie resolution needs the same typed ordering. The logged `AGF decode failed MVB914.AGF` is downstream: +after movie lookup fails, no movie identity is registered, the VM still binds the raw id to the surface, +and the compositor falls through to the still-image decoder because MPEG movies also use `.AGF` names. + +Resolution is not the only blocker. An archive-backed probe of the actual Windows backend found that the +current DirectShow graph decodes `MVB914` and exposes a positive stop time, but rejects all four 280x352 +assets while connecting the MPEG video decoder with HRESULT `0x80040217`. Wider probes establish the +boundary in the current filter stack: 208/288/304/400/800-pixel widths decode, while 280/360/520/600-pixel +widths fail; the latter are all 8 mod 16. This is not a rare content edge: 125 installed `MVB*.AGF` files +are 280x352. Requesting RGB24 instead of RGB32 does not change negotiation. The evidence localizes the +failure to DirectShow/filter compatibility with these non-16-aligned MPEG display widths; it does not prove +which internal stock filter imposes the restriction. + +The correct implementation boundary therefore has three parts: add a typed scene-first/raw-fallback +resolver for non-modal movies; introduce the already-planned decoder interface/factory and a software MPEG +fallback that handles the installed non-16-aligned effects; and preserve the destination surface's created +dimensions instead of replacing every movie surface with the SC0000-specific 800x600 value. Decoder failure +should also leave a completed movie identity long enough to suppress bogus still-AGF fallback. Regressions +must cover at least raw BTL resolution, a 280x352 effect (`MVB001`), a 400x400 effect (`MVB914`), immediate +`0x23f` stop time, RGBA frame publication, completion, and release. + ### Modal startup/ending movie resources (implemented 2026-07-20) Opcode `0x20f` uses universal raw SYS4INI indexes rather than the executing script's manifest. Its complete diff --git a/docs/opcode-reference.md b/docs/opcode-reference.md index 17fcac2..7ae01de 100644 --- a/docs/opcode-reference.md +++ b/docs/opcode-reference.md @@ -745,11 +745,11 @@ Implemented through IHost.PlayModalMovieToSurface. ResourceMap.ResolveRawMovie d - **evidence:** Ghidra handler 0x423da0 converts axis ints to floats -> worker 0x47f060. gfx_object_anim_interpolate@0x473ed0 consumes obj+0x228/+0x214/+0x244 on retained-gfx owner+0xb550 (EngineCtx+0x51b64) and matrix4_make_axis_angle@0x48b215. gfx_object_composite@0x47f650 calls one-shot transform first, cyclic animation second. ### 0x236 `play-movie-to-surface` (play-movie-to-surface, argc 4) -- **summary:** (resource_id)(surface_slot)(movie_flags)(sync_mask) - synchronously resolve/open an archive movie and construct its DirectShow graph, then start asynchronous frame delivery into the retained destination surface. The opcode itself is non-blocking: the VM advances to the next instruction. SC0000's native site evaluates (0x33, 0, 2, 0) at 0x13c8 and resumes at bytecode 0x13d1; its later 0x21c service boundary yields until movie EOF before cleanup. +- **summary:** (resource_id)(surface_slot)(movie_flags)(sync_mask) - synchronously resolve/open an archive movie and construct its DirectShow graph, then start asynchronous frame delivery into the retained destination surface. Resource ids use the executing SC manifest when present and universal raw catalog ids in non-SC workers such as BTL. The opcode itself is non-blocking: the VM advances to the next instruction. SC0000's native site evaluates (0x33, 0, 2, 0) at 0x13c8 and resumes at bytecode 0x13d1; its later 0x21c service boundary yields until movie EOF before cleanup. - **grounding:** source=investigation, confidence=high -- **evidence:** Native handler 0x423ee0 and helpers 0x463c50/0x463aa0/0x463e20/0x4625e0; SC0000 native operand capture; exact 0x13c8->0x13d1 trace; archive-only changing-frame decoder and Godot lifecycle tests. +- **evidence:** Native handler 0x423ee0 and helpers 0x463c50/0x463aa0/0x463e20/0x4625e0; SC0000 native operand capture; exact 0x13c8->0x13d1 trace; archive-only changing-frame decoder and Godot lifecycle tests. BTL's live 0x2b21 site supplies table-selected values 0x2af1/0x2af5/0x2bca/0x2bd8/0x2bde; those exact raw SYS4INI slots are MPEG-backed MVB001/MVB004/MVB914/MVB958/MVB955 rather than BTL-local manifest offsets. -The handler requires an existing destination texture, allocates/reuses a 0x478-byte movie-to-texture object for the surface, opens operand 1 through the native indexed-asset reader, builds FilterGraph/IGraphBuilder/IMediaControl/IMediaPosition/IMediaEvent/IBasicAudio, and presents bottom-up RGB samples through the movie texture renderer. Operand 3 selects movie/sound routing policy: bits 0x10000/0x20000/0x40000/0x80000 force sound route 0/1/2/3, otherwise set:DependMovieSound is used; SC0000's low value 2 is retained as native movie mode state. Operand 4 is stored as the movie sync/device mask at object+0x42c; SC0000 passes 0. Static layer preparation after 0x236 does not terminate the retained movie; 0x21c services it through EOF and subsequent surface cleanup stops/detaches it. +The handler requires an existing destination texture, allocates/reuses a 0x478-byte movie-to-texture object for the surface, opens operand 1 through the native indexed-asset reader, builds FilterGraph/IGraphBuilder/IMediaControl/IMediaPosition/IMediaEvent/IBasicAudio, and presents bottom-up RGB samples through the movie texture renderer. The port must resolve operand 1 through the active SC manifest first and then the typed universal raw movie catalog, matching non-SC BTL's raw MVB ids without weakening still-image/audio type gates. Operand 3 selects movie/sound routing policy: bits 0x10000/0x20000/0x40000/0x80000 force sound route 0/1/2/3, otherwise set:DependMovieSound is used; SC0000's low value 2 is retained as native movie mode state. Operand 4 is stored as the movie sync/device mask at object+0x42c; SC0000 passes 0. Static layer preparation after 0x236 does not terminate the retained movie; 0x21c services it through EOF and subsequent surface cleanup stops/detaches it. ### 0x238 `set-anim-clock` (set-anim-clock, argc 1) - **summary:** (duration) — set the GLOBAL animation clock: native ctx+0x51b78=0 (elapsed), +0x51b7c=duration. The generic instruction length is 3 dwords. NON-BLOCKING: only configures; the render loop advances it and interpolates all animating objects. SC0000 opening @0x123bd/@0x13858. Handler 0x4240e0; Kelebek VA 0x422390 is drift. diff --git a/docs/phase-b-framework.md b/docs/phase-b-framework.md index bbd88cc..ec448cb 100644 --- a/docs/phase-b-framework.md +++ b/docs/phase-b-framework.md @@ -535,7 +535,7 @@ build, and threaded selftest pass. Manual DEBUGMAP acceptance now confirms that reachable-tile overlays, accepts movement, and reaches working player combat. The next bounded work is to investigate the concrete combat discrepancies found during that acceptance run. -### Player-attack runtime frontier implemented (2026-07-21; manual attack pending) +### Player-attack runtime frontier implemented; combat reached (2026-07-21) The selected attack-path opcode cluster is now implemented. `0x191` preserves native signed-32-bit absolute value behavior (including `INT_MIN`); `0xd0` and `0x23c` sample the shared monotonic/frame clock; `0x23a` @@ -558,10 +558,19 @@ the current exchange. Focused regressions cover exact dispatch, signed edge cases, string aliasing, clock rollover truncation, movie polling, animation reset suppression, delayed voice operands, paired clipping, overlap, and colorkey -transparency. The next gate is manual: execute one player attack from DEBUGMAP, confirm HP/damage and combat -voice presentation settle, and verify control returns to interactive FIELD. If that passes, proceed to an -enemy-turn/end-turn slice; if it fails, use the first concrete visual/control discrepancy rather than the -deferred profile write as the investigation entry point. +transparency. Manual DEBUGMAP acceptance reached player combat and exposed the next concrete frontier: +combat-effect movies do not play. The resolver/decoder diagnosis is canonical in +`docs/asset-resolution-re.md`; fix that bounded movie path before proceeding to enemy-turn/end-turn breadth. + +**Combat-effect movie gap diagnosed; implementation pending.** BTL's `0x236@0x2b21` consumes universal +raw MVB ids because BTL has no SC manifest, while the host currently permits only scene-local resolution. +That explains each `movie unresolved BTL:...` warning and the secondary attempt to decode MPEG-backed +`MVB914.AGF` as a still image. A raw-catalog decoder probe also found a separate backend wall: the current +DirectShow graph handles `MVB914` (400x400) but rejects the reached 280x352 MVB001/MVB004/MVB955/MVB958 +assets with `0x80040217`. Broader samples tie current compatibility to 16-aligned display widths, while 125 +installed MVB assets use 280x352. The next slice is therefore typed scene-first/raw movie resolution plus +the existing portable-decoder seam and a software fallback for these effects, with destination dimensions +and failed-movie identity preserved correctly. **Mutable-surface fill/blend regression corrected.** The first visual recheck exposed BUNKI's menu interior as transparent. SYSTEM4 creates 800x600 surface 3 and fills it opaque white through `0x20b`; the metadata-only diff --git a/docs/platform-portability.md b/docs/platform-portability.md index aa11186..9c9fa76 100644 --- a/docs/platform-portability.md +++ b/docs/platform-portability.md @@ -23,7 +23,7 @@ also softer Windows assumptions that should be tested or replaced before claimin | Area | Current dependency | Runtime impact | Portability status / future action | |---|---|---|---| -| AGE movie decode (`0x236` scene movies; `0x20f` modal LOGO/OP/ED) | `godot/DirectShowMovieDecoder.cs`: DirectShow COM objects plus `ole32.dll` `CoInitializeEx` / `CoUninitialize` | Movie playback cannot run outside Windows | Introduce a decoder interface and backend factory; retain DirectShow on Windows while adding a portable MPEG backend | +| AGE movie decode (`0x236` scene movies; `0x20f` modal LOGO/OP/ED) | `godot/DirectShowMovieDecoder.cs`: DirectShow COM objects plus `ole32.dll` `CoInitializeEx` / `CoUninitialize` | Movie playback cannot run outside Windows; the current Windows filter stack also rejects the installed MPEG effects whose display width is 8 mod 16 | Introduce a decoder interface and backend factory; use a software MPEG backend for portable coverage and as the Windows fallback for unsupported effect dimensions | | Movie integration | `godot/Main.cs` directly constructs and stores `DirectShowMovieDecoder`; `Main` is marked `SupportedOSPlatform("windows")` | The frontend has no runtime fallback or OS-specific source selection | Type `MovieRuntime` against the decoder interface, select by platform/build, and move the Windows annotation to the DirectShow backend | | Movie audio | DirectShow connects only the video pin to the sample grabber/null renderer | The MPEG audio stream is intentionally silent on every platform | Design a PCM/audio-clock contract or let a future backend own synchronized A/V; separate feature slice | | ADV font discovery | `godot/Main.cs` probes `C:/Windows/Fonts` for Japanese fonts | Harmless fallback today, but appearance depends on host fonts | Bundle/configure a redistributable font or add platform-specific discovery | @@ -67,6 +67,12 @@ interface. A future cleanup should: 6. Treat synchronized movie audio as a separate extension of the contract rather than coupling it to the compositor. +This replacement is now also required for Windows gameplay parity. Archive-backed probes on 2026-07-21 +show that the current DirectShow graph accepts tested MPEG widths divisible by 16 (208, 288, 304, 400, and +800) but fails `Connect` with `0x80040217` for tested widths that are 8 mod 16 (280, 360, 520, and 600). +The dominant combat-effect family is 280x352 (125 installed `MVB` assets), so retaining DirectShow as the +only Windows decoder is insufficient even though SC0000's 800x600 `CHAPTER.AGF` works. + An OS-specific build is also viable: include DirectShow only in Windows exports and a different decoder in other exports. The current plain `net8.0` project has no conditional backend selection. COM declarations may compile on another OS, but `[SupportedOSPlatform]` is analyzer metadata rather than a runtime guard, and diff --git a/vm-map/opcodes.toml b/vm-map/opcodes.toml index 57c47e5..8f4a7df 100644 --- a/vm-map/opcodes.toml +++ b/vm-map/opcodes.toml @@ -6181,13 +6181,13 @@ abi_source = "kelebek+decode-validated" [opcode.semantics] name = "play-movie-to-surface" category = "draw" -summary = "(resource_id)(surface_slot)(movie_flags)(sync_mask) - synchronously resolve/open an archive movie and construct its DirectShow graph, then start asynchronous frame delivery into the retained destination surface. The opcode itself is non-blocking: the VM advances to the next instruction. SC0000's native site evaluates (0x33, 0, 2, 0) at 0x13c8 and resumes at bytecode 0x13d1; its later 0x21c service boundary yields until movie EOF before cleanup." +summary = "(resource_id)(surface_slot)(movie_flags)(sync_mask) - synchronously resolve/open an archive movie and construct its DirectShow graph, then start asynchronous frame delivery into the retained destination surface. Resource ids use the executing SC manifest when present and universal raw catalog ids in non-SC workers such as BTL. The opcode itself is non-blocking: the VM advances to the next instruction. SC0000's native site evaluates (0x33, 0, 2, 0) at 0x13c8 and resumes at bytecode 0x13d1; its later 0x21c service boundary yields until movie EOF before cleanup." noop_headless = false source = "investigation" confidence = "high" depends_on = [] -evidence = "Native handler 0x423ee0 and helpers 0x463c50/0x463aa0/0x463e20/0x4625e0; SC0000 native operand capture; exact 0x13c8->0x13d1 trace; archive-only changing-frame decoder and Godot lifecycle tests." -details = "The handler requires an existing destination texture, allocates/reuses a 0x478-byte movie-to-texture object for the surface, opens operand 1 through the native indexed-asset reader, builds FilterGraph/IGraphBuilder/IMediaControl/IMediaPosition/IMediaEvent/IBasicAudio, and presents bottom-up RGB samples through the movie texture renderer. Operand 3 selects movie/sound routing policy: bits 0x10000/0x20000/0x40000/0x80000 force sound route 0/1/2/3, otherwise set:DependMovieSound is used; SC0000's low value 2 is retained as native movie mode state. Operand 4 is stored as the movie sync/device mask at object+0x42c; SC0000 passes 0. Static layer preparation after 0x236 does not terminate the retained movie; 0x21c services it through EOF and subsequent surface cleanup stops/detaches it." +evidence = "Native handler 0x423ee0 and helpers 0x463c50/0x463aa0/0x463e20/0x4625e0; SC0000 native operand capture; exact 0x13c8->0x13d1 trace; archive-only changing-frame decoder and Godot lifecycle tests. BTL's live 0x2b21 site supplies table-selected values 0x2af1/0x2af5/0x2bca/0x2bd8/0x2bde; those exact raw SYS4INI slots are MPEG-backed MVB001/MVB004/MVB914/MVB958/MVB955 rather than BTL-local manifest offsets." +details = "The handler requires an existing destination texture, allocates/reuses a 0x478-byte movie-to-texture object for the surface, opens operand 1 through the native indexed-asset reader, builds FilterGraph/IGraphBuilder/IMediaControl/IMediaPosition/IMediaEvent/IBasicAudio, and presents bottom-up RGB samples through the movie texture renderer. The port must resolve operand 1 through the active SC manifest first and then the typed universal raw movie catalog, matching non-SC BTL's raw MVB ids without weakening still-image/audio type gates. Operand 3 selects movie/sound routing policy: bits 0x10000/0x20000/0x40000/0x80000 force sound route 0/1/2/3, otherwise set:DependMovieSound is used; SC0000's low value 2 is retained as native movie mode state. Operand 4 is stored as the movie sync/device mask at object+0x42c; SC0000 passes 0. Static layer preparation after 0x236 does not terminate the retained movie; 0x21c services it through EOF and subsequent surface cleanup stops/detaches it." [[opcode.semantics.args]] i = 1