mirror of
https://github.com/samvallad33/vestige.git
synced 2026-04-30 19:36:22 +02:00
feat(v2.3-terrarium): Memory Birth Ritual + event pipeline fix
v2.3 "Terrarium" headline feature. When a MemoryCreated event arrives, a
glowing orb materialises in the cosmic center (camera-relative z=-40),
gestates for ~800ms growing from a tiny spark into a full orb, then arcs
along a dynamic quadratic Bezier curve to the live position of the real
node, and on arrival hands off to the existing RainbowBurst + Shockwave +
RippleWave cascade. The target position is re-resolved every frame so
the force simulation can move the destination during flight without the
orb losing its mark.
**New primitive — EffectManager.createBirthOrb()** (effects.ts):
Accepts a camera, a color, a live target-position getter, and an
arrival callback. Owns a sprite pair (outer halo + inner bright core),
both depthTest:false with renderOrder 999/1000 so the orb is always
visible through the starfield and the graph.
- Gestation phase: easeOutCubic growth + sinusoidal pulse, halo tints
from neutral to event color as the ritual charges.
- Flight phase: QuadraticBezierCurve3 with control point at midpoint
raised on Y by 30 + 15% of orb-to-target distance (shooting-star
arc). Sampled with easeInOutQuad. Orb shrinks ~35% approaching target.
- Arrival: fires onArrive callback once, then fades out over 8 frames
while expanding slightly (energy dispersal).
- Caller's onArrive triggers the burst cascade at arrivePos (NOT the
original spawnPos — the force sim may have moved the target during
the ritual, so we re-read nodeManager.positions on arrival).
- Dispose path integrated with existing EffectManager.dispose().
**Event pipeline fix — Graph3D.processEvents()**:
Previously tracked `processedEventCount` assuming APPEND order, but
websocket.ts PREPENDS new events (index 0) and caps the array at
MAX_EVENTS. Result: only the first MemoryCreated event after page
load fired correctly; subsequent ones reprocessed the oldest entry.
Fixed to walk from index 0 until hitting the last-processed event
by reference identity — correct regardless of array direction or
eviction pressure. Events are then processed oldest-first so causes
precede effects. Found while wiring the v2.3 demo button; would have
manifested as "first orb only" in production.
**Demo trigger** (Settings -> Birth Ritual Preview):
Button that calls websocket.injectEvent() with a synthetic
MemoryCreated event, cycling through node types (fact / concept /
pattern / decision / person / place) to showcase the type-color
mapping. Downstream consumers can't distinguish synthetic from real,
so this drives the full ritual end-to-end. Intended for demo clip
recording for the Wednesday launch.
**Test coverage:**
- events.test.ts now tests the v2.3 birth ritual path: spawns 2+
sprites in the scene immediately, and fires the full arrival
cascade after driving the effects.update() loop past the ritual
duration.
- three-mock.ts extended with Vector3.addVectors, Vector3.applyQuaternion,
Color.multiplyScalar, Quaternion, QuadraticBezierCurve3, Texture,
and Object3D.quaternion/renderOrder so production code runs unaltered
in tests.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm test: 251/251 pass (net +0 from v2.2)
- npm run build: clean adapter-static output
The Sanhedrin Shatter (anti-birth ritual for hallucination veto) needs
server-side event plumbing and is deferred. Ship this as the Wednesday
visual mic-drop.
This commit is contained in:
parent
f01375b815
commit
f40aa2e086
8 changed files with 393 additions and 29 deletions
|
|
@ -59,8 +59,13 @@
|
|||
let nebulaMaterial: THREE.ShaderMaterial;
|
||||
let postStack: PostProcessingStack;
|
||||
|
||||
// Event tracking
|
||||
let processedEventCount = 0;
|
||||
// Event tracking — we track the last-processed event by reference identity
|
||||
// rather than by count, because the WebSocket store PREPENDS new events
|
||||
// at index 0 and CAPS the array at MAX_EVENTS, so a numeric high-water
|
||||
// mark would drift out of alignment (and did for ~3 versions — v2.3
|
||||
// demo uncovered this while trying to fire multiple MemoryCreated events
|
||||
// in sequence).
|
||||
let lastProcessedEvent: VestigeEvent | null = null;
|
||||
|
||||
// Internal tracking: initial nodes + live-added nodes
|
||||
let allNodes: GraphNode[] = [];
|
||||
|
|
@ -157,10 +162,19 @@
|
|||
}
|
||||
|
||||
function processEvents() {
|
||||
if (!events || events.length <= processedEventCount) return;
|
||||
if (!events || events.length === 0) return;
|
||||
|
||||
const newEvents = events.slice(processedEventCount);
|
||||
processedEventCount = events.length;
|
||||
// Walk the feed from newest (index 0) backward until we hit the last
|
||||
// event we already processed. Everything between is fresh. This is
|
||||
// robust against both (a) prepend ordering and (b) the MAX_EVENTS cap
|
||||
// dropping old entries off the tail.
|
||||
const fresh: VestigeEvent[] = [];
|
||||
for (const e of events) {
|
||||
if (e === lastProcessedEvent) break;
|
||||
fresh.push(e);
|
||||
}
|
||||
if (fresh.length === 0) return;
|
||||
lastProcessedEvent = events[0];
|
||||
|
||||
const mutationCtx: GraphMutationContext = {
|
||||
effects,
|
||||
|
|
@ -180,8 +194,11 @@
|
|||
},
|
||||
};
|
||||
|
||||
for (const event of newEvents) {
|
||||
mapEventToEffects(event, mutationCtx, allNodes);
|
||||
// Process oldest-first so cause precedes effect (e.g. MemoryCreated
|
||||
// fires before a ConnectionDiscovered that references the new node).
|
||||
// `fresh` is newest-first from the walk above, so iterate reversed.
|
||||
for (let i = fresh.length - 1; i >= 0; i--) {
|
||||
mapEventToEffects(fresh[i], mutationCtx, allNodes);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue