Repository navigation
Conversation
Host pages (Observatory coaching, PlayerADE) need to drive the GotA WASM
replay viewer: exact seek, play/pause/speed, camera modes, and screen<->world
queries for overlays.
- src/polyworld/replaycontrols.nim: game-agnostic C exports over the shared
Player transport plus ReplayControlHooks (groundY, pick, unit, camera) that
a game installs; per-frame callback with tick, seek id and view-projection.
- replay.html: window.polyworldReplay for same-origin hosts, a postMessage
bridge ({dst:'coworld-replay'}) with opt-in tick/frame events, and URL
options embed=1 (hide HUD, block input, forward keys), loop=0|1, seekSlice.
Existing outbound messages are unchanged.
- player.nim: paused seeks now yield after a per-transport catchUpSeconds
budget instead of blocking until they land; add cancelSeek.
- GotA installs the hooks; unit queries report alive and seat.
- Tests: test_replaycontrols, test_player cases, browser control checks
(--control-only, --gpu); GotA browser end tick updated to 28909.
- Docs: docs/replay-control.md, coworld/integration.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add a replay control API for embedded browser viewers
What and why
Pages that embed the WASM replay viewer can currently only load a replay and listen
for
loading,readyanderror. Coaching and analysis tools, such as SoftmaxObservatory coaching and PlayerADE, need to drive the viewer. They need to seek to
the moment being discussed, pause, change speed, choose the camera, and draw
overlays that stay on top of units. That last one needs screen-to-world projection
and picking.
This PR adds that control surface. Gods of the Arena is hooked up. Other games
load as before and report that control is not available.
src/polyworld/replaycontrols.nim(new, game-agnostic): C exports over theshared
Playertransport (seek, cancel, play, pause, speed, loop, seek slice),plus projection math. Game-specific queries go through a small
ReplayControlHooksobject that the game installs: ground height, pick, unitlookup and camera mode. After presenting each frame, the game calls
publishReplayControlFrame. That call reports the tick, play and seek state, aseek id and the view-projection matrix the frame was drawn with.
src/polyworld/replay.html:window.polyworldReplayfor same-origin hosts. It is synchronous and works inCSS pixels.
{dst: 'coworld-replay', type, ...}, and queries are answered through arequestId.tickevents (throttled to 10 Hz) andframeevents.embed=1hides the in-canvas HUD, blocks direct mouse, wheel, touch and keyinput, and forwards key presses to the host.
loop=0|1sets looping.seekSlice=MSsets the seek time slice.only to hosts that subscribe or use
embed=1.src/polyworld/player.nim:catchUpSeconds.Before, they ran to completion inside one frame, so a cold seek to the end of
a match froze the page for about 7.5 s.
cancelSeek()clears both the seek target and any pending restore.examples/gods_of_the_arena/graphics.nim):pickEntitynow takes an optional screen position, which defaults to themouse.
aliveandseat.director,fixed,follow, andfreeze, which holds thecurrent pose.
The API is documented in
docs/replay-control.md, whichcoworld/integration.mdlinks to.
How to use it
Viewer URL:
gota.html#replay=URL&embed=1&loop=0.What to review
The paused-seek change in
Player.shouldTickaffects every game's transport.Paused seeks used to finish in one frame. They now yield after 100 ms by
default, the same budget playing seeks already had.
seekingis true.tests/test_player.nimnow passesepochTime()instead of0as the framestart. With
0the budget is always exceeded, and the old assertions onlypassed because paused seeks ignored the budget.
The seek slice is a trade-off between responsiveness and seek time. These
numbers are for a cold seek over a 28,909-tick match, measured on an M-series
Mac in headless Chromium using Metal:
The worst single gap was about 300 to 460 ms at every slice. Something other
than simulation causes those gaps, and I did not trace it. Hosts can pick
their own slice with
seekSlice.embed=1blocks input in JavaScript, not in Nim. A capture-phase listeneron
windowstops events before Emscripten or windy see them. Resize is notblocked. This keeps the Nim input code untouched.
Exports pass vector results through
polyworld_replay_result(i). Thisavoids relying on Emscripten heap views being exported, which the 6.x builds
in testing did not need.
The GotA end tick in the browser test changed from 18680 to 28909. A
recording made today with
base.basfor all 10 seats runs to the time limit,1204.54 s at 24 ticks/s. The old constant no longer matches what the
documented recording procedure produces.
Tests run
All results are from macOS arm64, Nim 2.2.10 and Emscripten 6.0.12.
nim r tests/test_player.nim: pass. Includes new cases for thepaused-seek budget and for
cancelSeek.nim r tests/test_replaycontrols.nim: pass. This is a new test. It coversthe projection round trip and the exports driving the transport and the hooks.
nim r tests/tests.nim: all tests passed.nim r tests/test_directors.nim: pass.nim check:-d:headlessand-d:coworldbuilds.nim r coworld/tools/test_runtime.nim gota: pass, using a freshly built-d:coworldbinary.tmp/coworld/gota.replaywiththe headless binary and
--record. Replaying it with--replayverified8902/8902 actions.
test_browser_with_playwright.py --control-only --gpu: every check passes.tickevents, andrequestIdreplies.and keys are forwarded.
test_browser_with_playwright.py --games gotaon SwiftShader:still on by default.
built locally. That step always loads CTA, even with
--games gota. Thisbehaviour already existed and is not caused by this change.
--control-onlyon SwiftShader: every check passes.seconds late in a match.
asserted without a GPU.
02eda3d:--control-only --gpurun passes.with no divergence.
Follow-ups (not in this PR)
already exists, so seeking back to the end re-simulates the whole match. Two
cheaper options: restore the nearest checkpoint at or below the target in both
directions, or pre-simulate the tape once in the background.
candidates are checkpoint capture and memory growth.
ReplayControlHooks.PlayerSpeeds.🤖 Generated with Claude Code