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.
#!/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();
}
