mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-20 11:51:30 +00:00
feat(highway_3d): wire flash is a lightning strike, not a lingering glow
The flash was instant-on with a 0.32 s exponential tail, and a held sustain kept re-feeding it — wires stayed lit for the whole note. The requested feel is a shock: light hits the frets, they jolt, it's over. The flash is now a one-shot pulse triggered on the input's rising edge: a near-instant crack up (RISE 25 ms), a fast fall (FALL 160 ms) shaped (1-u)^2 so it drops hard then eases out, with a 26 Hz flicker biting into the fall (the electric shudder — the crack itself stays clean), then hard zero. A held 'active' verdict keeps the input high continuously, which by construction triggers nothing new: one strike per hit, and the wires go dark while the note rings on. A re-strike after the provider goes silent re-triggers cleanly. Seeking backward or a long stall clears all pulse state, and a pulse whose strike time lands ahead of the playhead after a seek is discarded. The outer-pair bracket rule is unchanged — it now selects across pulses instead of decay tails. Knobs: FRET_WIRE_HIT_RISE / _FALL / _FLICKER_HZ / _FLICKER_DEPTH (replacing FRET_WIRE_HIT_DECAY). Signed-off-by: topkoa <topkoa@gmail.com>
This commit is contained in:
parent
9a5ec36f47
commit
e05d90e949
@ -55,7 +55,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
survives only as the fallback on anchor-less charts). At most **two wires are ever lit at
|
||||
once**: when overlapping decay tails (fast passages) would light a run of wires, the
|
||||
flash collapses to the outermost pair of the lit span — one bracket, never a picket
|
||||
fence. The gem's rim joins in: on a confirmed hit the outline flashes in the
|
||||
fence. The flash itself is a one-shot "lightning strike": a near-instant crack up, a
|
||||
fast shocked fall with an electric flicker biting into it, then hard dark — a held
|
||||
sustain triggers exactly one strike and the wires go out while the note rings on. The gem's rim joins in: on a confirmed hit the outline flashes in the
|
||||
string's own colour with the same intensity treatment as the wires, fading with the
|
||||
scorer's alpha. With no scorer attached, nothing changes.
|
||||
|
||||
|
||||
@ -921,7 +921,13 @@
|
||||
// The provider already fades its own `alpha` on a struck note; this tail
|
||||
// just keeps the hand-off from popping, and smooths the frame-to-frame
|
||||
// jitter of a held sustain (whose alpha tracks live input level).
|
||||
const FRET_WIRE_HIT_DECAY = 0.32;
|
||||
// The flash is a one-shot "lightning strike" pulse, not a lingering glow:
|
||||
// a near-instant crack up, then a fast shocked fall with an electric
|
||||
// flicker, then HARD ZERO — a held sustain does not keep the wires lit.
|
||||
const FRET_WIRE_HIT_RISE = 0.025; // s, the crack — near-instant fade-in
|
||||
const FRET_WIRE_HIT_FALL = 0.16; // s, the shock dying out; dark after
|
||||
const FRET_WIRE_HIT_FLICKER_HZ = 26; // shudder rate during the fall
|
||||
const FRET_WIRE_HIT_FLICKER_DEPTH = 0.35; // how deep the shudder bites (0..1)
|
||||
|
||||
const S_BASE = 3 * K;
|
||||
const S_GAP = 4 * K;
|
||||
@ -4784,11 +4790,18 @@
|
||||
const _scrStringAnticipation = new Array(MAX_RENDER_STRINGS).fill(0);
|
||||
const _scrFretHeat = new Array(NFRETS + 1).fill(0);
|
||||
// Fret-wire hit flash. _fwHitIn is per-frame (cleared with the rest of
|
||||
// the frame state, written by drawNote when a provider confirms a note);
|
||||
// _fwHitGlow persists across frames so the flash can decay smoothly
|
||||
// rather than snapping off the frame the provider goes quiet.
|
||||
// the frame state, written by drawNote when a provider confirms a note).
|
||||
// A RISING EDGE on it triggers a one-shot pulse: _fwPulseT0 records the
|
||||
// strike's chart time, _fwPulseAmp its amplitude, _fwPrevIn detects the
|
||||
// edge (a held 'active' verdict keeps the input high continuously, so a
|
||||
// sustain triggers exactly one pulse — the wire goes dark while the
|
||||
// note rings on). _fwHitGlow holds each wire's evaluated envelope value
|
||||
// this frame, for the outer-pair selection below.
|
||||
const _fwHitIn = new Float32Array(NFRETS + 1);
|
||||
const _fwHitGlow = new Float32Array(NFRETS + 1);
|
||||
const _fwPulseT0 = new Float32Array(NFRETS + 1).fill(-Infinity);
|
||||
const _fwPulseAmp = new Float32Array(NFRETS + 1);
|
||||
const _fwPrevIn = new Float32Array(NFRETS + 1);
|
||||
// Per-frame chord accumulator. A chord flashes only the OUTERMOST wires
|
||||
// of its shape, but drawNote() sees one chord note at a time and can't
|
||||
// know the span — so hits accumulate here keyed by chord, and the flash
|
||||
@ -12700,23 +12713,47 @@
|
||||
}
|
||||
|
||||
const _fwDt = now - _fwHitPrevTime;
|
||||
if (!(_fwDt >= 0) || _fwDt > 1) _fwHitGlow.fill(0); // first frame, seek, or long stall
|
||||
const _fwDecay = (_fwDt > 0 && _fwDt <= 1)
|
||||
? Math.exp(-_fwDt / FRET_WIRE_HIT_DECAY)
|
||||
: 0;
|
||||
if (!(_fwDt >= 0) || _fwDt > 1) { // first frame, seek, or long stall
|
||||
_fwHitGlow.fill(0);
|
||||
_fwPulseT0.fill(-Infinity);
|
||||
_fwPulseAmp.fill(0);
|
||||
_fwPrevIn.fill(0);
|
||||
}
|
||||
_fwHitPrevTime = now;
|
||||
// Decay EVERY wire's glow state, but flash only the OUTERMOST
|
||||
// pair of lit wires. Fast passages overlap their decay tails, so
|
||||
// without this a run of consecutive notes lights a picket fence
|
||||
// of wires at once; collapsing to the outer pair keeps the whole
|
||||
// lit span reading as ONE bracket — the same rule chords already
|
||||
// follow, applied across everything currently glowing. Interior
|
||||
// wires keep decaying invisibly (the base tier loop re-seeds
|
||||
// their materials each frame), so the bracket tightens naturally
|
||||
// as outer tails expire.
|
||||
// Evaluate every wire's pulse, but flash only the OUTERMOST pair
|
||||
// of lit wires (one bracket, never a picket fence — the chord
|
||||
// rule applied across everything currently pulsing). The pulse
|
||||
// itself is the lightning strike: a rising edge on the input
|
||||
// triggers it, it cracks up over RISE, falls over FALL with a
|
||||
// high-rate flicker biting into it (the shock), and is then
|
||||
// hard-zero — a held sustain keeps the input high but triggers
|
||||
// nothing new, so the wires go dark while the note rings.
|
||||
const _fwSpan = FRET_WIRE_HIT_RISE + FRET_WIRE_HIT_FALL;
|
||||
let _fwLo = -1, _fwHi = -1;
|
||||
for (let _f = 0; _f <= NFRETS; _f++) {
|
||||
const _g = Math.max(_fwHitIn[_f], _fwHitGlow[_f] * _fwDecay);
|
||||
const _in = _fwHitIn[_f];
|
||||
if (_in > 0.004 && _fwPrevIn[_f] <= 0.004) {
|
||||
_fwPulseT0[_f] = now; // strike
|
||||
_fwPulseAmp[_f] = _in;
|
||||
}
|
||||
_fwPrevIn[_f] = _in;
|
||||
let _g = 0;
|
||||
const _pt = now - _fwPulseT0[_f];
|
||||
if (_pt >= 0 && _pt < _fwSpan) {
|
||||
if (_pt < FRET_WIRE_HIT_RISE) {
|
||||
_g = _pt / FRET_WIRE_HIT_RISE;
|
||||
} else {
|
||||
const _u = (_pt - FRET_WIRE_HIT_RISE) / FRET_WIRE_HIT_FALL;
|
||||
_g = (1 - _u) * (1 - _u); // fast drop, easing out
|
||||
// Electric shudder during the fall only — the crack
|
||||
// itself stays clean.
|
||||
_g *= 1 - FRET_WIRE_HIT_FLICKER_DEPTH
|
||||
* (0.5 + 0.5 * Math.sin(_pt * 6.2832 * FRET_WIRE_HIT_FLICKER_HZ));
|
||||
}
|
||||
_g *= _fwPulseAmp[_f];
|
||||
} else if (_pt < 0) {
|
||||
_fwPulseT0[_f] = -Infinity; // seek landed before the strike
|
||||
}
|
||||
_fwHitGlow[_f] = _g;
|
||||
if (_g < 0.004) continue; // below perceptible
|
||||
if (_fwLo < 0) _fwLo = _f;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user