Skip to content

perf(keys_highway_3d): window updateScene to the visible note span - #141

Merged
cchacs merged 1 commit into
mainfrom
pullfrog/95-keys-highway-window-update-scene
Oct 6, 2026
Merged

cchacs merged 1 commit into
mainfrom
pullfrog/95-keys-highway-window-update-scene

Conversation

@pullfrog

@pullfrog pullfrog Bot commented Oct 6, 2026

Copy link
Copy Markdown

Fixes #95.

What

updateScene() ran three full-array passes every frame: camera pan/zoom framing over every note mesh, the positions/labels/key-glow pass over every note mesh again, and the floating bar numbers over every marker sprite. Per-frame cost therefore grew with the chart's total length no matter how little of it was on screen.

Both t-sorted arrays are now windowed to the span that can actually be drawn:

  • Module helpers (next to scrollZ): noteWindowAheadS(highwayLen, hitZ) (front edge reaches the runway's far end), noteWindowBehindS(maxNoteLen) (max of the note-consumption exit and the label fade), MARKER_WIN_BEHIND_S, _tBound, and advanceTWindow — a per-frame lower-bound search over [now - behind, now + ahead] that calls onHide only for runs that just fell out of the previous window (fresh/rebuilt lo = -1 hides nothing).
  • updateScene advances both windows once, then the camera loop, note loop, and marker loop each walk for (let i = win.lo; i < win.hi; i++) with their original bodies and per-entry geometry checks unchanged (kept: they remain exact for markers, conservative for notes between the shared bound and each note's own exit).
  • Entries leaving the window are parked hidden exactly once (_hideNoteWindow / _hideMarkerWindow); re-entering entries (backward seek) are fully re-derived on their first processed frame.
  • Window state resets in buildNoteMeshes, buildMarkerSprites, and teardown alongside the array reassignments; markers are now explicitly t-sorted at build (windowing binary-searches them).
  • The linger margins shared by the window math and the in-loop hides are one pair of constants (NOTE_LINGER / MARKER_LINGER) so they can't drift apart.

Bounds at stock geometry are ≈ [-0.62 s, +8.67 s], which contains every span updateScene filters on inside the window (camera band [-0.4, CAM_ZOOM_AHEAD], key glow [-0.05, KEY_GLOW_AHEAD]) — so framing, glow, labels, consumption, and miss-sweep behaviour are unchanged; only the iteration is bounded. No new per-frame allocations (binary search + stable keyOf/hide callbacks).

Tests

  • data_layer.test.js: window-bound geometry tied back to scrollZ (the ahead bound is exactly the front-edge/runway crossing), behind-bound covers longest-note consumption and the label fade (with a tightness bound so it can't grow beyond the longest note + label), monotonicity, and the advanceTWindow state machine (fresh-window/no-hide, steady advance delta-hide, forward-seek vs rewind, same-onset chord runs as one run, marker keyOf, empty arrays).
  • instance_invariants.test.js: source-scan pins the updateScene wiring — both advanceTWindow calls with correct behind-minus/ahead-plus pairing, windowed for loops, no remaining full-array of loops — and that all four array reassignments reset their window beside them (the pure helpers alone can't catch them being exported and never wired up).

node --test (full CI-equivalent JS suite): 1449/1449 pass. npx eslint: 0 errors (pre-existing max-lines warning only).

Out of scope

Per-note label sprite/material memory (the issue's second bullet) — unchanged here; this PR is the windowing fix the issue's suggested approach describes.

Self-review via reviewfrog: no blockers; its three findings (shared linger constants, band-containment pin, reset-adjacency pin) are addressed in this diff.

Pullfrog  | View workflow run | via Pullfrog | Using big-pickle (free) | 𝕏

updateScene walked every note mesh twice per frame (camera framing,
positions/labels/key glow) plus every bar-number sprite, so its cost
grew with the length of the chart however little of it was on screen.
Both t-sorted arrays are now windowed to the drawable span via
advanceTWindow: entries that fall out are parked hidden exactly once,
and the loops walk only [lo, hi). Bounds derive from the runway
geometry and the chart's longest note (shared NOTE_LINGER /
MARKER_LINGER constants with the in-loop hides), so nothing drawable
is skipped.
@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 27 complexity · 2 duplication

Metric Results
Complexity 27
Duplication 2

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@kilo-code-bot

kilo-code-bot Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0
Files Reviewed (4 files)
  • plugins/keys_highway_3d/screen.js - 0 issues
  • plugins/keys_highway_3d/tests/data_layer.test.js - 0 issues
  • plugins/keys_highway_3d/tests/instance_invariants.test.js - 0 issues
  • CHANGELOG.md - 0 issues

Reviewed by free · Input: 755.1K · Output: 38.1K · Cached: 587.3K

@cchacs
cchacs merged commit 01dbe0e into main Oct 6, 2026
14 checks passed
@cchacs
cchacs deleted the pullfrog/95-keys-highway-window-update-scene branch October 6, 2026 16:49
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.

keys_highway_3d: updateScene iterates every note mesh each frame

1 participant