Prevent failed combat movies from blocking

This commit is contained in:
gamer147
2026-07-21 20:46:10 -04:00
parent 80b5105032
commit 633e3b3085
10 changed files with 84 additions and 37 deletions

View File

@@ -343,19 +343,28 @@ bytes) and `MVB238` (`0x2b94`, 143,364 bytes) both declare 280x352 and fail at
completion, and is stopped by script cleanup. This is decisive backend evidence rather than a resolver,
VFS, signature, or corrupt-asset problem.
The user nevertheless observed the combat presentation stall after that sequence. The existing failure
path calls `NotifyMovieCompleted` immediately, and the successful `movie stopped MVB908` line proves the
shared `0x21c` movie wait reached completion at least for the logged sequence. The log contains no VM offset
or transition-state record after cleanup, so it does not yet prove that DirectShow itself owns the final
stall. One remaining non-native input is concrete: failed graphs make `0x23f` return `-1`, which BTL stores
in its per-effect duration table at `0x2b31`; a real fallback decoder must instead supply duration and normal
completion. If the stall survives that backend correction, capture the VM/service boundary at the stall and
treat it as a separate BTL timed-presentation bug.
The user nevertheless observed the combat presentation stall after that sequence. Static BTL tracing rules
out `0x23f == -1` as an infinite-loop mechanism by itself. `local 0x44`, the total effect horizon, is first
set to `base_time + 1000` at `0x2230`; each effect can only extend that maximum with
`effect_start + duration` at `0x2b64..0x2b8b`. A failed duration of -1 can understate that one extension but
cannot make the callback count negative. After callbacks, `0x2492..0x2515` independently scans movie
surfaces 7..10 through `0x23a`, sleeping 16 ms only while one reports active. The successful
`movie stopped MVB908` line is emitted by the subsequent `0x2518` cleanup, proving that explicit movie wait
exited in the logged run. The final stall therefore was not localized; it may be after movie cleanup.
The port now nevertheless makes decoder failure safe and deterministic. A valid `0x236` movie whose host
backend cannot initialize is marked completed explicitly and receives stop time 0, meaning an immediate
effect, rather than the former diagnostic -1. Empty movie slots retain native `0x23f == -1`. Successfully
started decoders also carry a completion watchdog: the larger of five seconds or reported stop time plus
two seconds (capped at five minutes), with a 30-second default when timing is unavailable. Expiry forces the
same completed state so a backend that starts but never signals EOF cannot hold `0x21c` indefinitely. If the
reported combat stall survives this guard, capture the VM/service coordinate after `0x2518` and treat it as
a separate BTL timed-presentation bug.
The remaining implementation boundary is the already-planned decoder interface/factory plus a software
MPEG backend that handles the installed non-16-aligned effects, and preservation of the destination
surface's created dimensions instead of replacing every movie surface with the SC0000-specific 800x600
value. Failure must remain nonblocking and suppress bogus still-AGF fallback. Regressions must cover at
value. Failure is now nonblocking and suppresses bogus still-AGF fallback. Regressions must cover at
least a 280x352 effect (`MVB001`), a 400x400 effect (`MVB914`), immediate `0x23f` stop time, RGBA frame
publication, completion, failure completion, and release.

View File

@@ -1268,11 +1268,14 @@ initialization returns. It applies native seconds-to-milliseconds truncation and
`IHost.PlayMovieToSurface` into a movie-surface record owned by `GfxState`. Decoder construction moved from
the deferred Godot callback to the VM-side synchronous open boundary; the ready decoder is staged in a
thread-safe pending registry and adopted by the main thread before frame sampling, preserving asynchronous
presentation. `0x23f` silently returns -1 for an empty movie slot. If the movie record exists but DirectShow
returned an error, non-finite value, or value outside native signed-32-bit range, the port emits
`movie stop-time unavailable ...; returning -1` and returns -1. This is the chosen safe substitute for
native's ignored-HRESULT/uninitialized-output edge case. A normal Game Start through SC0000 was manually
validated without the warning, confirming that the installed movie's ordinary metadata path succeeds.
presentation. `0x23f` silently returns -1 for an empty movie slot. Native ignores the HRESULT for a non-null
movie's stop-time query and has no meaningful contract for the port-only case where the installed host
decoder cannot construct a graph for a shipped asset. The port therefore models backend failure as an
explicitly completed movie with stop time 0. This gives BTL an immediate-effect duration rather than feeding
its timeline -1. Started decoders also have a duration-based completion watchdog (minimum five seconds,
reported stop time plus two seconds, maximum five minutes; 30 seconds without timing) so missing EOF cannot
hold the shared `0x21c` wait indefinitely. A normal Game Start through SC0000 was manually validated with
ordinary positive timing.
### Grey-background root cause — slot collision + tint-strength (2026-07-08, gfx-log)

View File

@@ -786,11 +786,11 @@ First erase digit_capacity objects beginning at base_handle. Then split value by
For each fixed slot in [42,1000), the handler stops/releases the movie-to-texture object at ctx+0x52bd4[slot], then invokes the ordinary retained-gfx surface-release worker. Protected/externally owned slots may be retained by the worker's per-slot guard. This is the resource half of the common 0x1f6/0x23d full-reset sequence.
### 0x23f `query-surface-stop-time-ms` (u00422930, argc 2)
- **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. The port retains this metadata during 0x236 graph initialization; unavailable metadata emits a warning and also returns -1.
- **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.
- **grounding:** source=investigation, confidence=high
- **evidence:** Ghidra /v2: op_0x23f_query_surface_stop_time_ms@0x42a520 indexes EngineCtx surface array operand 2, returns -1 for a null slot, otherwise dereferences movie+0x414 IMediaPosition and calls vtable+0x28 get_StopTime. The adjacent op 0x23e uses the same interface at vtable+0x24 get_CurrentPosition; movie op 0x245 uses +0x20 put_CurrentPosition, independently confirming the documented vtable layout. The returned seconds are multiplied by g_dMillisecondsPerSecond@0x5713e8 (double 1000.0) and truncated by crt_ftol2_sse_truncate@0x550850 before vm_operand_write(1). All 23 Himegari sites in 17 scripts are associated with a preceding op 0x236 movie load to the queried surface. FIELD divides one result by 16 and adds 1 to build a 16 ms callback schedule; another path clamps the result to 600 ms before DRAWVOL.
The surface object's +0x414 member is IMediaPosition. Its vtable +0x28 entry is get_StopTime (after IUnknown, IDispatch, get_Duration, put_CurrentPosition, and get_CurrentPosition), returning a REFTIME double in seconds. Native multiplies by the double constant 1000.0 and calls the compiler float-to-integer helper, whose SSE2 and x87 paths both truncate toward zero. It does not inspect the getter HRESULT. For a valid graph the default stop time normally equals media duration, which explains duration-style consumers, but the exact ABI is stop position rather than get_Duration. The handler only queries state; it does not yield or alter playback. Port safety extension: a modeled movie surface whose decoder cannot supply a finite signed-32-bit stop time reports a warning and returns -1 instead of reproducing native's uninitialized-output failure path.
The surface object's +0x414 member is IMediaPosition. Its vtable +0x28 entry is get_StopTime (after IUnknown, IDispatch, get_Duration, put_CurrentPosition, and get_CurrentPosition), returning a REFTIME double in seconds. Native multiplies by the double constant 1000.0 and calls the compiler float-to-integer helper, whose SSE2 and x87 paths both truncate toward zero. It does not inspect the getter HRESULT. For a valid graph the default stop time normally equals media duration, which explains duration-style consumers, but the exact ABI is stop position rather than get_Duration. The handler only queries state; it does not yield or alter playback. Native has no meaningful answer for a port host that cannot build a graph for a valid shipped MPEG. Port safety extension: op 0x236 normalizes missing host metadata to stop time 0 and marks decoder failure completed, while a truly empty movie slot still returns -1.
### 0x242 `set-object-animation-detached` (set-object-animation-detached, argc 2)
- **summary:** Replace the retained object's animation-control word at obj+0x2d0. Bit 0 detaches finite one-shot channels from blocking presentation and protects them from 0x243 forced completion until they finish naturally.

View File

@@ -577,12 +577,14 @@ resolution; VM surface state and Godot caches retain the full selector. Focused
texture/voice ids and append-pack identity; 302 engine tests, a zero-warning Godot build, and threaded
selftest pass. Manual combat acceptance confirms the split: 400x400 MVB908 plays and completes, while
280x352 MVB961/MVB238 resolve correctly but DirectShow rejects their graph connection with `0x80040217`.
The user observed a stall after the sequence; failed movies are marked complete and the successful movie's
stop log proves the shared movie wait resumed, but failed `0x23f` duration remains `-1` and the final stalled
VM/service coordinate was not logged. **NEXT:** replace the decoder boundary with a software MPEG path that
returns real duration/frames for 280x352 effects and regress failure as nonblocking. If the stall remains,
capture it as a separate BTL timed-presentation issue. Preserve created destination dimensions and failed
movie identity through the backend change.
The user observed a stall after the sequence. Static BTL tracing proves failed `0x23f == -1` cannot create
an infinite callback count: the total horizon starts at `base_time + 1000`, and the explicit post-callback
loop polls `0x23a` until surfaces 7..10 are inactive. MVB908's stop log occurs after that loop. As a bounded
safety correction, decoder failure now becomes an explicitly completed zero-duration effect, while started
movies have a stop-time-based completion watchdog so a missing EOF cannot hold `0x21c` forever. **NEXT:**
manually recheck the same exchange. If it still stalls, capture the VM/service coordinate after BTL cleanup
as a separate timed-presentation issue; otherwise proceed to the software MPEG backend for real 280x352
duration/frames. Preserve created destination dimensions and failed-movie identity through that backend change.
**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