Back to Supersonic RC Revive
SOURCE / PINNED RELEASE

Made of little things.

Supersonic RC Revive

Release
1ba42f1ca1d6…
Author-recorded commit
baecad10b1cd…
License
LICENSE
Author’s source reference
nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.napplet.soy%2F/n-143146b0d6f

Archive hash verified: 90d22b206672eba4…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

tools/sim/inject.mjs
#!/usr/bin/env node
// Start the port from one logged original state and replay the next frames.
//   node tools/sim/inject.mjs <original-log.txt> <row> <count> [havokOverridesJSON]
// Prints original vs port side by side (z, vz, speed, tilt, springs).
import { existsSync, readFileSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { createServer } from 'vite';

const root = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
// SIM_LEVEL: another level folder or Blender level .glb (default: the extracted originals).
const game = resolve(root, process.env.SIM_LEVEL ?? 'ref-out/game');
const [logPath, startArg, countArg = '40', overrides = '{}'] = process.argv.slice(2);
const rows = readFileSync(logPath, 'utf8').split(/\r?\n|\r/).map((l) => l.trim().split(/\s+/).map(Number)).filter((r) => r.length >= 33);
const start = Number(startArg);
const count = Number(countArg);
// Same chunk decoding and one-frame key lag as replay.mjs.
const frames = [];
const chunks = [];
for (let i = start + 1; i <= start + count && i < rows.length; i++) {
  const left = (rows[i][1] - rows[i - 1][1]) / 2.5;
  const n = Math.max(0, Math.floor((left - 0.003) / 0.015 + 1e-6));
  const rem = left - n * 0.015;
  chunks.push(left < 0.0029 ? [] : [Math.round(rem * 1e6) / 1000, ...Array(n).fill(20)]);
  frames.push(rows[i - 1].slice(27, 32));
}
/** The level: a Blender level GLB, or a folder of legacy files (or holding only level.glb). */
function readLevel(path) {
  const bytes = (file) => new Uint8Array(readFileSync(file)).buffer; // copy out of Node's shared pool
  if (path.endsWith('.glb')) return { levelGlb: bytes(path) };
  if (!existsSync(resolve(path, 'collision.json'))) return { levelGlb: bytes(resolve(path, 'level.glb')) };
  return {
    markers: JSON.parse(readFileSync(resolve(path, 'markers.json'), 'utf8')),
    collision: JSON.parse(readFileSync(resolve(path, 'collision.json'), 'utf8')),
    rayMeshBuffer: bytes(resolve(path, 'raymesh.bin')),
  };
}
const server = await createServer({ root, logLevel: 'error', server: { middlewareMode: true }, appType: 'custom', optimizeDeps: { noDiscovery: true, entries: [] } });
try {
  const { runInject } = await server.ssrLoadModule('/src/game/sim/headless.ts');
  const level = readLevel(game);
  const out = await runInject(level, rows[start], frames, chunks, JSON.parse(overrides));
  const tilt = (r) => {
    const n = Math.hypot(r[5], r[6], r[7]) || 1;
    const az = r[7] / n, a = (r[8] * Math.PI) / 180;
    const c = Math.cos(a);
    return (Math.acos(Math.max(-1, Math.min(1, c + az * az * (1 - c)))) * 180) / Math.PI;
  };
  const f = (x, w = 7, d = 1) => x.toFixed(d).padStart(w);
  const desc = (r) => `${f(r[4])}${f(r[11])}${f(Math.hypot(r[9], r[10]), 6, 0)}${f(tilt(r), 6)}  ${[16, 19, 22, 25].map((c) => (r[c - 1] ? r[c].toFixed(1) : ' air')).join(' ')} ${process.env.LOADS ? [37, 41, 45, 49].map((c) => f(r[c] ?? 0, 6, 0)).join('') : ''}`;
  console.log('   sim |       z     vz   spd  tilt  springs (orig)           |       z     vz   spd  tilt  springs (port)           | dpos');
  let sum = 0;
  out.forEach((p, k) => {
    const o = rows[start + 1 + k];
    const d = Math.hypot(o[2] - p[2], o[3] - p[3], o[4] - p[4]);
    sum += d;
    console.log(`${f(o[1], 6, 2)} | ${desc(o)} | ${desc(p)} | ${f(d, 5)}`);
  });
  console.log(`SUMMARY meanPosErr=${(sum / out.length).toFixed(2)} finalErr=${Math.hypot(...[2, 3, 4].map((c) => rows[start + out.length][c] - out[out.length - 1][c])).toFixed(1)}`);
} finally {
  await server.close();
}