diff --git a/CHANGELOG.md b/CHANGELOG.md index 8236ffa..224037e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -212,6 +212,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 engine (`app.js`, `highway.js`, `playSong`, `showScreen`, the capability registry). ### Fixed +- **3D Highway: the lane stops at the hit line** (#991) — the highway lane, its + dividers, and the fret boundary extension lines ran `BEHIND` seconds *past* the + hit line toward the player. Nothing is ever drawn in that strip (notes and chord + frames clamp to `Math.min(0, dZ(dt))`), so it read as lane with no notes on it. + The floor geometry now ends at the hit line; its far edge is unchanged, still + `-AHEAD*TS` at the note horizon. - **Career passports review polish** — the passport tabs and book overlay carry proper ARIA semantics (`aria-selected`/`aria-controls`/`tabpanel`; `role="dialog"` + `aria-modal` with focus moved to the close button on open diff --git a/plugins/highway_3d/screen.js b/plugins/highway_3d/screen.js index 3bc2ff8..521a305 100644 --- a/plugins/highway_3d/screen.js +++ b/plugins/highway_3d/screen.js @@ -12614,8 +12614,19 @@ const tC = now + (dt0 + dt1) * 0.5 - BEHIND; const b = laneBoundsFromAnchor(getChartAnchorAt(anchors, tC)); if (!b) continue; - const z0 = dZ(dt0) + TS * BEHIND; - const z1 = dZ(dt1) + TS * BEHIND; + // The lane STOPS AT THE HIT LINE (z = 0) — issue #991. The + // slice window starts BEHIND seconds in the past, so the + // first slices map to positive z, i.e. past the hit line + // toward the player. Nothing is ever drawn there: notes and + // chord frames clamp to Math.min(0, dZ(dt)), so that strip + // is lane with nothing on it. Clamp the NEAR edge only — + // the far edge stays at dZ(AHEAD+BEHIND)+TS*BEHIND = -AHEAD*TS, + // aligned with the note horizon, exactly as before. + const z0 = Math.min(0, dZ(dt0) + TS * BEHIND); + const z1 = Math.min(0, dZ(dt1) + TS * BEHIND); + // Slice lies entirely past the hit line -> zero length, nothing + // to draw. Skip before the arp probe so it costs nothing. + if (z0 === z1) continue; const arpSlice = (laneRailArpHsFlags && handShapesRails && handShapesRails.length) ? arpeggioLaneOuterRailLaneSlice( dt0, dt1, now, @@ -12764,9 +12775,13 @@ divMin = dMin; divMax = dMax; - // Same fix: extend to AHEAD+BEHIND so far edge = -AHEAD*TS. - const laneLen = TS * (AHEAD + BEHIND); - const zLane = -laneLen / 2 + TS * BEHIND; + // Far edge at -AHEAD*TS (the note horizon), near edge at the + // hit line (z = 0) — the lane does not run past it toward the + // player, where nothing is ever drawn (#991). Spanning + // AHEAD+BEHIND and shifting by +TS*BEHIND put the near edge at + // +TS*BEHIND; spanning AHEAD alone keeps the same far edge. + const laneLen = TS * AHEAD; + const zLane = -laneLen / 2; const laneOp = (HWY_LANE_STRIPE_OP_BASE + highwayIntensity * HWY_LANE_STRIPE_OP_INT) * (_venueSceneOverride ? VENUE_LANE_OP_BOOST : 1); mLaneOdd.opacity = laneOp; @@ -12786,7 +12801,8 @@ } if (highwayIntensity > 0.05) { - const divLen = TS * (AHEAD + BEHIND); + // Matches the lane above: ends at the hit line (#991). + const divLen = TS * AHEAD; const yPos = boardY + 0.03 * K; const divOp2 = 0.02 + highwayIntensity * 0.1; const divOpArp2 = Math.min(0.92, 0.16 + highwayIntensity * 0.42); @@ -12799,7 +12815,7 @@ for (let f = fDivA; f <= fDivB; f++) { if (hwyLaneArpOuterDividers && (f === fDivA || f === fDivB)) continue; const div = pLaneDivider.get(); - div.position.set(xFret(f), yPos, dZ(0) - divLen * 0.5 + TS * BEHIND); + div.position.set(xFret(f), yPos, -divLen * 0.5); div.material = mLaneDivider; div.scale.set(1, 1, divLen); div.renderOrder = 2; @@ -12818,8 +12834,10 @@ // ── Fret boundary extension lines ───────────────────────── if (mLaneDividerExt && fretDividersVisible) { - const extLaneLen = TS * (AHEAD + BEHIND); - const extZMid = -extLaneLen / 2 + TS * BEHIND; + // Same hit-line stop as the lane (#991) — otherwise these lines + // would be the only floor geometry still running past it. + const extLaneLen = TS * AHEAD; + const extZMid = -extLaneLen / 2; const extYPos = boardY + 0.03 * K; mLaneDividerExt.opacity = Math.max(0.3, 0.3 + highwayIntensity * 0.15); for (let f = 0; f <= NFRETS; f++) {