| No-input physics is byte-identical to the frozen baseline | tests/verification/physics_invariant_harness.js (test:verify) gates exit on max-abs diff 0; gate new behavior behind controls.* / per-tier tuning / provenance flags | you add ungated randomness or force to the grounded path |
Seeded Math.random stream is not perturbed | withPrivateThreeRandom(fn) (src/scenery/scenery-rng.ts; original in mountains/trees.ts getSwayDepthMaterial); makeSceneryRng(seed) for placement | any THREE construction on a seeded path draws UUID randomness |
| Physics is fixed-timestep & frame-rate independent | FIXED_DT=1/60, MAX_SUBSTEPS=8 accumulator in src/game/main-loop.ts; src/diagnostics.ts (Diag) detects it live | per-frame factor mixed with dt-scaled force; per-step > collision radius (tunneling) |
| Terrain height two-formula contract | createTerrain() mesh term and getTerrainHeight(x,z) sampler are commented MUST MATCH; edit in lockstep; key heightMap per tier/run | you edit one path only → snowman floats/sinks; or stale cache serves old heights |
| Collision arrays are the single source of truth | only treePositions / rockPositions drive collisions; course-line.ts laneX(z) is the one centerline | a cosmetic layer writes them, or path math is duplicated |
| Cosmetics never mutate kernel state | read per-frame result only; snow-splash restores pos after moving particles | you write pos/velocity/rotation.y from a cosmetic tick |
| Everything is teardown-safe & idempotent | dispose() (56× in src); test:leak, test:teardown pin it; build-epoch cancels in-flight async | a singleton/window.* closure/InstancedMesh buffer/timer survives dispose |
| Automation serves a reduced deterministic scene | gate heavy/nondeterministic features off under window.isTestMode || navigator.webdriver || ?test=; opt in via window.testHooks.* | you screenshot the automation path and think the game is broken |
No window.* module bridges | modules import each other; only AuthModule/ScoresModule + boot/test seams stay global | you re-add window.Mountains/window.Camera/THREE |
| Run clock ↔ physics pause together | src/game/run-clock.ts shifts startTime on tab-hide and skips stepping while hidden | a throttled background rAF banks free distance (cheat) or inflates times |
| Never trust the client for score validity | MIN_VALID_SCORE_TIME enforced client + server (firestore.rules); guests kept out of Firestore | you enforce only client-side, or forget rules deploy is not auto |