Conversation
Keep the authored simulation cadence and original presentation policy as defaults while adding explicitly selectable display, numeric, and uncapped presentation modes.\n\nAlso integrate presentation interpolation and lifecycle coverage, real-ROM oracle replay diagnostics, fail-closed magic-code validation, macOS signing/notarization repairs, native audio queue qualification, and clearer native/web repository navigation.\n\nNo ROM or ROM-derived game assets are included.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4d7d3bb6ac
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| gh release view "$RELEASE_TAG" >/dev/null | ||
| gh release upload "$RELEASE_TAG" \ | ||
| "$DMG_PATH" "${DMG_PATH}.sha256" "${DMG_PATH}.provenance.json" |
There was a problem hiding this comment.
Bind the uploaded release to the checked-out commit
When this workflow is dispatched from a ref other than the commit referenced by RELEASE_TAG, the provenance check only proves that the artifact was built from github.sha; it never verifies that the release tag points to that SHA. The gh release upload manual specifies gh release upload <tag> <files>..., so the subsequent command will attach the differently sourced, signed artifact to whatever existing tag the operator entered. Resolve and dereference the tag and require it to equal SOURCE_COMMIT before uploading, otherwise a release can distribute a binary built from the wrong commit.
Useful? React with 👍 / 👎.
…d-off card MINOR-4 (functional): the room-ready takeover latch was one-shot per adapter, so a SECOND tournament in one session fell back to the per-race ImGui path instead of the native takeover. Re-arm it edge-triggered + reason-aware: - runOnlineLobbyStartLiveSession captures the session end reason and arms the re-arm ONLY on a FINISHED return (never LEFT/ERROR/NONE -- those can land with the room-ready condition still TRUE, so an instant re-arm would re-boot the session the player just left). - OnlineRoom_observeRoomReadyRearm (called every drawBetaRoom frame) completes the re-arm: it clears the latch ONLY while the room-ready condition is FALSE (parked in RESULTS after FINISHED), so the next SELECTING+2+LOBBY+tournament arrival is a genuine false->true rising edge the poll re-fires on exactly once. Cannot re-boot-loop: reason-gating blocks LEFT/ERROR; condition-false gating blocks any instant re-fire. resetRoomReadyLatch clears the pending re-arm too, so the room-ready probe's exactly-1-fire contract is unchanged. Hand-off card (polish): for a TOURNAMENT room in the takeover window (SELECTING+2+LOBBY+tournament) the now-dead tournament-scoped ImGui widgets -- the cup chips + series line in the session card, the tournament ready-gating region, and the mid-cup Next-Race branch in RESULTS -- are replaced with a concise "Starting tournament -- handing to the game..." card. Single-race widgets (character grid, vehicle row, single-race track/ready/start), the MODE chip (tournament entry), the room-ready trigger poll, and the post-FINISHED RESULTS landing + New Tournament entry all survive untouched. Test: new headless lane check_online_room_ready_rearm.py drives the re-arm state machine via the MDKR_APP_TEST_ONLINE_ROOM_READY_REARM_PROBE seam and proves tournament #1 fires once, a LEFT/ERROR return does not re-arm (no loop), a FINISHED return does not instantly re-fire, and the fresh rising edge re-takes native once for tournament #2. All online lanes green; ctest 220/221 (only pre-existing network_viewport_invariance); OFF anchors byte-identical.
…INISHED
Add the native, decomp-authentic 2D champion celebration shown after the final
tournament standings and before the session hands back to the launcher. The
MDKR_ONLINE_SESSION_CEREMONY phase was a dead enum swallowed by default:; make it
real as a beta-only, ZERO-offline-impact screen.
- New online_ceremony.{c,h}: mirrors the online_results.c screen contract
(enter/tick/exit, borrowed portrait+font asset group, exit-frees-assets
symmetry, same include set, throttled stderr witness, headless test seams).
Renders a big gold CHAMPION headline, the winner's portrait + name ([YOU] if
local) + point total, the runners-up rows, and a pulsed "RETURNING TO ROOM..."
footer, using the SAME 2D primitives (draw_text/texrect_draw/bgdraw_fillcolour)
and the shared navy backdrop every online screen uses. Crowd-cheer SFX on enter.
It never enters the offline trophy-ceremony cinematic (which re-enters
GAMEMODE_MENU).
- Advance gate: a bounded TIMED auto-advance (~6s) that fires for EVERY endpoint
on the local clock -- no cross-endpoint/snapshot convergence gate -- so it is
impossible to hang the session on. A host may skip early; a joiner never blocks
anyone; a remote vacate ends it promptly. No second required "press A" (the
RESULTS terminal already took the one FINISH confirm).
- Wiring (online_session.c): the final-standings RESULTS LEAVE now detours into
the CEREMONY phase; the EXACT PD-T6d FINISHED note + platform_request_exit(0)
moved intact into the new CEREMONY case, so FINISHED still fires EXACTLY ONCE,
with the same reason/result the launcher reads, once the ceremony ends. The
non-final LEFT path is unchanged.
- DRY winner identity: lift the RESULTS selection-sort into online_standings.h
(mdkr_online_standings_compute), called by both online_results.c and the
ceremony, so the champion (order[0]) is byte-for-byte the seat STANDINGS crowns
#1. online_results.c output behavior is unchanged.
- Tests: new check_online_ceremony.py proves the ceremony enter/render sit
strictly between the final standings and the single FINISHED (skip), auto-
advances with no input (auto -- impossible-to-hang), and ends promptly on a
remote vacate (vacate); champion cross-checked against the standings #1. The
existing hold-ending lanes (lobby_tournament / session_end incl joiner /
resident_live / lobby_single_endpoint) set MDKR_TEST_ONLINE_CEREMONY_SKIP so
their frame budgets + FINISHED assertions are preserved with the ceremony in
the path.
Beta-only: online_ceremony.c is added to GAME_SOURCES only under the
MDKR_ENABLE_ONLINE_BETA gate; thread3_main.c / menu.c / online_race_results.c
untouched (OFF-build objects byte-identical). All online lanes green; ctest
220/221 (only pre-existing network_viewport_invariance).
…naments The existing re-arm probe proves ONE FINISHED cycle (#1 -> #2) plus a fresh- adapter reset coda; it cannot show the arm -> clear -> rising-edge cycle is repeatable on the SAME adapter, so a latent one-shot bug would pass #1 -> #2 yet silently drop the 3rd tournament (called out unexercised in the human plan). Add a beta/test-only MDKR_APP_TEST_ONLINE_ROOM_READY_REARM3_PROBE branch (main_app.cpp, inside the existing beta gate) that drives THREE consecutive tournaments through the wiring's real re-arm edges, plus a LEFT return wedged between #1 and #2 that must not re-arm. New lane check_online_rearm_third.py asserts totalFires=3, one takeover per tournament, no mid-run re-boot loop, and that "re-arm armed"/"re-arm complete" each log exactly twice in order (one re-arm per FINISHED return, not a one-shot). No offline TU touched; anchors unaffected.
…tate-hash convergence bar Two intertwined pieces on the resident descriptor-less coordinator: - FINAL-REPLAY (ruled): when the host picks a replay option at the tournament final (NEW TOURNAMENT / CHANGE CUP / CHANGE MODE / RACE AGAIN / CHANGE CHARACTER), the REMATCH wrap resets race_index 3 -> 0 -- which the coordinator's tournament re-cycle trigger (race_index ADVANCED) by definition never sees. The continuing host's coordinator parked in Results forever: roster/match-input never re-cycled, the next race never booted, and the per-round re-wait watchdog was the only exit. A new race_index-DECREASED arm now takes the OBSERVE-ONLY re-cycle (the single-race replay machinery): the engine's own re-selection screen owns the re-drive, the launcher clears the stale roster/match-input and re-arms on the engine-driven fresh epoch -- auto-driving the mid-cup advance would race the host's re-selection to START on the old config. New lane check_online_final_replay.py (registered, documented) proves the ruled two-road re-convergence on the loopback 2-endpoint rig: NEW TOURNAMENT at final #1 -> wrap -> observe-only re-cycle -> the second endpoint re-readies through the reducer -> race 5 boots on the fresh epoch as the fresh series' round 1 -> the full second cup races -> FINISH #2 ends cleanly. RED at the pre-fix coordinator: one cup raced, not two (race 5 never booted). - STATE-HASH witness: the resident descriptor-less path now emits, once per epoch, the same FNV confirmed-input fold the per-race path logs ([online-resident-live] race fold ...), over a FIXED firstTick-anchored window so two endpoints' folds are directly comparable (an endpoint-local frontier anchor would never be). Log-only, launcher-side, session-gated; loopback rigs also fold the peer for in-process corroboration. The cloud capstone's (d)/(e) now require BOTH witnesses cross-process -- the reducer-agreed finish order AND identical span+hash folds -- restoring the convergence bar the resident path lost when it stopped emitting ENGINE-ONLINE-LIVE fold lines. Verified over the real cloud: both races, identical folds on both processes, full run green.
The 192-byte stack overflow fixed in 18a639bd had no instrument that could
see it. fuzz_modern_character_asset never called registry_init, and
check_array_bounds_sweep drives retail routes whose fixtures never exceed
64 animations. The fix was argued statically; this proves it.
The fixture is the corpus's valid-character.mdkc with its ANIMATIONS
section expanded from 1 record to 65 -- one past the old array -- with the
later section offsets shifted, the declared size corrected and the payload
CRC recomputed, so it is a genuinely valid asset rather than a corrupt one
the loader would refuse early. It loads with animations=65.
The test stages it in a scratch cache directory and calls the public
mdkr_modern_character_registry_init(), which is the same entry point
engine_boot, ui_settings and the runtime use. It asserts the character is
ADMITTED and not skipped, because a skip would mean the fixture never
reached the animation walk and the test would be proving nothing.
Measured both ways under ASan. Pre-fix:
ERROR: AddressSanitizer: stack-buffer-overflow, WRITE of size 1
#0 registry_init modern_character_registry.c:489
#1 mdkr_modern_character_registry_init :599
which is exactly the animation_moves[index] = 1u line. With the fix: clean,
count=1, skipped=0.
The fixture also joins the fuzz corpus, so the animation walk added in
df629834 now has a seed that exercises a large animation count rather than
the single-clip one it had.
Summary
The integration commit is parented directly to public main and contains only the reviewed public tree. No ROM or ROM-derived game assets are included.
Validation
Release publication remains a separate manual decision.