Skip to content

feat(highway_3d): fret wires flash on a confirmed hit#969

Draft
topkoa wants to merge 2 commits into
mainfrom
feat/highway-3d-fret-wire-hit-flash
Draft

feat(highway_3d): fret wires flash on a confirmed hit#969
topkoa wants to merge 2 commits into
mainfrom
feat/highway-3d-fret-wire-hit-flash

Conversation

@topkoa

@topkoa topkoa commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

What

The 3D highway's fret wires were static scenery — gold inside the anchor lane, grey outside, and nothing tied them to what the player was doing. This gives them a job:

  1. Widened the lane/neck contrast, so the wires around the active lane read as a focus cue and the rest of the neck recedes.
  2. Made the wires slightly thicker (and bumped the tube's radial segments, since a fatter tube shows its hexagonal facets).
  3. Flash the wires bracketing a note when a scorer confirms a hit, so a hit registers on the board itself. Here is a good example of it in action for reference: https://www.youtube.com/watch?v=gIpDvz4PNaQ

Which wires flash depends on the note:

Wires lit
Fretted single note on fret f f-1 and f — the wire behind it, and the wire it's pressed against
Chord Only the outermost wires of the shape (behind the lowest fret, at the highest). Frets 3/5/5 → wires 2 and 5, not 2‑3‑4‑5, so it reads as one bracketed block instead of a picket fence
Open string The anchor lane's edge wires. An open string has no fret of its own, and its gem is drawn as a wide slab spanning the lane, so those are the wires it actually sits between

⚠️ Draft: not yet verified on screen

This has not been run. It typechecks and the logic is reviewed, but every value here is a taste call that has to be judged by eye, and it hasn't been. The brightness in particular went through several rounds of "brighter" / "too bright" and landed at a value that was never actually looked at. Treat the constants as a starting point, not a finished look.

The levers — all named constants, all in one block

All in plugins/highway_3d/screen.js, in the fret-wire constants block next to FRET_METALNESS / FRET_BOW_DZ. Nothing here is derived from anything else, so each can be moved independently.

Hit flash

Constant Now What it does
FRET_WIRE_HIT_INTENSITY 4.2 The main brightness knob. Multiplies emissiveIntensity at full flash (baseline 1). This is what makes a wire read as a light source rather than a brightly-lit object. Went 3.0 → 6.0 → 4.2; last feedback on 6.0 was "slightly too bright", so 4.2 is an untested guess between.
FRET_WIRE_HIT_DECAY 0.32 The linger knob. Seconds for a flash to fall to ~1/e once the provider stops reporting. Deliberately independent of brightness — a long tail can read as "too bright" even when the peak is right, so if it feels hot, try trimming this to ~0.22 before dropping the intensity.
FRET_WIRE_HIT_EMISSIVE 0xFFE9B0 Glow colour at full flash (hot warm-white).
FRET_WIRE_HIT_HEX 0xFFFFFF Albedo at full flash. Has less effect than you'd expect — see the note below.
FRET_WIRE_HIT_OP 1.0 Opacity at full flash.

Why emissive and not colour: these are MeshStandardMaterial in a scene with no envMap, so raising albedo alone barely brightens them — it saturates toward white and stops. emissiveIntensity is the lever with real range. Corollary: there's no bloom/post-processing pass in this scene, so past ~8 you're just painting white pixels for longer and can't get glow bleed into the surrounding area. If it needs to be brighter than the material can go, the next step is an additive halo mesh around the flashing wire — a bigger change, not a constant.

Base look (the two idle tiers)

Constant Now Was
FRET_WIRE_ACTIVE_HEX / _OP 0xD8A636 / 0.9 0xD8A636 / 0.8
FRET_WIRE_IDLE_HEX / _OP 0x4A4A60 / 0.28 0x666688 / 0.4
FRET_TUBE_RADIUS STR_THICK * 0.75 * 0.55
FRET_TUBE_RADIAL 8 6

Things to actually look at when picking this up

  • Fast passages. With a 0.32 s tail, consecutive notes on nearby frets overlap their flashes, so neighbouring wires may stay lit more or less continuously. That could look great (a glowing band tracking your hand) or muddy (nothing ever goes dark). This is the single most likely thing to need tuning.
  • Held sustains. The provider returns alpha tracking live input level for a held note, which jitters frame to frame. The decay tail is what smooths it — check it's actually smooth and not pulsing.
  • High frets under fog. The idle tier is dimmer now (0.28 vs 0.4); confirm distant wires haven't been swallowed entirely.
  • Charts with no anchors. anchorLaneBoundsAt returns null, so open-string flashes silently do nothing (fretted notes are unaffected). Rare — GP imports synthesise anchors — but it's the one case that fails quietly rather than visibly.

Implementation notes for a reviewer

  • Gated on the provider verdict (_ndGood), never the proximity hit heuristic. The latter only means "near the strike line" — using it would flash on every passing note whether or not it was played. So with no scorer attached, the neck behaves exactly as it does today.
  • Two passes, deliberately. The base tier loop (gold/grey) runs near the top of update(), long before any note is drawn. The flash is applied in a second pass after the note and chord draw loops, so it sees this frame's verdicts rather than the previous frame's. emissive / emissiveIntensity are re-seeded to baseline in the tier loop each frame — without that, a flash would never fade back out.
  • Chords accumulate. drawNote() is the chord loop's per-note call and can't see the chord's span, so chord hits collect into a small Map (keyed by chord id, falling back to chord time — ch.id can be absent, a documented pitfall) and the flash pass resolves min/max fret into the outer wire pair. This avoided restructuring the chord loop with begin/end hooks.
  • The flash decays in chart time, so it respects playback speed, and a backward seek clears it rather than leaving a stale flash on a wire you jumped away from.
  • drawNote() is a sibling of update(), not nested in it, so the anchors are snapshotted into a closure var (_drawAnchors) — following the pattern already established there for _drawChordTemplates.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d4a4dada-d89d-475d-a59d-1b36b7d72bc7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/highway-3d-fret-wire-hit-flash

Comment @coderabbitai help to get the list of available commands.

The fret wires were static scenery: gold inside the anchor lane, grey
outside, and nothing tied them to what the player was actually doing.

Give them a job. Widen the lane/neck contrast so the wires around the
active lane read as a focus cue, and flash the wires bracketing a note
when a scorer confirms it. A fretted note lights the wire behind it and
the wire it is pressed against; a chord lights only the outermost wires
of its shape, so it reads as one bracketed block rather than a picket
fence; an open string has no fret of its own and its gem is drawn as a
slab spanning the lane, so it lights the lane's edge wires instead.

Gated on the provider verdict, never the proximity heuristic -- the
latter only means "near the strike line", so it would flash on every
passing note whether or not it was played. With no scorer attached the
neck behaves exactly as before.

Emissive (and emissiveIntensity) carry the flash, not albedo: these are
MeshStandard materials in a scene with no envMap, so raising albedo
alone barely brightens them.

Every value is a named constant -- see FRET_WIRE_* -- because the look
is a taste call that wants tuning by eye, not a derivation.

Signed-off-by: Kris Anderson <topkoa@gmail.com>
@topkoa
topkoa force-pushed the feat/highway-3d-fret-wire-hit-flash branch from f55c021 to 981b779 Compare July 14, 2026 21:50
Bring the branch up to date with main (includes #994, which also touched
highway_3d/screen.js — the lane hit-line fix — in a different region).
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