Closes the launch guardrail: the field's ambient orbit + force-sim drift
run >5s, so WCAG needs a persistent pause control and prefers-reduced-
motion must be honored.
- engine.setPaused(): freezes the deterministic clock so the orbit + sim
drift hold still, while the frame still renders and the live preFrameHook
still runs — discrete event pulses (firewall, decay, dream) are
information, not decoration, so they land even when motion is reduced.
- ObservatoryStage: a persistent ❚❚ PAUSE / ▶ RESUME control on the live
field; auto-pauses under prefers-reduced-motion (user can override).
Verified live: the pause button toggles engine.isPaused (frozen drift,
pulses still land); check 0 err, 1043 tests, build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The dead routes were flat purple. This adds ONE cheap WebGPU substrate
behind them whose drive is REAL backend metrics, so it legibly READS the
data instead of being decoration — a store with 129 endangered memories
storms; 0 contradictions is a calm field (the discipline test still holds).
- AmbientField.svelte + ambient-field.wgsl: a metric-bound mote field.
Each mote's vertical drift IS its retention (low sinks to the forgetting
floor, high floats up); the real endangered fraction storms turbulence +
the sink split; contradiction fraction tears rifts; due-for-review
pulses it. Deterministic per mote (no Math.random in the loop).
- Guardrails: no WebGPU / null adapter / context throw -> renders nothing
(page background shows, never a black canvas). prefers-reduced-motion
freezes drift but keeps the metric snapshot. Page-Visibility +
IntersectionObserver gate the rAF loop. DPR clamped, mobile-first budget.
- Wired to the two highest-signal routes: /stats (endangered + due) and
/contradictions (fracture). Reusable — any remaining route adopts it with
one import + real metric props.
Verified live: /stats renders the field reading the retention distribution
(dense healthy top, endangered embers); /contradictions shows a calm
crimson field at 0 contradictions (fracture=0). check 0 err, 1043 tests,
build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A real recall lights the backward CAUSE chain through the graph — every
hop after the origin follows a real ConnectionType::Causal edge, not mere
co-occurrence. This is the conjunction no competitor has: RSB's directed
causation + the causal edge type + per-memory FSRS + the raw-WebGPU engine.
- pathfinder: isCausalEdge() + a preferCausal walk option (defaults false,
so Memory Cinema is byte-identical). When set, the greedy walk prefers
real causal edges every step, tracing true causation.
- path-builder: buildRecallPath gains preferCausal + centerId; causal hops
(detected on viaEdge.type, keeping the shared Cinema beat-kind union and
the protected narrator untouched) map to PATH_KIND.backwardCause — the
magenta backward rim in render-path.wgsl.
- live-bridge: a real DeepReferenceCompleted (or BackfillFired) whose
primary is in-field rebuilds the PathStep buffer centered on the real
recalled memory, preferring causal edges, and rides the proven recall
wavefront machinery. Honest scope: only lights when the recalled memory
is actually on screen (never invents a path).
- 2 new tests: the pathfinder picks the real cause over the strongest
co-occurrence; a live DeepReferenceCompleted arms the causal wavefront.
Verified live: injected a real in-field DeepReferenceCompleted → the
bridge armed causalRecall for the real primary + set an 8-step path whose
7 post-origin hops are ALL kind-1 (backward-cause) following real causal
edges. 1043 tests, check + build green. Cinema untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When the real dream pipeline runs, the field enters a metabolic storm:
damping loosens, the newly-discovered edges are appended live, and their
springs pull the clusters into their new shape.
- node-renderer: setEdges() — a setPathSteps clone that regrows the edge
index buffer, updates the CPU graph, bumps edge_count, rebuilds the sim
pipeline so new springs act immediately.
- simulate.wgsl: while live_kind==dreamStorm, damping loosens with
live_energy + a deterministic turbulence rides the envelope (pure of
node index + live_frame, so no wall clock).
- live-bridge: real ConnectionDiscovered pairs append to a deduped edge
accumulator, coalesced to ONE setEdges per frame (a 1000-edge burst is
one regrow, not a thousand). Storm fires on DreamStarted OR
DreamCompleted (a real dream floods the 200-event ring and evicts
DreamStarted, so Completed must also arm it), holds ~6s, settles.
- ingest rewrite: timestamp watermark instead of object-identity dedup —
robust to ring eviction AND browser/backend clock skew (seeds from the
backend clock, never the browser wall clock). This fixed a real bug
where a 36s clock skew silently dropped every event.
- 8 new LiveBridge unit tests machine-prove decay/firewall/storm/edges
(the multi-second storm can't be screenshotted in a frozen-rAF preview).
Verified: firewall re-fired live post-rewrite naming the real memory;
1041 tests (8 new), check + build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The crimson quarantine firewall was hard-locked to the deterministic demo
clock. Now it fires on real cognitive events — a MemorySuppressed or a
DeepReferenceCompleted contradiction pair — targeting the REAL memory and
its REAL graph neighbors, on camera.
- firewall-plan: buildLiveFirewallPlan(graph, seed, intruderIndex) reuses
all the deterministic machinery (real-layout shock delays, real severed
neighbors, spine, verdict) but takes the intruder from the live event.
- firewall.wgsl + render-nodes.wgsl: dual-gate on demo_id==4 OR
live_kind==1, driven by live_frame (frames since the event) so the
720-frame beat map plays once from the event, not the loop clock.
- firewall-renderer: rearm(plan) rebuilds the fire buffer for a live
target; compute() gates on demo OR live.
- live-bridge: lazily builds + rearms a live FirewallRenderer on a
firewall event; onFirewall callback surfaces the verdict.
- ObservatoryStage: crimson "threat quarantined" verdict card naming the
real memory, auto-clears after the choreography.
- live lane [13] repurposed liveStartFrame -> liveFrame (event-relative).
Verified live: fired 3 real suppressions on the 1241-memory brain — each
raised the crimson verdict naming the real memory ("ProtocolGate Lane 4
design decision", cascade ~54), the field ran the quarantine, the forget
counter incremented 129->132, then all 3 reversed cleanly. check 0 err,
1033 tests, build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The raw-WebGPU field was inert — it ran the deterministic DemoClock and
never heard the backend. This wires it to the real event stream and makes
every memory visibly forget on its true FSRS curve.
- LiveBridge (new): lock-free per-frame drain via engine.preFrameHook,
ingests $eventFeed oldest-first, decodes real events into live lanes.
- fsrs.ts (new): exact TS port of retrievability_with_decay (w20=0.1542).
- params 12→16 floats (capture_mode + 4 live lanes); all 8 WGSL Params
structs updated in lockstep. _pad renamed to its real meaning.
- node-renderer: live-retention GPU buffer (binding 4); simulate.wgsl
overwrites vel_retention.w so render-nodes dims on the real curve.
- ObservatoryStage: `live` prop, $eventFeed subscription, persistent
FORGETTING HORIZON scrubber (project the curve forward N days, honest).
- backend: get_graph node json now carries stability + lastAccessed.
Verified live vs the real 1241-memory brain: +300d scrub visibly decays
low-stability nodes while high-stability survive; per-node FSRS math
confirmed. svelte-check 0 err, 1033 tests, build, cargo check all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The living field is now interactive, killing Classic's main reason to
exist. Clicking a node in the WebGPU field opens the same Memory Detail
inspector (content, FSRS bars, retention forecast, promote/demote,
explore) that Classic's Three.js picking drove.
How: NodeRenderer.pickAt(ndcX, ndcY) — the one sanctioned GPU readback,
input-driven and click-frequency only, so the render loop's determinism
contract (spec §6) holds. It copies the live NodeState buffer (post
force-sim positions) to a MAP_READ staging buffer, reprojects every node
on the CPU through the SAME deterministic orbit camera the frame used
(engine params phase + canvas aspect, column-major viewProj), and picks
the nearest node whose projected disc (r·f/w with a floor for faint
nodes, 1.6x halo slop) contains the click. NodeState gains COPY_SRC for
the copy; a fresh staging buffer per click is created and destroyed.
ObservatoryStage gains an onpick prop wired to the canvas layer
(crosshair cursor when interactive); the graph page passes onNodeSelect
so field picks open the existing panel.
Verified live: click at field center hit a real memory, panel opened
with real FSRS data (retention 65.4%, forecast 65%->37% @30d), zero
console errors. svelte-check 940 files 0/0; 96 observatory tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The main graph page defaults to the Observatory engine — GPU force sim,
HDR bloom, and the recall wavefront sweeping the REAL memory graph on a
12s loop — instead of the flat Three.js view. Visuals to the max, per
the locked ALIVE-field direction.
- ObservatoryStage gains embedded (fills parent, not viewport) and
chrome ('full' | 'none') props; chrome='none' is a pure living canvas
plus loading/error text, so the page's own DOM chrome stays in charge.
- New Field | Classic renderer toggle in the control bar. Field is the
default wherever WebGPU exists; no WebGPU forces classic. Choice
persists in localStorage.
- Classic (Three.js) remains one click away and untouched: node picking,
colour modes, node count, brightness, temporal scrubbing and legends
are classic-renderer features and now only render there. Dream,
Observatory takeover, Memory Cinema (protected) and Reload work in
both modes.
Verified live: fresh load defaults to Field (148 real nodes, 2865 real
edges, recall ribbon igniting on camera, zero console errors); Classic
toggle restores search/colour/brightness with a single canvas; the
choice round-trips through localStorage. svelte-check 940 files 0
errors 0 warnings.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Observatory is no longer a separate URL you have to know about. It
is now a first-class part of the main graph page, following the proven
Memory Cinema pattern (which remains untouched per the protected-block
rule):
- The full experience is factored into a reusable ObservatoryStage
component (engine, plans, overlays moved verbatim from the route).
- The graph control bar gains an Observatory button beside Dream and
Memory Cinema; clicking it takes over the screen full-bleed (z-50).
- Inside the stage: an on-screen switcher for the 5 cognitive moments
(RECALL / BIRTH / RESCUE / HORIZON / FIREWALL) and an EXIT control
(Esc works too) that returns to the graph exactly as it was. Both are
hidden in capture mode so recordings stay chrome-free.
- {#key demo} remounts the stage per moment: fresh engine, plans, and
clock, deterministic from frame 0.
- The /observatory route stays as a thin wrapper so deep links and the
?frame=N / ?capture=1 recording contract are unchanged; switching a
moment there rewrites the URL so links stay shareable.
Verified live in the running dashboard: button renders between Dream
and Cinema, click boots the WebGPU takeover (148 real nodes, 2865
edges), RESCUE chip switches the moment (telemetry: SALIENCE-RESCUE),
EXIT unmounts cleanly back to the graph (canvas count 2 -> 1).
svelte-check 940 files 0 errors 0 warnings; 96 observatory tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two demo-readiness fixes so the Observatory reads as a living brain
doing real work, not a sparse placeholder:
1. Graph query switched from sort=recent to sort=connected (max_nodes
200). 'recent' centered on the newest memory, a lonely node whose
depth-3 neighborhood is only ~12 nodes, so the field looked empty.
'connected' surfaces the populous hotspot: ~148 real memories and
~2865 real edges, laid out as two dense clusters. Verified live on
recall-path and salience-rescue: 148 nodes / 2865 edges at 110-119fps.
2. Verdict card now holds. The 'root cause found' card faded fully out
by frame 660 in a 720-frame (12s) loop, so it flashed for ~1s then
vanished. Widened the default fade window to [600,620,705,719]: it
fades in, holds fully readable through the field's decay to rest, and
only fades out just before the seamless loop wrap. ~1.75s of readable
verdict instead of a blink. GPU envelopes and the seam-zero loop test
are unaffected (DOM overlay window is independent of the sim).
Verified live against the real brain: 96 observatory tests green, zero
console errors, verdict readable at frames 660 and 695 (both blank before).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Observatory is a full-bleed, zero-library WebGPU surface that renders
the memory graph as a living cognitive field. Five deterministic demo
moments driven by a URL contract (?demo=<name>&seed=...&frame=N):
recall-path, engram-birth, salience-rescue, forgetting-horizon, firewall.
Capture mode (?frame=N) freezes the sim deterministically so the same URL
produces identical pixels, the viral-clip primitive.
Architecture: bare-metal WebGPU engine (no Three.js), seeded demo clock,
per-demo plan + renderer modules, WGSL shaders (simulate, nodes, edges,
path, birth particles, rescue, forgetting, firewall) plus a post-processing
chain (tone mapping, MIP). DOM is instrument overlays only (telemetry strip,
timeline spine, rescue verdict); the layout gives /observatory the same
full-bleed bypass as marketing routes so recordings stay clean.
Reads the real memory graph. Verified live: svelte-check 939 files 0 errors,
96 observatory unit tests green, all 5 demos load at 108-119fps with zero
console errors against the live brain.
Known follow-up: engram-birth capture-mode particle cluster needs a render
fix pass before it is camera-ready; the other 4 demos are camera-ready.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The /duplicates, /contradictions, and /patterns routes shipped hardcoded mock
data — and /duplicates rendered a "Live" badge over it. That is a launch
credibility risk: a visitor who spots the canned data distrusts every real
claim (including the CauseBench benchmark).
Replaces the three mocks (mockFetchDuplicates, MOCK_CONTRADICTIONS,
mockFetchCrossProject) with real dashboard HTTP endpoints backed by the existing
core capabilities:
- GET /api/duplicates -> dedup cluster detection
- GET /api/contradictions -> trust-weighted contradiction analysis
- GET /api/patterns/cross-project-> cross-project pattern transfer
Adds the handlers (dashboard/handlers.rs), route registrations (dashboard/mod.rs),
frontend api client methods + response types, and rewrites the three routes to
fetch live. The "Live" badge is now truthful.
Verified this session:
- cargo build -p vestige-mcp: compiles
- pnpm --filter @vestige/dashboard check: 905 files, 0 errors, 0 warnings
- live backend on a 1073-memory DB returns real rows from all three endpoints
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A full multi-agent review found 7 real issues (4 blockers). All fixed + tested.
B1 (blocker): Promoting a Memory PR did not release the quarantined memory —
the UI said "promoted" while the memory stayed suppressed/out of retrieval.
act_on_memory_pr now calls reverse_suppression(subject_id) on accept actions;
MemoryPrAction::releases_memory() encodes the rule (promote/merge/supersede
release; forget/quarantine keep it held). Proven live: PR response
subjectReleased:true, SQLite suppression_count 0.
B2 (blocker): memory promote/demote (returns `action`, not `decision`) and
codebase remember_* writes bypassed the write-trace + PR gate. extract_writes
now reads `action` too, filtered by is_write_decision (reads like get/state
excluded); is_write_tool includes `codebase`.
B3 (blocker): receipt ids collided within a run (r_<date>_<runId> +
INSERT OR REPLACE overwrote earlier receipts). IDs are now
r_<date>_<runId8>_<unique6>; build() mints the suffix, build_with_unique()
keeps tests deterministic.
B4 (blocker): proof bundle was assembled from two runs (trace.json=run_proof,
websocket-events.jsonl=run_proof2). Re-captured the whole bundle from a single
run — trace, websocket, receipt, and memory_pr all carry run_proof now.
B5: Black Box receipts panel showed global latest, not the selected run.
Added list_receipts_for_run + /api/receipts?run= ; the page uses listForRun.
B6: SENSITIVE_TOPICS substring matching false-fired (tokenizer->token,
author->auth, secretary->secret). Switched to word-boundary matching; real
phrasings (auth token, security vulnerability, api key) still gate.
B7: set_review_mode now writes atomically (temp+rename via write_atomic);
export_trace sanitizes run_id in the Content-Disposition filename; memory-prs
static routes declared before the dynamic /{id} route.
Withdrawn: the /mode-vs-/{id} route order is NOT a functional bug (axum 0.8 /
matchit prioritizes static segments) — reordered for clarity only.
Gates: 999 lib tests pass (+9 new regressions), clippy -D warnings clean,
dashboard check + build clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the receipt chain impossible to doubt. Freeze the claim surface, prove
every hop, and turn the two off-by-default producers into explicit UI states.
Frozen public claim: "Vestige records real MCP memory activity into a
replayable local trace, with receipts and reviewable risky writes." We do NOT
claim Sanhedrin vetoes or dream patches are live by default.
Regression — full-spine test (server.rs): one runId must cross, byte-identical,
MCP output -> SQLite trace -> WebSocket event -> API response shape ->
MCP resource. Fails if any hop drops or rewrites the id.
Honest UI states (Black Box "Event producers" panel):
- sanhedrin.veto -> "No veto producer connected (optional Sanhedrin hook, off
by default)" instead of empty mystery.
- dream.patch -> "No dream run in this trace" unless a dream actually ran.
- contradiction.detected -> "no contradiction in this run" when none fired.
Quarantine review (not pre-write blocking): risky writes are committed then
suppressed — audit history preserved, retrieval influence suspended until
reviewed. Reworded the server notice + UI copy to say exactly that.
Receipts UI gap closed: ReceiptCard is now mounted on the Black Box page
(retrieved/suppressed/trust-floor, activation path, "Open receipt in Cinema").
Proof pack (blackbox-proof-2026-06-22/): status.json, trace.json (the
.vestige-trace.json export), receipt.json, memory_pr.json (promoted via
UI->API->SQLite), websocket-events.jsonl (live TraceEvent x6 + PR opened/
decided), screenshots (Black Box, Receipts, Memory PRs, Graph), and PROOF.md
with real/caveat/stub per feature.
Gates: 988 lib tests pass, clippy -D warnings clean, dashboard check + build
clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The loop fired requestAnimationFrame immediately and never awaited render(); with
150k particles + compute + bloom a frame can exceed 16ms, so rAF callbacks queued
faster than the GPU could drain them and the camera/parallax visibly lagged behind
the cursor. Now AWAIT sb.render(dt) before scheduling the next frame → the loop is
capped to real GPU throughput, every frame reflects the latest pointer position,
no backlog. Also snappier active-steer damping (lam 3.5→9, ~110ms converge) so
input feels immediate; idle glide-home unchanged. renderFailures resets on success.
Plus docs/MEMORY_CINEMA.md — complete feature reference for the cinema engine.
Gate: svelte-check 0/0, 937 tests, verified live (parallax tracks cursor, no lag).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
near-fade × fog × DOF × seam-fade each multiply a <1 factor; together they were
crushing the figure dark. Raised the fog floor (0.18→0.45) and the color/emissive
glow bases so the stacked attenuation lands in a vivid range. Verified live.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Step 5 — VELOCITY-STRETCH FLYTHROUGH: sandbox derives camera velocity per frame
(one Vector3, zero compute) and pushes view-space apparent velocity to the storm;
flythrough relaxes the camera clamp floor (lerp 30→6) so the camera plunges
inside the shell. Storm stretches each sprite along screen-space velocity via
rotationNode + scaleNode (clamped streak), separate output graph (no extra
positionView read). Defaults 0 → no-op until wired.
Step 6 — INTERACTIVE PARALLAX: pointer orbits / scroll + pinch zoom the camera
with frame-rate-independent damping, composed onto the director's base pose in
loop() (after director.update, before render); idle >2.5s eases back to 0 so it's
a toy when touched and a film when left alone. sandbox.render re-clamps so the
user can't break framing. Per-beat flythrough strength wired from shot.tension;
dream mode flies through at 0.6. Fully gated off under reduced-motion (no
listeners, flythrough 0).
The 4-feature immersion stack (infinite zoom + flythrough + parallax + DOF/fog)
now composes. Gate: svelte-check 0/0, 937 tests, build green, verified live (all
4 compose, no white-out, no recursion, parallax responds without breaking framing).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The cloud now dives inward FOREVER, seamlessly. Two layers ride offset phases of
fract(uTime/T): the outer grows pow(λ, phase) toward the camera then snaps back
(invisible — inner@1/λ == outer@1); the inner (half-period offset) grows promoted
by λ to become the next outer shell, while a fresh inner spawns inside. λ=1.923
(=1/0.52 inner scale) makes the snap mathematically exact. A sin(phase·π) seam
cross-fade in rimFactor makes each layer fully transparent at its snap → ZERO pop.
Particle-space (not a camera dolly) so it can't clip or fight the camera clamp.
Rack-focus tracks the descent. uZoomOn gates it: Act II+ and dream mode dive;
beats 0/1 + reduced-motion stay still. Verified: seamless loop, no white-out, no
recursion, 937 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Off-focus particles dim (read as bokeh defocus under the bloom) with a breathing
rack-focus. Folded into the single depthFade depth read — NOT sprite scaleNode,
which collapsed the sprites to invisible and collides with the upcoming streak.
Subtle (0.3) so it adds cinematic depth without darkening the figure.
Gate: svelte-check 0/0, 937 tests, verified live (depth grading reads, no recursion).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Distant particles dim toward the void with view depth (exp falloff) → real 3D
atmospheric recession, not a flat sprite cloud. Combined with the near-fade into
a SINGLE depthFade Fn — critical three@0.172 TSL constraint discovered: reading
positionView from a second Fn feeding the same material output triggers a cyclic
stack-overflow in the node type-resolver (getNodeType). One depth read, one Fn.
Gate: svelte-check 0/0, 937 tests, verified live (fog depth reads, no recursion).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Particles dissolve as they approach the camera (view-space -positionView.z,
smoothstep over [near, near+band]) so the upcoming flythrough never additive-pops
a sprite in your face. Folded into color + emissive so the bloom fades too.
Invisible at the default far camera. positionView confirmed working in the
SpriteNodeMaterial color node.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SpriteNodeMaterial.setupPositionView already rebuilds the billboard quad from
positionGeometry — the prior .add(positionLocal) double-counted it (harmless at
0.1 size). Bare center is required for the upcoming velocity-stretch streak
(scaleNode/rotationNode will drive the quad). Verified renders identically.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The nested 3D-within-3D figure now collides with its shell in OPPOSING color
universes, not a shared rainbow. Each layer is painted from a hard duotone:
the outer shell from one world (ice / acid / gold / mint / electric-blue), the
inner figure from its enemy (fire / blood / violet / crimson / gold). A new
uClash uniform cycles the pair every beat (and randomizes per dream figure), so
it's a fresh ice-vs-fire / acid-vs-blood collision each time — the kind of
contrast that stops a scroll.
To make the clash READ instead of washing white: inner glow floor dropped hard
(dense small-radius overlap was blowing to white and killing the color), inner
figure scaled up to 0.52 (spread → less overlap), the color blast capped at 0.6
mix so the duotone shows through even during a detonation, and Act II/III
ignition lowered 8.0→4.5 so beats no longer flash the clash to white.
Gate: svelte-check 0/0, 937/937 tests pass, build green, verified live (gold
shell + violet core clash reads clearly, no white-out, beats 0/1 calm).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three upgrades to Memory Cinema:
1. IMPOSSIBLE-GEOMETRY FORM PACK — replaced the stringy ribbon dream worlds with
brand-new signature skins nobody ships as a living particle figure:
- world 8 → Calabi–Yau quintic cross-section (6D string-theory manifold,
Hanson 4D→3D projection; α rotates it through the 4th dimension)
- world 9 → Boy's surface (Bryant–Kusner minimal immersion of RP²)
- world 10 → Aizawa attractor shell (breathing strange-attractor skin)
- world 11 → Gyroid↔Schwarz-D Bonnet morph (triply-periodic minimal surface)
The (u,v) MANIFOLD GRID basis is the key fix: particles map over a tensor grid
so neighbors share edges → reads as a sculpted SKIN, not spaghetti. Plus a
facing-ratio Fresnel rim so forms read as lit solids. Inline complex-math +
hyperbolics (sinh/cosh not in three@0.172). atan2→atan (deprecation).
2. 3D-WITHIN-3D NESTING — ~34% of particles form a SECOND, smaller, counter-
rotating figure (a different world, ~45% scale, complementary hue) at the core
of the outer shell. A figure inside a figure — fills the formerly-blank-bright
center with intentional structure and depth.
3. DEMO-CAPTURE MODE — press H in the cinema overlay to hide all UI chrome
(top bar + captions) for clean recording; a faint restore hint remains.
Overlay z-index raised + body.cinema-open hides the graph page's stats pill so
nothing bleeds through.
Gate: svelte-check 0/0, 937/937 tests pass, build green, verified live (Calabi–Yau
+ nested core render, forms cycle, no errors, beats 0/1 calm).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The 7-beat tour no longer freezes on the last figure. When it ends, Memory
Cinema drops into an infinite generative loop: every ~5.5s it morphs into a
fresh RANDOM procedural figure and detonates a color blast — each crazier than
the last.
Five new procedural worlds (7..11), parameterized by a per-figure uMorphSeed +
a uChaos ramp so the same index never looks the same twice:
7 supershape (3D superformula) 8 torus knot (random p,q winding)
9 warped lissajous lattice 10 helix storm
11 quantum foam (curl-warped chaos — max wild)
storm.dreamBeat() picks a random world, reseeds it, ramps chaos, and fires a
moderate-ignition blast (kept below the tour's 8.0 so dense random figures don't
wash white). Surfaced via sandbox.dreamBeat(); MemoryCinema starts a dream timer
on director onComplete, shows "∞ Dreaming", and tears it down on close/replay.
Honors reduced-motion (no dream loop) and the render-fail fallback.
Gate: svelte-check 0/0, 937/937 tests pass, build green, verified live (reaches
dream mode, generates distinct figures — supershapes, torus knots — cycling
forever, no white-out, no errors).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Memory Cinema is now a choreographed 7-act journey. One 150k-particle pool,
one compute kernel; a uWorld state machine select()s which home-target + forces
are live, and uBlend crossfades world→world over ~1s. The particles never swap
— only the forces on them — which IS the journey.
The seven worlds (beats map 1:1, beatIndex % 7):
0 nebula mist (curl-noise flow) 1 orbital anchor (cross-product spin)
2 strange attractor (Thomas) 3 detonation void (staggered shockwave)
4 crystal lattice (voxel snap) 5 fluid galaxy (curl + tangential swirl)
6 phyllotaxis bloom (Vogel sunflower, golden angle)
The signature COLOR BLAST: a long-lived uBlast envelope (~2.8s, decoupled from
the fast physics burst so the color OUTLIVES the shockwave) drives an outward
SPECTRAL DISPERSION wave — concentric rainbow rings expanding through the radius
over uBlastTime (real prism order, red lags / blue leads), with a warm blackbody
ember core underneath. Spectrum dominates so the detonation reads as RAINBOW,
not a white plasma flash.
Plus per-world cosine palettes (IQ) so each world is a distinct PLACE. All the
white-out guardrails preserved + extended: rim-gated blast, capped kelvin/gain,
emissive blast held below the color path. Beats 0/1 stay calm (low uBlast),
Acts II/III blaze.
Frontier techniques sourced from a parallel web-research + design workflow;
verified against the installed three@0.172 TSL build. Retires the old uShape
5-form gallery.
Gate: svelte-check 0/0, 937/937 tests pass, build green, verified live (7
distinct worlds, spectral blast, no white-out, calm opening).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Memory Cinema storm color/shape overhaul (the crown-jewel pillar):
- Fix the white-out root cause: emissiveNode was NEVER set, so the selective
MRT bloom had no color to bloom and washed the frame white. Route the shared
iridescent rainbow to BOTH colorNode and emissiveNode.
- Rim glow (fresnel-style): bright glowing edges, dim readable center — the
shareable luminous-shell / hollow-torus look.
- Morphing geometry: the home target cycles sphere → torus → galaxy spiral →
cube lattice → wave sheet, drifting continuously and snapping per beat.
- Hyper-saturated full-spectrum palette (per-particle phase + radial shells +
spatial bands + time) so the whole rainbow is present at once.
- Spread the initial spawn across a wide hollow shell (was a tiny dense ball
that boot-flashed white).
- Act/beat-aware brightness: beats 0/1 fade in soft, Act I held calm, Acts
II/III blaze at full. No white-out regressions.
Gate: svelte-check 0/0, 937/937 tests pass (cinema auteur/pathfinder green),
verified live in browser.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the dashboard feel alive every second, with clear controls, for the
July 14 HN relaunch. Memory Cinema is left fully untouched (zero changes to
MemoryCinema.svelte / graph/cinema/*; its tests still pass).
Foundation (lifts every page):
- Icon.svelte: inline-SVG icon system, zero runtime dep. A UNIQUE semantic
silhouette per nav item — kills the old duplicated Unicode glyphs (◎◈◉◷
were each reused across multiple items). Wired into sidebar, mobile nav,
command palette, logo.
- Dropdown.svelte: accessible, keyboard-nav, type-ahead, animated select
replacement with color dots / badges. Replaces dead native <select>s.
- AnimatedNumber.svelte: rAF count-up/tween, reduced-motion safe.
- PageHeader.svelte: shared masthead (drawn route icon + aurora title).
- actions/reveal.ts + actions/interactions.ts: scroll-reveal, magnetic,
tilt(+glare), spotlight — all no-op under reduced-motion.
- app.css "alive layer": @property animatable gradients, conic live-border,
breathe/ping, shimmer skeletons, @starting-style entry, aurora text, lift.
Per-page: every route (graph non-cinema controls, reasoning, memories,
timeline, feed, explore, activation, dreams, schedule, importance,
duplicates, contradictions, patterns, intentions, stats, settings) now uses
PageHeader, real Icons, count-ups, staggered reveals, shimmer loaders,
spotlight cards, and warm empty states. Native selects and button-row
filters became clear Dropdowns where it improves clarity.
Gates: svelte-check 0 errors/0 warnings, 937/937 tests pass, build green,
verified live in the browser preview.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per direction: keep the mind-blowing explosion + pixelation moments, ditch the
thin ribbon swirls. Complete physics rewrite:
- removed orbital/stream/Rössler modes (the swirls + the off-center drift source)
- each particle has a deterministic HOME on a volumetric shell around ORIGIN
(centroid anchored — can never drift off-frame again)
- uBurst detonation cycle: every beat blows particles radially out (explosion),
then a home-spring crystallizes them back (reform); contradictions detonate hardest
- PIXELATION: positions snap to a 3D grid that's fine when reformed, dissolved
during the burst — the crystalline voxel look
- hard velocity + radius clamps so it can never fly off or blow up
937 tests + build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Storm was a small ring leaving the canvas empty, and the core blew to white.
- FILL: sandbox fitRadius margin 0.40 -> 0.82 so the storm fills most of the
frame; particles now target their OWN radius across 0.12r..0.92r (filled
volumetric ORB, not a thin ring).
- COLOR: brightness was x(ignition*2.4+0.6) = up to x19.8, which + additive
blending across 150k sprites clipped every channel to white. Clamp the glow
low (0.45 floor, ~1.15 ceil) so the RAINBOW shows as pure spectral color;
smaller quads (0.18 -> 0.1) keep particles crisp instead of overlapping to
mush; gentler bloom (strength 1.1->0.6, threshold 0->0.35) accents cores
rather than washing the cloud. 937 tests + build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The rainbow storm looked next-dimensional but still clipped the edges — the
additive bloom halo extends each particle's glow well past its geometric radius,
so the visible cloud was bigger than the contain sphere.
- spawn radius 15 -> 8 (particles start inside the shell, no asymmetric inward yank)
- sandbox fitRadius margin 0.55 -> 0.40 (leaves room for the bloom halo)
- camera band tightened + pushed farther (30-44) so the contained cloud sits
small + centered; director standoff clamped into that band in centerOnOrigin
mode so the camera never fights the per-frame clamp (the off-center jump).
937 tests + build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes the runaway ring (screenshot showed an expanding ellipse clipping the
frame): orbital mode added tangential velocity with nothing pulling particles to
a target radius, so they spiraled outward. Now a two-sided RADIAL SPRING pulls
every particle toward an in-frame shell (containRadius*0.62) with a per-particle
band so the cloud is a contained breathing sphere, not an ever-growing ring.
Tighter velocity clamp + boundary snap as belt-and-suspenders.
Color: replaced the flat 3-color tint with a living iridescent RAINBOW — hue
drifts by per-particle phase + radius + time + a global rotating hue shift
(fract/abs hexagon palette). Dramatic beats blend their mode color over the
rainbow (crimson at contradictions, gold at surprises) via uModeTintAmt; calm
beats stay mostly rainbow. 937 tests + build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause: layoutPositions grew per beat (radius 22 + i*6), so each beat sat
farther out; the camera + storm marched off into space as the tour progressed.
Fix (centered-by-construction):
- layoutPositions: tight BOUNDED golden-angle shell (SHELL_RADIUS 14), no growth.
- sandbox: storm pinned to the WORLD ORIGIN permanently; camera hard-clamped to
an 18-46 unit distance band and always lookAt(origin); containment sphere
sized to the FOV at origin. A runaway move is corrected every frame.
- director: new centerOnOrigin mode (enabled when WebGPU active) — frames/orbits
the origin instead of flying to scattered nodes; variety from angle/standoff.
No path remains for the subject to leave frame. 937 tests + build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
director.ts: optional shots:ResolvedShot[] in DirectorOptions; per-beat
flight/dwell timing; framePosition now reads move (push_in/pull_back/crane
scale standoff) + angle (low=look-up, high=look-down) + standoff; orbit shots
revolve the camera during dwell; Dutch roll via camera.up; hard/match cuts
snap (editorial cut). With NO shots the camera is byte-identical to before
(all values fall back to the existing constants + easeInOutCubic lerp).
MemoryCinema.svelte: build computeSignals + planShotsDeterministic + resolveShots
on launch, pass shots to the director; onBeat drives storm mode + director's
note + Act + tension from the shot. New UI: pre-roll DIRECTOR'S PLAN card
(logline naming real memories), per-beat 'why this shot' note, Act I/II/III
badge, tension-tinted progress bar, Auteur source badge.
The deterministic auteur ships the hero film with zero LLM. 937 tests + build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Particles (esp. the unbounded Rössler chaos mode) could fly off-screen. Add a
camera-frame-sized spherical containment field: spring pull-back past the
radius, hard velocity clamp, and a snap-to-shell safety net so no particle can
escape. The sandbox sizes the radius from camera distance + vfov each frame so
the storm reframes as the camera flies. Verified: check + build green.
storm.ts (blockers): correct particle position wiring — positionNode now
instancePos.add(positionLocal) (bare storage element collapsed every quad to a
point); serialize GPU compute dispatches (computeInFlight) to stop queue
stalls; ignition floor so the storm never fades to black; null buffers on
dispose for GC (StorageBufferAttribute has no dispose()); typed computeNode +
InstancedMesh, removed unsafe casts.
narrator.ts: validate + filter backend beats, bounds-safe fallback merge,
KIND_CHIP satisfies (compile-time enum coverage), chip type guard, timer cleanup.
MemoryCinema.svelte: replace the null-returning Local AI stub with a real
on-device transformers.js text-generation pipeline (+ genuine fallback);
Escape-to-close + autofocus a11y; reset all run state on launch (no stale
Replay); fix render/close race; computed fallback camera aspect; typed state.
director.ts: NaN-guard progress on empty path; clamp dt >= 0.
sandbox.ts: guard three/webgpu exports + tsl pass() API shape; resize w/h floor.
926 tests + build green. Net: every audit blocker/high/medium fixed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Disable camera auto-rotate (the dominant continuous motion) when the OS
reduce-motion setting is on; live-toggle aware via matchMedia change listener,
cleaned up on destroy. Graph stays fully usable (manual orbit/hover/select/live
events). Closes the a11y gap where 0 of ~3,200 graph LOC honoured the setting.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Native View Transitions API via onNavigate (feature-detected, reduced-motion safe)
- OKLCH + display-p3 accent palette with hex fallback (@supports progressive enhancement)
- WebSocket gains 'reconnecting' state so stale errors clear on reconnect
- Graph control bar wraps + safe-area insets for <640px / notched phones
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The github-pages deployment showed a red X: actions/configure-pages@v5
ran with enablement:true, but Pages was never enabled in repo settings and
the default GITHUB_TOKEN cannot create a Pages site, so deploy failed with
"Resource not accessible by integration". The launch-kit revert then deleted
the workflow entirely, leaving nothing to deploy.
- Restore a modernized pages.yml (Pages now enabled via API, so no
enablement hack; actions/checkout@v5 + Node 24 off the deprecated Node 20).
- Make the dashboard base path env-driven (VESTIGE_BASE_PATH), defaulting to
/dashboard for local/embedded use and overridden to /vestige in CI so
assets resolve at the Pages project subpath instead of 404ing.
- Workflow builds the dashboard under /vestige and writes a root-level
redirect index.html so the bare Pages URL lands on the dashboard.
- Rebuild the committed dashboard artifact (was stale at 2.1.23) to 2.1.27.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump all manifests 2.1.26 → 2.1.27 and date the CHANGELOG entry for the
GitHub + Redmine connector layer and source-aware search filters (#57, PR #78).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rebased on v2.1.25 merge/supersede and bumped the post-release metadata to v2.1.26 so this branch does not roll versions backward.
Adds local vestige.toml defaults, output profiles, and MCP response precedence for search, timeline, codebase context, and session context.
Verified:
- cargo metadata --format-version 1 --locked --no-deps
- cargo test -p vestige-core config --no-fail-fast
- cargo test -p vestige-mcp config --no-fail-fast