Integrate post-1.0 hardening and opt-in high-rate presentation - #1
Integrate post-1.0 hardening and opt-in high-rate presentation#1akratch wants to merge 1 commit into
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.
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.