mirror of
https://github.com/samvallad33/vestige.git
synced 2026-05-01 03:46:22 +02:00
fix(v2.3): 5 FATAL bugs + 4 god-tier upgrades from post-ship audit
Post-ship audit surfaced 6 FATALs and 4 upgrades. Shipping 5 of the 6 +
all 4 upgrades. FATAL 4 (VRAM hemorrhage from un-pooled label canvases
in createTextSprite) is pre-existing, not from this session, and scoped
separately for a proper texture-pool refactor.
**FATAL 1 — Toast Silent Lobotomy** (stores/toast.ts)
Subscriber tracked events[0] only. When Svelte batched multiple events
in one update tick (swarm firing DreamCompleted + ConnectionDiscovered
within the same millisecond), every event but the newest got silently
dropped. Fixed to walk from index 0 until hitting lastSeen — same
pattern as Graph3D.processEvents. Processes oldest-first to preserve
narrative order.
**FATAL 2 — Premature Birth** (graph/nodes.ts + graph/events.ts)
Orb flight is 138 frames; materialization was 30 frames. Node popped
fully grown ~100 frames before orb arrived — cheap UI glitch instead
of a biological birth. Added `addNode(..., { isBirthRitual: true })`
option that reserves the physics slot but hides mesh/glow/label and
skips the materializing queue. New `igniteNode(id)` flips visibility
and enqueues materialization. events.ts onArrive now calls igniteNode
at the exact docking moment, so the elastic spring-up peaks on impact.
**FATAL 3 — 120Hz ProMotion Time-Bomb** (components/Graph3D.svelte)
All physics + effect counters are frame-based. On a 120Hz display every
ritual ran at 2x speed. Added a `lastTime`-based governor in animate()
that early-returns if dt < 16ms, clamping effective rate to ~60fps.
`- (dt % 16)` carry avoids long-term drift. Zero API changes; tonight's
fast fix until physics is rewritten to use dt.
**FATAL 5 — Bezier GC Panic** (graph/effects.ts birth-orb update)
Flight phase allocated a new Vector3 (control point) and a new
QuadraticBezierCurve3 every frame per orb. With 3 orbs in flight that's
360 objects/sec for the GC to collect. Rewrote as inline algebraic
evaluation — zero allocations per frame, identical curve.
**FATAL 6 — Phantom Shockwave** (graph/events.ts)
A 166ms setTimeout fired the 2nd shockwave. If the user navigated
away during that window the scene was disposed, the timer still
fired, and .add() on a dead scene threw unhandled rejection. Dropped
the setTimeout entirely; both shockwaves fire immediately in onArrive
with different scales/colors for the same layered-crash feel.
**UPGRADE 1 — Sanhedrin Shatter** (graph/effects.ts birth-orb update)
If getTargetPos() returns undefined AFTER gestation (target node was
deleted mid-ritual — Stop hook sniping a hallucination), the orb
turns blood-red, triggers a violent implosion in place, and skips
the arrival cascade. Cognitive immune system made visible.
**UPGRADE 2 — Newton's Cradle** (graph/events.ts onArrive)
On docking the target mesh's scale gets bumped 1.8×, so the elastic
materialization + force-sim springs physically recoil instead of the
orb landing silently. The graph flinches when an idea is born into it.
**UPGRADE 3 — Hover Panic** (stores/toast.ts + InsightToast.svelte)
Paused dwell timer on mouseenter/focus, resume on mouseleave/blur.
Stored remaining ms at pause so resume schedules a correctly-sized
timer. CSS pairs via `animation-play-state: paused` on the progress
bar. A toast the user is reading no longer dismisses mid-sentence.
**UPGRADE 4 — Event Horizon Guard** (components/Graph3D.svelte)
If >MAX_EVENTS (200) events arrive in one tick, lastProcessedEvent
falls off the end of the array and the walk consumes ALL 200 entries
as "fresh" — GPU meltdown from 200 simultaneous births. Detect the
overflow and drop the batch with a console.warn, advancing the
high-water mark so next frame is normal.
Build + test:
- npm run check: 0 errors, 0 warnings
- npm test: 251/251 pass
- npm run build: clean static build
This commit is contained in:
parent
f40aa2e086
commit
ec614fed85
6 changed files with 242 additions and 52 deletions
|
|
@ -121,9 +121,23 @@
|
|||
if (ctx) disposeScene(ctx);
|
||||
});
|
||||
|
||||
// 120Hz Governor. All physics and effect counters are frame-based
|
||||
// (orb.age++, forceSim.tick, materialization frames). On a ProMotion
|
||||
// display the browser drives rAF at 120 FPS, which would double-speed
|
||||
// every ritual. Clamping to ~60 FPS keeps the visual timing identical
|
||||
// across displays without rewriting every counter to use delta time.
|
||||
// The `- (dt % 16)` carry avoids long-term drift.
|
||||
let govLastTime = 0;
|
||||
|
||||
function animate() {
|
||||
animationId = requestAnimationFrame(animate);
|
||||
const time = performance.now() * 0.001;
|
||||
const now = performance.now();
|
||||
if (govLastTime === 0) govLastTime = now;
|
||||
const dt = now - govLastTime;
|
||||
if (dt < 16) return;
|
||||
govLastTime = now - (dt % 16);
|
||||
|
||||
const time = now * 0.001;
|
||||
|
||||
// Force simulation
|
||||
forceSim.tick(edges);
|
||||
|
|
@ -174,6 +188,20 @@
|
|||
fresh.push(e);
|
||||
}
|
||||
if (fresh.length === 0) return;
|
||||
|
||||
// Event Horizon Guard. If the last-processed reference fell off the
|
||||
// end of the capped array (burst of >MAX_EVENTS events in one tick),
|
||||
// the walk above consumed the ENTIRE buffer — we'd try to animate
|
||||
// 200 simultaneous births and melt the GPU. Detect the overflow and
|
||||
// drop this batch on the floor; state is already current via
|
||||
// lastProcessedEvent pointing forward.
|
||||
if (fresh.length === events.length && events.length >= 200) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('[vestige] Event horizon overflow: dropping visuals for', fresh.length, 'events');
|
||||
lastProcessedEvent = events[0];
|
||||
return;
|
||||
}
|
||||
|
||||
lastProcessedEvent = events[0];
|
||||
|
||||
const mutationCtx: GraphMutationContext = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue