clone

an endless marble-race generator in the browser; the url is the seed, so every track can be shared

main
marbles / tools / physics-spike / spike.c
spike.c 3.7K
1// M9 spike: Box3D → WASM viability for the marbles physics era.2//3// A slalom-like trough (floor + two walls, tilted 5°) with a marble-sized4// sphere rolling down it. Checks, in order:5// 1. It simulates sanely (ball stays in the channel, rolls downhill).6// 2. Determinism: two full sims in-process hash identically; the host7// runs the binary twice to confirm run-to-run identity.8// 3. Performance: steps/second at 60 Hz × 4 substeps, single worker.9#include "box3d/box3d.h"10#include <stdio.h>11#include <stdint.h>12#include <string.h>13#include <time.h>1415static uint64_t hash64(uint64_t h, const void* data, size_t n) {16 const uint8_t* p = (const uint8_t*)data;17 for (size_t i = 0; i < n; i++) {18 h ^= p[i];19 h *= 1099511628211ULL; // FNV-1a20 }21 return h;22}2324// One full sim: returns a position-trace hash; writes the final position.25static uint64_t simulate(b3Pos* finalPos, double* maxAbsX) {26 b3WorldDef wdef = b3DefaultWorldDef();27 wdef.workerCount = 1;28 b3WorldId world = b3CreateWorld(&wdef);2930 // Slalom trough: floor hx=0.7 (slalom half-width), walls at x=±0.75,31 // 60 long, tilted 5° about x so the ball rolls toward +z... the tilt is32 // about the X axis: rotate the trough body itself.33 b3BodyDef ground = b3DefaultBodyDef();34 ground.type = b3_staticBody;35 ground.rotation = b3MakeQuatFromAxisAngle((b3Vec3){1, 0, 0}, 0.0873f);36 b3BodyId groundId = b3CreateBody(world, &ground);37 b3ShapeDef sdef = b3DefaultShapeDef();38 b3BoxHull floor = b3MakeOffsetBoxHull(0.7f, 0.1f, 30.0f, (b3Vec3){0, -0.1f, 0});39 b3CreateHullShape(groundId, &sdef, &floor.base);40 b3BoxHull wallL = b3MakeOffsetBoxHull(0.05f, 0.5f, 30.0f, (b3Vec3){-0.75f, 0.4f, 0});41 b3CreateHullShape(groundId, &sdef, &wallL.base);42 b3BoxHull wallR = b3MakeOffsetBoxHull(0.05f, 0.5f, 30.0f, (b3Vec3){0.75f, 0.4f, 0});43 b3CreateHullShape(groundId, &sdef, &wallR.base);44 b3BoxHull capA = b3MakeOffsetBoxHull(0.8f, 0.5f, 0.05f, (b3Vec3){0, 0.4f, -30.05f});45 b3CreateHullShape(groundId, &sdef, &capA.base);46 b3BoxHull capB = b3MakeOffsetBoxHull(0.8f, 0.5f, 0.05f, (b3Vec3){0, 0.4f, 30.05f});47 b3CreateHullShape(groundId, &sdef, &capB.base);4849 // The marble: r=0.2 dynamic sphere near the high end, nudged off-center50 // so it slaloms between the walls.51 b3BodyDef ball = b3DefaultBodyDef();52 ball.type = b3_dynamicBody;53 ball.position = (b3Pos){0.2, 2.4, -24.0}; // on the tilted high end54 ball.linearVelocity = (b3Vec3){0.6f, 0, 1.5f};55 b3BodyId ballId = b3CreateBody(world, &ball);56 b3Sphere sphere = {{0, 0, 0}, 0.2f};57 b3ShapeDef ballShape = b3DefaultShapeDef();58 b3CreateSphereShape(ballId, &ballShape, &sphere);5960 uint64_t h = 1469598103934665603ULL;61 *maxAbsX = 0;62 for (int i = 0; i < 1200; i++) { // 20 s at 60 Hz63 b3World_Step(world, 1.0f / 60.0f, 4);64 if (i % 30 == 0) {65 b3Pos p = b3Body_GetPosition(ballId);66 h = hash64(h, &p, sizeof p);67 if (p.x > *maxAbsX) *maxAbsX = p.x;68 if (-p.x > *maxAbsX) *maxAbsX = -p.x;69 }70 }71 *finalPos = b3Body_GetPosition(ballId);72 b3DestroyWorld(world);73 return h;74}7576int main(void) {77 b3Pos a, b;78 double maxX;79 uint64_t h1 = simulate(&a, &maxX);80 uint64_t h2 = simulate(&b, &maxX);81 printf("hash1=%llx hash2=%llx in-process-deterministic=%s\n",82 (unsigned long long)h1, (unsigned long long)h2,83 h1 == h2 ? "YES" : "NO");84 printf("final=(%.6f, %.6f, %.6f) maxAbsX=%.3f\n", a.x, a.y, a.z, maxX);8586 // Perf: fresh world, 6000 steps timed.87 clock_t t0 = clock();88 b3Pos dummy;89 double dx;90 for (int r = 0; r < 5; r++) simulate(&dummy, &dx);91 double secs = (double)(clock() - t0) / CLOCKS_PER_SEC;92 printf("perf: %d steps in %.3f s → %.0f steps/s (%.1fx realtime at 60 Hz)\n",93 5 * 1200, secs, 5 * 1200 / secs, (5 * 1200 / secs) / 60.0);94 return 0;95}