feat(wasm): ui_set_tick_rate export — declared-rate realms reach the sim - #276
Draft
NathanWalker wants to merge 1 commit into
Draft
feat(wasm): ui_set_tick_rate export — declared-rate realms reach the sim#276NathanWalker wants to merge 1 commit into
NathanWalker wants to merge 1 commit into
Conversation
The wasm core takes the same declare-before-first-tick lifecycle as the native surfaces; wasm-ops publishes an applied rate as ops.__tickHz and retracts it on init's core reset. The sim declares Scenario.tickHz before eval (mount-time conversions run at the rate), drives tickHz/hz core ticks per virtual frame, and touchGlide's finest-grid emission takes the rate instead of a literal 60. Co-Authored-By: Claude Fable 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.
The coverage deferral from #257:
engine/wasmexported noui_set_tick_rate, so sim/goldens/tapes could not reach a non-60 realm at all. This closes it end to end.What changed
engine/wasm/src/lib.rs:ui_set_tick_rate(hz) -> i32, delegating toUi::set_tick_rate— the same declare-before-first-tick lifecycle andMAX_TICK_HZgate the native surfaces settled on in feat(core): per-realm tick rate — fixed-step at a declared hz #257 (returns 0 onceui_tickhas run).hosts/web/wasm-ops.js: feature-detectedops.setTickRate(stale binaries keep booting at the spec 60, theui_set_viewportconvention). An applied rate is published asops.__tickHz— the same mount fact the native surfaces publish — andinit()'s core reset retracts it, so a runner that resets must redeclare. Theinit(initialDensity)boot call moved below theopsliteral to make that retraction possible; nothing in between calls into the wasm.hosts/sim/sim.ts:Scenario.tickHz/SimBootOptions.tickHz(default 60).bootWorlddeclares the rate before eval — the order the native surfaces enforce, since mount-timeanimate()/spring()convert ms at the rate — refuses anhzthat does not dividetickHz, and drivestickHz/hzcore ticks per virtual frame.SimWorld.tickHzis exposed so rate-aware companions pair with it (createSimAudioSink(world.tickHz)once fix(audio): audioFramesForTick counts frames at the realm's declared rate #275 lands).touchGlidetakes the finest grid as a parameter instead of a hardcoded 60 (the sim-side hardcode called out in review).tests/tick-rate-sim.test.ts+ atick-rate simgate stage (prep rebuilds the wasm and bakes a--hz=120café intodist/tick-rate-120/).Coverage
__tickHz; 0 / 241 / post-tick rejected with nothing published;init()resets to 60, retracts__tickHz, and a fresh declaration applies again.tickHz: 120: deterministic across runs, "ORDERS PLACED 1" in the tree, and the subsampling theorem holds at the declared rate — 60 Hz and 30 Hz presentations strictly subsample the 120 Hz trajectory (sub[m] == full[k(m+1)-1]for every m), byte-equal final frames, effects at identical virtual seconds.hznot dividingtickHzrefuses loudly.One finding worth recording: the first draft of the subsample test used the 120-baked hero, and it diverges at frame 1 — hero's mount spring is guest-side per-frame state, which DETERMINISM.md's theorem statement explicitly scopes out ("JS state changes only on events and virtual time"). The café is the theorem's reference app and subsamples exactly. The theorem's scope condition is doing real work at non-60 rates, not just at 60.
Out of scope
hosts/web/engine.js's browser pacing (let simHz = 60,60 / simHz) — driving a non-60 realm in the browser host needs a rAF pacing decision, not just plumbing; flagged for a follow-up.Verification
bun run test12/12 stages green (47.4 s) including the new stage;bunx tsc --noEmitclean.🤖 Generated with Claude Code