GPU swarms, shared animation and CS handoff — engine 24 Native and browser validation status: cs-science-framework/roadmap-batch16-18/PLAN.txt Create a school, flock or crowd of moving mesh instances. await createGPUAnimatedInstances("school", 512, "", {animationAsset:"trout", size:3}); await createGPUSwarm("school", {size:3, center:{x:0,y:5,z:0}, halfSize:{x:10,y:3,z:6}}); await startGPUSwarm("school"); Use a loaded, compatible generic/legacy animated model instead of a prepared asset. await createGPUAnimatedInstances("school", 512, "fishModel", {sampleRate:30, size:1}); Prepared asset: trout. Runtime setup currently needs one readable single-submesh SkinnedMeshRenderer, 1–128 bones, <=4 weights/vertex, <=100000 vertices, and 1–8 non-humanoid clips of <=30 seconds; no blend shapes. GLB is supported when its Unity import exposes those clips through Animation or Animator. Humanoid muscle retargeting and multi-material/multi-skin rigs must first be prepared; this is not an unrestricted humanoid crowd importer. Existing single-character animation APIs remain available. Never replace them with GPU instances for a player avatar. Animation is sampled once during setup, capped at 241 samples/clip and 32 MiB. Instances share those bone poses. Skinning, clip interpolation and blending run in the vertex shader; there is no CPU Animator per member. GPU source transform coordinates are Unity coordinates, like the other GPU tools (CS object.pos.x uses the legacy opposite X convention). Prepared trout is about 0.4 m long before size scaling. Motion direction defaults to mesh +Z; use modelRotation if needed. Pause/resume/reset steering; direct the goal without a custom camera or motion loop. await pauseGPUSwarm("school"); await setGPUSwarm("school", {speed:3, separation:2, alignment:1, cohesion:.6}); await setGPUSwarmTarget("school", 5, 4, 0); await stepGPUSwarm("school"); await resetGPUSwarm("school"); await startGPUSwarm("school"); A swarm needs styleGPUInstances first unless created with createGPUAnimatedInstances. Static box/sphere/custom/billboard collections can also use the same steering. Set settings while paused. reset stops motion, restores seed positions and keeps retired/handoff members disabled; start resumes. Swarm removal leaves its last positions and releases ownership for custom WGSL. Do not run competing motion programs on a swarm-owned buffer. Avoid named Unity box/sphere/capsule collider shapes (up to32). await addGPUSwarmObstacle("school", "rock"); await removeGPUSwarmObstacle("school", "rock"); This is bounded steering/avoidance, not contact-solid rigidbody simulation or navmesh pathfinding. No mesh collider, two-way forces or CCD in swarm avoidance. Use GPU particle/scene contact APIs for physical collisions. Keep obstacles inside the enclosure and use an appropriate radius/lookAhead. Moving obstacles are sampled each simulation step. Member overlap can occur in dense schools. Swarm settings: center/halfSize/target, modelRotation (degrees), size, radius, neighborRadius, separationRadius, separation/alignment/cohesion/seeking weights, speed, acceleration, avoidance, lookAhead, seed, maxNeighbors (1–256). Limits: 65536 members, 262144 grid cells, 32 members/cell, shared 512 MiB GPU budget. Actual usable count depends on mesh, screen size and device; measure FPS first. 60 Hz steering, at most four catch-up steps/frame. Overload drops simulation time rather than creating an unbounded frame stall. Diagnostics report cumulative grid overflow, neighbor-limit hits, invalid states, obstacle projections and steps dropped. Overflow means some neighbors were omitted; adjust density/grid. await getGPUSwarmState("school"); Control each member's animation independently, in batches of up to4096 entries. await setGPUInstanceAnimations("school", [ {clip:0, offset:0, speed:1, loop:true}, {clip:0, offset:.7, speed:.5, loop:false}, {clip:0, speed:1, blendClip:0, blendOffset:1.4, blend:.5} ], 0); Clip indices/names come from getGPUAnimationState. Offset is seconds; speed0..10; blend0..1 mixes two sampled clips/poses. loop:false holds the final pose. The clock is shared; use time:0 to restart, or offsets for per-instance phase. await setGPUAnimation("school", {paused:true}); await setGPUAnimation("school", {time:0, paused:false}); await getGPUAnimationState("school"); Budget geometry, animation sampling and shadows by camera distance. await styleGPUInstances("school", {lodDistance:25, farDistance:35, maxDistance:100, shadowDistance:20}); await setGPUAnimation("school", {nearFPS:60, mediumFPS:20, farFPS:8}); await getGPURenderState("school"); The existing renderer culls invisible instances and counts near/mid/far visible triangles. Prepared trout includes a compatible lower-poly mesh for mid/far. Runtime models keep their original geometry unless prepared with matching skin LODs. Arbitrary static LOD meshes cannot replace a skinned mesh. Animation rates quantize the clock; rates <=15 use nearest sampled bone poses (one frame read instead of two). Skinning still runs for each drawn vertex: lowering the rate alone does not eliminate that work. Animated bounds include all sampled poses. No new shader keyword variants are introduced for these capabilities. Turn one GPU member into a normal CS physics prop. await pauseGPUSwarm("school"); await makeGPUInstanceObject("school", 7, "fishProp", {mass:.3, gravity:true}); fishProp.vel = vector(0, 2, 0); await startGPUSwarm("school"); Handoff uses a bounded readback for one instance, preserves its position, rotation, scale, velocity and current posed mesh, creates box collider + Rigidbody + CS physics registration, then disables that GPU member. The pose becomes a rigid prop; it does not keep swimming or acquire a character controller. No Rigidbody interpolation. A rejected name/registration leaves the GPU member intact. Swarm reset keeps handed-off members retired. Removing/recreating the collection creates a fresh population. Custom WGSL programs can overwrite any slot, so do not restart a generator that recreates handed-off indices. Pending handoff blocks edits to that collection; deletion cancels safely. Avoid mass handoffs: use this for a few interactive members. Use normal CS code to manage and remove the resulting object; removing the GPU collection does not delete it. Demo recipes: gpu-swarms-swarm.js, gpu-swarms-animation.js, gpu-swarms-budgets.js. Use modern responsive controls, standard CS cameras and visible progress/errors. When deployed, refresh Game (whole iframe), then run the code. Run alone does not reload the new player or wrappers. Verify the live capability version is >=24.