Code-review follow-ups: obstacle radius, SeriesTally extraction, trail perf, test coverage#3
Conversation
The per-kind obstacle radius default was duplicated across three files and disagreed: SlopeBuilder used rock=0.85/tree=0.7, while SkiEngine (rock=0.7/ tree=0.8) and SlopeScenery's hitbox outline had it inverted. The builder always stamps an explicit radius so the live path was unaffected, but the mismatched dead fallbacks were a latent footgun. Add obstacleRadius(kind) to shared/slopes.js as the single source of truth for the procedural kinds (rock/tree); SlopeBuilder and SlopeScenery consume it. The import-free SkiEngine can't import it, so its fallback becomes kind-agnostic (o.radius || 0.8) — unreachable in the live path since the builder supplies the radius. Also promote the engine's ramp half-length literal to a named RAMP_HALF_S (value unchanged) and drop the dead, never-read ramp.radius field. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mgpq3wWcaK5ArAhgKW22ch
Centerline.js had no direct tests — the engine tests use a stub centerline, so the real spline interpolation (endpoint stencil clamping, degenerate-span nudges, the divide-by-zero guards) never ran under test. Add tests over the real Centerline + three: endpoint exactness, s<0/s>length clamping without NaN, finite unit-length tangents across the run, arclength monotonicity, and degenerate (2-point / duplicated-point / zero-length span) cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mgpq3wWcaK5ArAhgKW22ch
addPoint() set posAttr/nrmAttr.needsUpdate, re-uploading both full ~48KB ring buffers on every recorded point even though only one 4-vert slot (12 floats) changed — the dominant trail bandwidth cost on weak hardware. Scope the uploads to the changed slot via BufferAttribute.addUpdateRange(vb, VPP*3); three's GL backend clears the ranges after each upload, so re-adding per point is correct. The index buffer is deliberately left full-upload (the ring wrap seam makes incremental index updates unsafe); buffer contents and index logic are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mgpq3wWcaK5ArAhgKW22ch
…onstructor TiltInput and SwipeInput added window keydown/keyup listeners from the constructor (_bindKeys) but stop() only removed the pointer/orientation listeners, so the keyboard listeners leaked on re-instantiation. Bind the handlers once and register/unregister them in start()/stop(), symmetric with the pointer listeners. Handler bodies, keys, and preventDefault behavior are unchanged; keyboard input only matters during driving, which is exactly the start()/stop() window. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mgpq3wWcaK5ArAhgKW22ch
…ap sound hint main.js was a 1170-line god file. Lift the cross-run scoring (run index/length/ over-flag, per-player banked scores, points folding and standings-row derivation) into display/SeriesTally.js — a dependency-free, THREE-free class with seriesPoints injected, so it loads under Node and gets real unit tests (tests/seriesTally.test.js) the logic previously lacked (only e2e covered it). main.js keeps the lifecycle/IO around it. Behavior is preserved end-to-end (15/15 e2e over the relay). Also: - Share the "Run X of N" / "Final standings" header wording between the big screen and the phone board via shared/seriesFormat.js (runTag), the one bit that was genuinely duplicated between the two render paths. - Bound showSoundHint's autoplay-poll interval to ~30s so a kiosk that never unlocks audio doesn't poll forever (a later gesture still clears the hint). - Sync CLAUDE.md to the new SeriesTally location. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mgpq3wWcaK5ArAhgKW22ch
Preview
|
|
Claude finished @tim4724's task in 6m 17s —— View job Code Review
No correctness bugs found. The logic across all five change areas is sound. Notes below are test-quality observations only. fix(slopes) — obstacle radius unification
perf(trails) — scoped GPU uploadsThe gl.bufferSubData(bufferType, range.start * array.BYTES_PER_ELEMENT, array, range.start, range.count);The vendored Three.js uses element-index units for fix(controller) — keyboard listener lifetimeThe refactor(display) — SeriesTally extractionThe refactor is clean. Key correctness checks:
Tests
Overall: clean, well-motivated changes with strong test coverage. The co-champion sort, the trail ring-buffer math, and the fold/buildRows split are all correct. Ready to merge. |
Follow-ups from a thorough code/architecture review. Correctness, coverage, performance, and maintainability — no behavior change in the live game (verified end-to-end).
Changes
obstacleRadius(kind)source of truth (shared/slopes.js), consumed by builder + scenery; engine fallback made kind-agnostic;RAMP_HALF_Snamed; deadramp.radiusdroppedCenterline.jshad zero coverage — engine tests use a stub, so the real interpolation + divide-by-zero nudges never ran under test.addUpdateRange)start()/stop(), not the constructorstop()only removed pointer/orientation listeners — keyboard listeners leaked on re-instantiation.SeriesTallyfrom the 1170-linemain.js(dependency-free, unit-tested); sharerunTag; capshowSoundHintpollVerification
seriesTally+centerline+obstacleRadiustests)Deliberately out of scope
Hostile-peer defenses don't apply to a local couch party game (the relay-stamped
fromis the real trust boundary), so these were skipped by design: reclaim-token auth, message-size cap, PAUSE debounce, and thestyle-srcCSP nit. Also skipped:car→Skierrename (churn) and theMODULE_TYPELESStest warning (risky fix).🤖 Generated with Claude Code