Particle effects, physics impacts & VR/collider polish - #53
Merged
Conversation
… (PFX-A)
- particleShader/particleRuntime: deterministic STATELESS particles - every
particle is a closed-form function of (per-particle hash, config, synced
clock) evaluated in the vertex shader; no integration, no netcode for
continuous emission; hour-wrapped clock for float precision; one
THREE.Points per emitter under scene-root particle-root (never in
sceneObjects/GLTF sync); soundRuntime-style Map-diff lifecycle; procedural
sprite textures (dot/streak/puff/star/square); local vs world sim space
(world = per-rebirth aOrigin stamping, trails behind moving objects)
- particlePresets: Core 6 (Sparkles, Fire, Smoke, Dust puff, Confetti,
Sparks) over one schema; particleActions = replicating mutators
(userData.particles via objectParameters, props undo entries)
- burst emitters fire via replicated particleburst {uuid,t} shared-timestamp
message (keypress/nodetrigger precedent) - PFX-C wires ground impacts to it
- UI: Inspector Particles section (preset/emission/motion/look/space +
Burst now), object context menu Effects submenu (set-aware), viewport
Add > Effects standalone marker spheres
- flowRuntime tick hands particle node pairs + userData sweep to
updateParticles (particle NODE ships in PFX-B); caps 8 emitters x 500
particles (VR drawRange 200); Points hidden in wireframe view mode
(overrideMaterial would clobber the shader)
- e2e particles.test.cjs: 15 checks (attach/replicate/burst/undo/cap/menus,
no-GLTF-leak, shader-error probe) - ALL PASS; build green; svelte-check
holds the 485/72 baseline
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dust puff / Confetti / Sparks are burst-mode: they idle until a trigger, so attaching one showed nothing (looked broken). The runtime now auto-fires a burst ONCE when it builds a burst-mode emitter entry - immediate LOCAL feedback on attach / count change (each peer fires when it builds its own entry; tightly-synced bursts still ride the replicated particleburst timestamp from Burst now / PFX-C impacts). Rendering was already correct (verified via screenshot - fire embers + confetti squares both draw). - particles.test.cjs: +auto-fire-on-attach check (16 total, all pass) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wire particle emitters in the node editor: a `particle` node that targets the
connected object (Object Selector) or the graph owner (H1 implicit rule),
rendered by the same deterministic particleRuntime as the Inspector emitters
(keyed by node id).
- nodeCatalog: particle node in the Effects group, seeded from a preset,
params for the key fields (NOT in animationTypes - a keyed runtime like sound)
- flowSockets: INPUT particle {count:number, color:color, trigger:event};
output is the effect channel automatically
- ParticleNode.svelte: card with preset picker (seeds full config), emission/
motion/look controls, and count/color/trigger input sockets
- particleRuntime: a wired `color` tints both gradient stops; a wired event
fires a burst-mode emitter on its RISING edge (once per pulse; rides the
replicated nodetrigger stamp so peers stay ~in sync)
- NODES.md audit row
- e2e flow-particle.test.cjs (5 checks: catalog/sockets, node-keyed emitter,
wired count rebuild, event->burst, implicit-owner) - ALL PASS. build green;
svelte-check holds 485/72. Docs updated in theprototype-docs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ission offset Fixes from user testing of PFX-B: - Continuous emitters (Sparkles/Fire/Smoke) looked invisible: they spawn from the object CENTER and were occluded inside the solid mesh. Additive (glow) presets now render with depthTest OFF so they glow through the object (the standard VFX trick); normal-blend presets (smoke/confetti/dust) keep proper occlusion. - On Click wired THROUGH the particle node did nothing on object click: fireObjectClick only matched On Click to Object Selector DIRECT wiring. It now walks outgoing edges (BFS), so On Click, Particles, Object Selector fires on the target object's click. (Key Press already worked.) - NEW emission offset: offset (vector3, object-local) sets WHERE particles spawn - lift them to the top or corner instead of the center. Exposed as an Inspector "Emit from" XYZ row and a node Vector3 input. - More node inputs: offset, speed, gravity, size (alias of size-start) alongside count, color, trigger. particleEntries() debug view gains offset + depthTest. flow-particle.test.cjs extended (offset uniform, onclick-through-node BFS, additive/normal depthTest) - 8/8; particles 16/16. build green; svelte-check 485/72. Docs updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follow-ups from user testing: - ParticleNode sockets drifted (the trigger handle landed next to the preset row) because they were absolutely positioned at guessed pixel offsets. Each wired input now lives in its own row with the Socket anchored to that row (top:50% inside a padding-cancelled relative wrapper - the ObjectFlowNode pattern), so handles always line up with their label. preset/emission/sprite/ space are plain params with NO socket. - Editing a burst emitter (Dust puff / Confetti / Sparks) now re-fires it on any look change - moving the emission offset especially - so you see the effect at the new origin immediately, no manual trigger. Count changes already rebuilt + auto-fired; this covers offset/speed/gravity/size/sprite/color via a config signature. particles.test.cjs +offset-re-fire check (17); flow-particle 8/8. build green; svelte-check 485/72. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…PFX-C) Rapier collision events (none existed): the stepping peer passes an EventQueue to world.step, dynamic colliders carry ActiveEvents.COLLISION_EVENTS, and the queue drains PER SUBSTEP (events from early substeps would merge/get lost). Collider handles map to body entries (they were discarded); the ground cuboid's handle is captured. A contact counts as an impact when the body's PRE-step downward velocity >= 1.2 m/s (contact resolution rewrites linvel during the step - a bounce reads as upward after) with a 300ms per-body cooldown, so rolls and settling can't machine-gun. Impacts are INITIATOR-detected and replicate over the existing shared-stamp paths (golden rule 8 kept): - On Impact node (Triggers, event output, wirable minStrength gate): pulses via applyNodeTrigger/nodetrigger; targeting reuses the fireObjectClick BFS (works wired THROUGH a Particles node) + the implicit-owner rule. - Emitters with emission "On impact" (new Inspector + node option, mode 'impact' = a triggered mode like burst): burstObjectParticles -> replicated particleburst. Node-based impact emitters detected through particleRuntime.hasImpactEmitter. THROW (user request): VR grip-grabs of dynamic bodies mid-sim now ride the SAME holdBody/releaseBody contract as the desktop gizmo - release imparts the estimated hand velocity (mass makes momentum), via the multiTransform primed dynamic import pattern (no static physics edge into vrControls). Desktop already threw; on-device VR feel is the user's manual check. e2e particles-impact.test.cjs (8 checks incl. real rapier fall -> burst + nodetrigger + cooldown + clean stop); particles 17/17 + flow-particle 8/8 regression-green. build green; svelte-check 485/72. NODES.md row + docs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… rework - PHYSICS (and every flow animation, particle sweep, sound update) FROZE the moment a headset went on: flowRuntime's loop rode window.requestAnimationFrame, which browsers SUSPEND during an immersive WebXR session (only session.requestAnimationFrame is serviced) - and the physics step is that loop's postTick. The tick body is now split from its scheduler and Scene.svelte PUMPS it from threlte's task loop (setAnimationLoop, XR-aware) while renderer.xr.isPresenting; a 3ms timestamp guard makes a double delivery a no-op on browsers that keep window rAF alive. - Radial menu Scene ring reworked: the environment presets (Daylight/Night/...) moved into a nested "Environment >" sub-ring (they crowded the ring), and a new "Physics: On/Off" toggle starts/stops the scene simulation from VR (the P key is desktop-only; greyed out while a remote peer runs the sim). VRMenu re-derives sectors on simulating/remoteSimulating so the label tracks live. - vr-radial-menu.test.cjs updated for the new ring layout + a Scene-ring assertion (28 checks, all pass). flow-runtime single-page 8/8; particles 17/17 + flow-particle 8/8 + particles-impact 8/8. flow-runtime's two-peer half is env-gated here (hosts mapping off) - connection code untouched. build green; svelte-check 485/72. On-headset feel = user's manual check. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…es, inspector search
Follow-ups from VR play-testing:
- ORIENTED colliders: the collider size now comes from the object's LOCAL AABB
(rotation stripped for the measure, restored after) and the collider carries
the object's rotation RELATIVE to the identity-start body (joints keep their
C3 world-aligned requirement). A rotated wall/ramp/box collides as itself -
the old world-AABB capture inflated any rotated object into a fat
axis-aligned block, which is why VR-rotated objects had wrong collision.
- Collider SHAPES: Sphere / Capsule / Cylinder join Box + Convex hull
(shapeDesc fits the local extents; capsule/cylinder stand along local Y,
sphere takes the largest extent). Inspector Physics gains the options -
userData.physics.collider replicates as before.
- FUN BY DEFAULT: standard primitives (/create Box/Sphere/Stairs/... -
explicit FUN_PRIMITIVES allow-list) spawn with userData.physics
{mode:'dynamic', mass:1}, so a fresh cube falls, stacks and THROWS the
moment a sim runs - no Inspector trip first. Deterministic (receivers run
the same builder); Terrain + module primitives stay scenery; the
Add > Effects marker sphere explicitly clears the stamp (replicated).
- Inspector UX: every Section is now COLLAPSIBLE (chevron, persisted per
label) and a property SEARCH box sits under the drawer header (scene +
object) - typing filters sections by their RENDERED text (labels, values,
hints), with matches force-expanded; Esc clears. inspectorFilter store +
Section.svelte rework (label in its own span - getByText exact-match).
e2e physics-colliders.test.cjs (8: dynamic-default, Terrain excluded, ball
rolls down a 30-degree ROTATED ramp - impossible with the old AABB, search
filters/restores, collapse persists/reopens); inspector 17/17, particles
17/17, flow-particle 8/8, particles-impact 8/8 green. physics-joints'
two-peer half env-gated (known). build green; svelte-check 485/72. Docs
updated (physics.md collider shapes + ready-to-play tip).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Particle effects, physics impacts & VR/collider polish
A self-contained feature arc (8 commits, one per phase) adding a full particle system, physics-driven impact effects, object throwing, and the physics/VR/Inspector polish that fell out of play-testing it. Every mutation replicates; nothing changes OSS-vs-cloud behavior.
Particles (PFX-A/B/C)
THREE.Pointsper emitter at the scene root (never inobjectsGroup/GLTF sync).userData.particles(replicates likeuserData.physics).ActiveEvents, per-substep drain, pre-step downward-velocity filter + per-body cooldown). Emitters set to "On impact", or the new On Impact trigger node, fire when an object lands — replicated via the shared-timestampparticleburst/nodetriggerpaths.Physics & throwing
VR & Inspector
window.rAFis suspended in immersive XR, which froze the flow tick (and its physics post-tick). Scene now pumps the tick from threlte's XR-aware loop while presenting.Verification
Committed e2e suites:
particles(17),flow-particle(8),particles-impact(8),physics-colliders(8), plusinspector/vr-radial-menuupdated.npm run buildgreen;npx svelte-checkholds the 485/72 baseline (no new errors). Two-peer suite halves are exercised via send-spy asserts (the local hosts mapping is off). On-device VR feel was play-tested by the author.Public docs updated in
theprototype-docs(particle effects page, On Impact/Particles node pages, physics collider shapes + throwing).🤖 Generated with Claude Code