GPU measurements and checkpoints — engine 20 Check getGPUCapabilities().gpuMeasurements and .gpuCheckpoints. Measure a scalar array without downloading it var result = await measureGPUStatistics("experiment", "temperature") result has selected, invalid, excluded, sum, mean, minimum, maximum, rms and units. Options offset/count select scalar entries (count 0 means remaining entries). Nonfinite inputs and overflowing derived per-sample values count as invalid. An overflowing final aggregate rejects the request. These are float32 reductions; parallel summation changes rounding. Do not claim exact arbitrary-precision totals. Two measurements may be pending. Scratch is bounded; only 48 aggregate bytes and at most 520 histogram bytes return from the GPU. summarizeGPUBuffer keeps its old return fields but now uses the same GPU final reduction. Show a speed or temperature distribution var speeds = await histogramGPUBuffer("experiment", "velocity", 0, 10, 32, {kind:"speed"}) Scalar is the default; speed/kinetic require an f32 xyz velocity buffer. 1..128 bins, low < high. Low/high endpoints are included; the high endpoint goes in the last bin. Separate underflow/overflow counts; invalid values are not binned. Display bin range, units, counts and a legend beside the chart. A changing test velocity field is not automatically a gas simulation or Maxwell distribution. Measure kinetic energy and momentum var before = await measureGPUKinetics("experiment", "velocity", {masses:"mass"}) var after = await measureGPUKinetics("experiment", "velocity", {masses:"mass"}) var drift = compareGPUMeasurements(before, after) Use an f32 scalar mass per particle, or {mass:2} for a constant nonnegative mass. Supply masses matching the solver; they are not inferred from Rigidbody objects or particle profiles. Energy = sum(0.5*m*speed²), momentum = sum(m*velocity). This is kinetic energy only, not potential/thermal/field energy. Drift may be physical: external forces, friction, inelastic contact or boundaries exchange it. Relative energy change is null when the baseline is zero; absolute change remains. Use SI quantities if displaying joules or kg m/s; metadata alone does not convert. Measure a selected region var region = await measureGPURegion("experiment", "temperature", "positions", {x:0,y:-3,z:-3}, {x:3,y:3,z:3}) Matching f32 xyz positions and one scalar per point; bounds are inclusive. For a sphere use measureGPUStatistics with {region:"sphere",positions:"positions", center:{x:0,y:0,z:0},radius:2}. Combine region options with histograms/kinetics. Sum is a sample sum, not a volume integral. Numerical integration needs appropriate cell/volume/quadrature weights; unequal sampling density changes an unweighted mean. Compare a numerical result to a reference var error = await compareGPUBuffer("experiment", "computed", "reference") Matching f32 scalar lengths in the same program. Returns statistics of computed minus reference: mean is signed bias, rms is RMSE; max absolute error is Math.max(Math.abs(error.minimum), Math.abs(error.maximum)). For a timestep study, compare at equal simulated times and keep physical parameters/initial conditions fixed. Do not compare only how similar the pictures look. Demo: explicit Euler oscillators with 1 versus 10 substeps against the analytic cosine solution. Collect a bounded history for a graph var history = startGPUMeasurementHistory("experiment", "velocity", {intervalMs:500,capacity:120,measurement:{kind:"speed"}}) var samples = getGPUMeasurementHistory(history) stopGPUMeasurementHistory(history) At most 8 histories, 1..2048 samples each, 250..60000 ms intervals. No overlapping request within one history; latest samples replace oldest. Error stops collection. Each sample retains simulation timeStart/timeEnd around measurement and wallTime. The interval brackets the measurement; it is not an exact synchronous timestamp. For strict comparisons pause/step first. ClearRun/removal clears histories. Retrieve the history before stopping (stop removes it). Graph it in the existing control panel; do not create a separate mandatory math tab or obscure game controls. Save actual state, not just initial code await saveGPUCheckpoint("experiment", "My experiment", {description:"Before impact"}) Explicitly pauses the program and leaves it paused. Captures all typed buffers as raw little-endian f32/u32 bits, WGSL pass definitions, timestep/time/step counter, speed and skipped-time report. RNG and parameters must live in those buffers or small explicit metadata (up to 16 KiB text). Hidden JS variables, external clocks, images, CS actors, contacts and unregistered randomness are not captured. 32 MiB raw buffers per checkpoint. Physics-owned programs, editable meshes and tracked ropes are rejected: their extra solver/body state is not implemented here. These checkpoints are for ordinary createGPUProgram/runtime-WGSL experiments. A SHA-256 checksum verifies the stored payload. This is integrity checking, not cryptographic ownership or a guarantee of cross-device float32 reproducibility. Restore safely and resume var restored = await loadGPUCheckpoint("restoredExperiment", "My experiment") await showGPUFieldSlice("restoredExperiment", "temperature", {min:0,max:100}) await runGPUProgram("restoredExperiment", restored.clock.dt, restored.clock.speed, restored.clock.maxSteps) The target name must be NEW. Validate before allocation; failed restoration removes only the new target. Restore returns paused. Reattach display/camera/UI through normal commands; the snapshot is data/compute state, not an entire movie set. The original is untouched until your script explicitly removes it. Avoid auto restoring merely because a public activity was opened; provide a Restore button. A renderer needing initial triangle metadata is not reconstructed by this batch. Storage and UI var storage = await listGPUCheckpoints() await deleteGPUCheckpoint("My experiment") Local IndexedDB, 64 MiB serialized checkpoint-library cap per browser origin. This is THIS BROWSER's storage, not per-owner paid asset storage, server storage, a shared challenge attachment, cloud backup or cross-device sync. Other accounts in the same browser profile share this local library; use descriptive project keys. Browser clearing/eviction can remove it. Do not promise permanent safekeeping. Replacing a key is atomic; a quota failure preserves the previous saved record. List returns dates/sizes/time and usedBytes/limitBytes, not the large buffers. Show save/restore progress and success/error messages. Confirm before deletion. Game controls remain primary. Universal sound controls stay available. Five reusable saved activities: GPU Measures 01 Collision Accounting, 02 Speed Distribution, 03 Region Measurements, 04 Accuracy Explorer, 05 Save and Resume. Complete source in science-recipes/gpu-measures-*.js. Native tests first; browser verification and limits recorded in batch9/RESULTS.txt. Refresh Game after the new engine and cs-gpu.js are deployed; Run alone does not reload them. New/restarted teams receive this capability documentation.