CS GPU instances, fields, passes and runtime WGSL — version 3, 2026-10-23 Availability Use the WebGPU preview player at https://cinematicsciences.com/?gpu=1. Always await getGPUCapabilities(). runtimeWGSL must be true and backend WebGPU. Version 2 reports maxInstancesPerCollection=1048576, maxTotalInstances=4000001, and maxCollections=8. Check the loaded version before using larger counts. The ordinary player is unchanged. This is an experimental activity workflow; do not publish activities requiring it as compatible with the ordinary player. Runtime/browser verification status is recorded separately in TEST_RESULTS.txt. Purpose Generic GPU instance collections driven by AI-written calculations supplied after the Unity player loads. The browser compiles WGSL on Unity's GPU device. Unity draws the same buffer. No per-frame JavaScript position loop or Rigidbody. Stars, fluids and FDTD are not built-in claims: this release proves generic instance updates. Version 3 adds regular fields and immutable snapshots for ordered passes. Cross-collection coupling and built-in scientific solvers are future additions. Current updates must write only their own instance and must not read another instance that the same dispatch can mutate; use previous in compileGPUPasses. API (all asynchronous) createBillboards(name,count,options) createGPUInstances(name,shape,count,options) shape: billboard, sphere, box, custom. options: size=.1, extent=1000; custom requires meshObject naming an existing static MeshFilter object. Version 2: 1..1048576 instances per collection, at most eight collections; total budget 4000001 (four million particles plus one reference marker). Split large demonstrations across collections, using a WGSL index offset and total count constant for global initialization. Each buffer is at most 64 MiB; four million records occupy 256 MB, excluding rendering/runtime overhead. Creation checks device buffer/binding/dispatch limits before allocation. CPU initialization uses a reusable 1 MiB staging block, not a full-size copy. Version 1 remains limited to 262144 per collection. extent is rendering bounds half-size about origin, not a physical boundary. Ensure all instances fit. Initial records: position zero, size supplied, velocity zero, mass1, white RGBA, custom properties zero. Collection starts paused. Duplicate names fail. compileGPU(name,source) Compile and replace program without executing it or resetting simulation time. Old program remains if compilation fails. Does NOT validate scientific claims. runGPU(name,dt=1/60) Fixed timestep, positive and <=.1, at most4 steps/frame. Overload drops steps rather than enlarging dt. Simulation time can lag wall time. pauseGPU(name) Pause; v3 preserves dt. The previous v2 build resets dt back to1/60. stepGPU(name) One step while paused, resolves after GPU submitted work completes. getGPUState(name) count, compiled, running, time, droppedSteps. measureGPUInstance(name,index) Read ONE actual GPU record asynchronously. Returns particle.position[3],size, velocity[3],mass,color[4],properties[4]. No large or per-frame readback. removeGPUInstances(name) clearGPUInstances() Dispose resources. clearRun/clearCode also clear generic GPU collections. Persist creation, source and compile/run calls in editor code for replay. WGSL contract Provide fn updateParticle(i:u32) and optional WGSL helper functions. The harness supplies main, bindings and declarations below. Do not redeclare these: struct Particle { positionSize: vec4, velocityMass: vec4, color: vec4, properties: vec4 }; struct Params { count:u32, dt:f32, time:f32, padding:f32 }; @group(0) @binding(0) var particles:array; @group(0) @binding(1) var params:Params; positionSize.xyz = position in Unity world coordinates, Y up. positionSize.w = display size (not physical radius). velocityMass.xyz = velocity; velocityMass.w = mass. color = RGBA, RGB may exceed1 for brighter additive output. Alpha0 hides. properties = four user-defined scalar values; assign meanings/units explicitly. Color is not automatically temperature. Source can map properties.x to color. There are no Unity lights per particle. HDR/bloom is separate camera appearance. No orientation field yet; custom meshes are unlit/additive, not PBR GameObjects. Example: after createBillboards("sample",17), compile an initializer: fn updateParticle(i:u32) { particles[i].positionSize = vec4(f32(i)*0.4-3.2,3.0,0.0,0.15); particles[i].properties.x = 300.0 + f32(i); particles[i].color = vec4(0.1,0.8,1.0,1.0); } Then stepGPU("sample") and measureGPUInstance("sample",3). Expected x=-2, temperature slot=303, color approximately[.1,.8,1,1]. Compile a new program to update each frame: fn updateParticle(i:u32) { let velocity=particles[i].velocityMass.xyz; particles[i].positionSize = vec4(particles[i].positionSize.xyz + velocity*params.dt,particles[i].positionSize.w); } Velocity begins at zero; assign velocities in initialization if motion is wanted. WGSL is a STRING supplied to compileGPU. Ordinary activity code remains readable CS JavaScript. Do not write C# or Unity API calls in the editor. Team workflow 1. Read getCapabilities contracts and actual getGPUCapabilities result. 2. Create a small collection, compile initializer, step, read one known record. 3. Compile a new calculation, step, verify its expected numeric change. 4. Run and inspect screenshot including controls. Position existing MainCamera using documented CS camera commands; do not create another camera. 5. Explain the observed result, limitations and what the user can change. Stop for the user to explore; do not autonomously build a complex simulation. Compile success and a pretty picture do not establish mathematical accuracy. Document units, equations, approximations and a numerical comparison. Version 3 — scientific fundamentals New methods require getGPUCapabilities().version >= 3. This is infrastructure, not an already-validated plasma, wave, optics, or quantum solver. createGPUField(name,width,height,depth=1,options={}) Regular XY grid, Z layers. Total cells <=1048576; shares collection/count budget. options spacing=1, origin={x:0,y:0,z:0}, extent=1000, component=0,min=0,max=1,slice=0. Origin is the CENTER of cell(0,0,0), not the corner of the grid. Spacing positive. index=x+width*(y+height*z). properties.xyzw are four independent scalar channels. positionSize.xyz is cell center; w is full cell width. Cells initially zero. Renderer displays one XY slice. No interpolation between cell values. Its blue, cyan,yellow palette clamps display values to min..max; stored data is unchanged. showGPUField(name,component=0,min=0,max=1,slice=0) Select component 0..3, finite min (read-only storage) @binding(3) grid: Grid (uniform) struct Grid { size:vec4, originSpacing:vec4 } grid.size.xyz = width,height,depth; originSpacing.xyz=origin,w=spacing. For non-field collections dimensions default to 1,1,1; use params.count. BEFORE EACH PASS the engine copies the complete current records to previous on GPU. Read neighbors ONLY from previous. Write ONLY particles[i]. Unwritten properties retain their values. This is a snapshot per pass, not per frame. Each dispatch completes in queue order before the next pass's snapshot. All passes see the same params.dt and params.time; time advances ONCE after the entire sequence. Equations must account for their own intermediate stages. Clamping, reflecting, periodic or absorbing boundary rules are YOUR WGSL code. Example periodic previous-cell index: (i+params.count-1u)%params.count. No shared bindings between named collections in this first version. A snapshot doubles record memory for that collection. Total record+snapshot budget512MiB, in addition to count/device limits. This is not total app memory. All passes compile before replacement commits. A bad pass retains the entire previous working program. compileGPU switches back to the old in-place mode and releases the snapshot. Neither compile method executes or resets time. getGPUState also reports passes,snapshot,dt,computeBytes. Version3 pauseGPU preserves configured dt; version2 resets it to1/60. Minimal neighbor-field example (a numeric test, not a physical model) await createGPUField("temperature",5,3,2,{spacing:.5,origin:{x:-1,y:2,z:0}}); await compileGPU("temperature","fn updateParticle(i:u32) { particles[i].properties.x=f32(i); }"); await stepGPU("temperature"); await compileGPUPasses("temperature",[ "fn updateParticle(i:u32) { let x=i%grid.size.x; let left=i-x+(x+grid.size.x-1u)%grid.size.x; particles[i].properties.x=previous[i].properties.x+previous[left].properties.x; }", "fn updateParticle(i:u32) { particles[i].properties.x=2.0*previous[i].properties.x; }" ]); await stepGPU("temperature"); await showGPUField("temperature",0,0,114,1); // Measured cell0=8; cell29=114. Read one cell with measureGPUInstance. Scientific development rules Separate simulation from appearance. Glow/size/color are display choices unless explicitly defined as physical measurements. Specify units, boundaries, equations, initial state and solver. Test a known solution, timestep/grid convergence and appropriate conserved quantities. Large particle counts do not prove accuracy. Use snapshot passes for neighbors; never read another mutable particles[j] in an in-place dispatch. Do not claim arbitrary cross-field coupling or a built-in physics solver. Keep finite grids, pass counts and memory budgets inspectable. Version 4 — optical textures and RGBA image grids createGPUImage and compileGPUTexture add a generic XY image display and one CORS image binding on the Unity WebGPU device. Exact contracts and the new traceOpticalRay, measureThinLens, drawOpticalRays, renderOpticalImage tools are in OPTICS.txt. These bounded geometric-optics tools do not trace arbitrary Unity scene meshes. getGPUState also reports shape and textureBytes.