Skip to content

refactor(engine): extract per-frame body to runFrame.ts#44

Merged
rulkens merged 3 commits intomainfrom
chore/extract-runFrame
May 8, 2026
Merged

refactor(engine): extract per-frame body to runFrame.ts#44
rulkens merged 3 commits intomainfrom
chore/extract-runFrame

Conversation

@rulkens
Copy link
Copy Markdown
Owner

@rulkens rulkens commented May 7, 2026

Summary

Phase 3 of Spec B's engine internal restructure. The ~310-line per-frame body has been lifted out of engine.ts into a dedicated runFrame.ts module.

  • src/services/engine/runFrame.ts (new): the per-frame body, lifted verbatim. No behaviour changes.
  • src/services/engine/engine.ts: forward-declared frame arrow now dispatches to runFrame(state, frameDeps, performance.now()). Net diff: -272 lines (2225 → 1953).
  • tests/services/engine/runFrame.test.ts (new): focused integration test covering the FPS-counter wiring through the new RunFrameDeps.

Closure captures threaded through RunFrameDeps

Name Source R/W Pattern
canvas createEngine arg RO by reference
cb createEngine arg RO by reference
fpsCounter createEngine local const RO by reference
lastReportedFps createEngine local let RW {current} ref
device IIFE local RO by reference
context IIFE local RO by reference
milkyWayRenderer IIFE local RO by reference
filamentRenderer IIFE local RO by reference
quadRenderer IIFE local RO by reference
diskRenderer IIFE local RO by reference
milkyWayITimeEpochMs createEngine local const RO by value
cssToTexPx createEngine local function RO by reference
setHovered createEngine local function RO by reference
updateScaleBar createEngine local function RO by reference

Only lastReportedFps needed the {current} ref pattern — it's the only mutable closure value the body writes directly. lastScaleSig (also a let) is encapsulated inside updateScaleBar()'s closure, so it stays inline; same story for the other helpers' internal state.

Deviations from the plan

  • The plan mentioned a scheduleFrameTail() helper that "stays in engine.ts". In practice the keep-rendering predicate is a single 8-line conditional with no other call sites, so it lives inside runFrame rather than as a separate helper. Splitting it out would have been pure ceremony.

Test plan

  • runFrame.test.ts passes (3/3 — FPS round-trip, dedup, bootstrap)
  • npm run typecheck clean (both src and tools configs)
  • full suite green (918 tests; was 915 pre-phase, +3 from this PR)
  • manual smoke: render loop visibly steady; FPS readout updates; scale bar refreshes; hover/click/tween work; SpaceMouse still feeds; auto-rotate gates correctly during home tween

Spec: docs/superpowers/specs/2026-05-08-engine-internal-restructure-design.md

🤖 Generated with Claude Code

rulkens and others added 2 commits May 8, 2026 01:51
Per-frame body lifted verbatim from engine.ts:1407-1708 into runFrame.ts.
Closure-captured locals threaded through RunFrameDeps; the mutable
`lastReportedFps` becomes a {current} ref so its writes round-trip into
engine.ts across the module boundary.

`updateScaleBar` and `setHovered` are passed as dep functions rather
than ref-ifying their internal `let`s (lastScaleSig, hovered, etc.) —
the helpers already close over those locals and the frame body never
reads/writes them directly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Frame body collapses to a single runFrame(state, frameDeps, now) call.
The forward-declared `frame` arrow now just dispatches to runFrame; the
~310-line per-frame body that used to live inline in engine.ts has been
relocated wholesale to runFrame.ts.

Closure-captured locals are threaded through `RunFrameDeps`:
- `lastReportedFps` becomes a `{current}` ref (only mutated entry).
- `fpsCounter`, `updateScaleBar`, `setHovered`, `cssToTexPx`,
  `milkyWayITimeEpochMs` ride as read-only refs / values.
- IIFE-local renderers (`device`, `context`, `milkyWayRenderer`,
  `filamentRenderer`, `quadRenderer`, `diskRenderer`) ride as
  read-only refs.

The keep-rendering predicate (formerly the "stillAnimating" tail of
the frame body) lives inside `runFrame` itself in this phase — there
is no separate `scheduleFrameTail` helper to keep behind in engine.ts.
The plan mentioned one as a possibility; in practice it's a single
condition that can stay in the lifted body without losing readability.

Removes now-unused imports: `computeViewProj`, `autoLodMask`,
`renderFrame`, `PROCEDURAL_DISK_FADE_START_PX`,
`PROCEDURAL_DISK_FADE_END_PX`.

engine.ts: 2225 → 1953 lines (-272).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 7, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
skymap 8882cfd Commit Preview URL

Branch Preview URL
May 08 2026, 12:02 AM

…redicate

Per code-quality review nit: the `if (visibleSources.length === 0)`
early-return inside the hover-pick block bypasses the keep-rendering
predicate at the end of runFrame.  In engine.ts pre-extraction the
two were close together and the asymmetry was easier to spot; the
relocation pushed them ~80 lines apart.  Acknowledge the by-design
behaviour with a comment so a fresh reader doesn't think it's a bug.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@rulkens rulkens merged commit c23dd9f into main May 8, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant