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.

src/game/sim/headless.ts
// Headless driving harness used by tools/sim/drive.mjs (not bundled).
import { Quaternion, Vector3 } from 'three';
import { Input, type DirectorKey } from '../input.js';
import { type CollisionFile, type LevelData, type Markers, parseRayMesh } from '../level/data.js';
import { deriveLevel } from '../level/editorLevel.js';
import { DrivingManager, ORIGINAL_FRAME_MS } from '../lingo/drivingManager.js';
import { initPhysics, type HavokOptions } from '../physics/havok.js';

/** A level as tools/sim reads it: legacy JSON + ray mesh files, or a Blender level GLB. */
export type RawLevel = { markers: Markers; collision: CollisionFile; rayMeshBuffer: ArrayBuffer } | { levelGlb: ArrayBuffer };

function levelData(raw: RawLevel): LevelData {
  if ('levelGlb' in raw) {
    const { level, warnings } = deriveLevel(raw.levelGlb);
    for (const w of warnings) console.warn(`level.glb: ${w}`);
    return level;
  }
  return { markers: raw.markers, collision: raw.collision, rayMesh: parseRayMesh(raw.rayMeshBuffer) };
}

const KEYS: Record<string, DirectorKey> = { u: 126, d: 125, l: 123, r: 124, s: ' ' };

export async function runDrive(
  raw: RawLevel,
  script: string,
  overrides: Partial<HavokOptions>,
  printEvery = 0.25,
): Promise<void> {
  await initPhysics();
  const input = new Input();
  const dm = new DrivingManager(
    levelData(raw),
    input,
    overrides,
  );
  const steps = script.split(',').map((s) => {
    const [keys, secs] = s.split(':');
    return { keys: keys === '-' ? [] : [...keys].map((k) => KEYS[k]), secs: Number(secs) };
  });
  let t = 0;
  let nextPrint = 0;
  const rb = dm.car.rb;
  const start = rb.position;
  const fmt = (v: Vector3) => `${v.x.toFixed(0)},${v.y.toFixed(0)},${v.z.toFixed(1)}`;
  for (const step of steps) {
    input.clear();
    for (const k of step.keys) input.setVirtual(k, true);
    const end = t + step.secs;
    while (t < end - 1e-9) {
      dm.stepFrame(ORIGINAL_FRAME_MS);
      t += ORIGINAL_FRAME_MS / 1000;
      if (t >= nextPrint) {
        nextPrint += printEvery;
        const v = rb.linearVelocity;
        const up = new Vector3().setFromMatrixColumn(rb.transform, 2);
        const fwd = new Vector3().setFromMatrixColumn(rb.transform, 1);
        const heading = (Math.atan2(-fwd.x, fwd.y) * 180) / Math.PI;
        const ground = dm.car.wheels.map((w) => w.pOnGround).join('');
        const springs = dm.car.wheels.map((w) => w.pSpringLength.toFixed(1)).join('/');
        console.log(
          `t=${t.toFixed(2)} keys=${step.keys.join('+') || '-'} pos=${fmt(rb.position)} dist=${rb.position.distanceTo(start).toFixed(0)} ` +
            `speed=${v.length().toFixed(0)} hdg=${heading.toFixed(0)} upZ=${up.z.toFixed(2)} ground=${ground} springs=${springs} ` +
            `skid=${dm.car.pSkidding} wz=${rb.angularVelocity.z.toFixed(1)} rays=${dm.car.wheels.map((w) => w.pGroundNormal.toArray().map((c) => c.toFixed(2)).join(" ")).join("|")}`,
        );
      }
    }
  }
  dm.destroy();
}

/**
 * Replay per-frame key states (126, 125, 123, 124, space as 0/1) logged from
 * the original and return the same columns its logger writes, one row per frame.
 */
export async function runReplay(
  raw: RawLevel,
  frames: number[][],
  overrides: Partial<HavokOptions>,
  /** Optional per-frame chunk lists (ms) decoded from the original's simTime deltas. */
  chunks?: number[][],
  /** Logger experiments: after frame k, set [position, axis, angleDeg, velocity] and drop queued forces. */
  experiments: Record<number, [number[], number[], number, number[]]> = {},
): Promise<number[][]> {
  await initPhysics();
  const input = new Input();
  const dm = new DrivingManager(
    levelData(raw),
    input,
    overrides,
  );
  const rb = dm.car.rb;
  const order: DirectorKey[] = [126, 125, 123, 124, ' '];
  const rows: number[][] = [];
  for (const [f, keys] of frames.entries()) {
    order.forEach((k, i) => input.setVirtual(k, keys[i] === 1));
    if (chunks) for (const c of chunks[f]) dm.update(c);
    else dm.stepFrame(ORIGINAL_FRAME_MS);
    const exp = experiments[f];
    if (exp) {
      dm.havokManager.clearQueuedForces();
      rb.position = new Vector3(...exp[0]);
      rb.quaternion = new Quaternion().setFromAxisAngle(new Vector3(...exp[1]).normalize(), (exp[2] * Math.PI) / 180);
      rb.linearVelocity = new Vector3(...exp[3]);
      rb.angularVelocity = new Vector3();
    }
    const p = rb.position;
    const q = rb.quaternion;
    const angle = 2 * Math.acos(Math.min(1, Math.abs(q.w)));
    const s = Math.sqrt(Math.max(1e-12, 1 - q.w * q.w)) * Math.sign(q.w || 1);
    const v = rb.linearVelocity;
    const w = rb.angularVelocity;
    const row = [0, dm.havokManager.getSimTime(), p.x, p.y, p.z, q.x / s, q.y / s, q.z / s, (angle * 180) / Math.PI, v.x, v.y, v.z, w.x, w.y, w.z];
    for (const wh of dm.car.wheels) row.push(wh.pOnGround, wh.pSpringLength, wh.pRotation);
    row.push(...keys, dm.car.pSkidding);
    rows.push(row);
  }
  dm.destroy();
  return rows;
}

/**
 * Start the port from a logged original state (row `start`: position, axis-angle
 * rotation, velocities, steering), queue that frame's wheel forces, then replay
 * the following frames' chunks and keys. Isolates contact responses (landings)
 * from accumulated drift.
 */
export async function runInject(
  raw: RawLevel,
  start: number[],
  frames: number[][],
  chunks: number[][],
  overrides: Partial<HavokOptions>,
): Promise<number[][]> {
  await initPhysics();
  const input = new Input();
  const dm = new DrivingManager(
    levelData(raw),
    input,
    overrides,
  );
  const rb = dm.car.rb;
  const order: DirectorKey[] = [126, 125, 123, 124, ' '];
  rb.position = new Vector3(start[2], start[3], start[4]);
  const axis = new Vector3(start[5], start[6], start[7]);
  rb.quaternion = axis.lengthSq() > 1e-12 ? new Quaternion().setFromAxisAngle(axis.normalize(), (start[8] * Math.PI) / 180) : new Quaternion();
  rb.linearVelocity = new Vector3(start[9], start[10], start[11]);
  rb.angularVelocity = new Vector3(start[12], start[13], start[14]);
  dm.car.wheels.forEach((w, i) => (w.pRotation = start[17 + 3 * i]));
  order.forEach((k, i) => input.setVirtual(k, start[27 + i] === 1));
  dm.car.update();
  const rows: number[][] = [];
  for (const [f, keys] of frames.entries()) {
    order.forEach((k, i) => input.setVirtual(k, keys[i] === 1));
    for (const c of chunks[f]) dm.update(c);
    const p = rb.position;
    const q = rb.quaternion;
    const angle = 2 * Math.acos(Math.min(1, Math.abs(q.w)));
    const s = Math.sqrt(Math.max(1e-12, 1 - q.w * q.w)) * Math.sign(q.w || 1);
    const v = rb.linearVelocity;
    const w = rb.angularVelocity;
    const row = [0, start[1] + dm.havokManager.getSimTime(), p.x, p.y, p.z, q.x / s, q.y / s, q.z / s, (angle * 180) / Math.PI, v.x, v.y, v.z, w.x, w.y, w.z];
    for (const wh of dm.car.wheels) row.push(wh.pOnGround, wh.pSpringLength, wh.pRotation);
    row.push(...keys, dm.car.pSkidding);
    const c = rb.centerOfMass;
    row.push(c.x, c.y, c.z, rb.mass);
    for (const wh of dm.car.wheels) row.push(wh.pLoad, wh.pSpeed.x, wh.pSpeed.y, wh.pSpeed.z);
    rows.push(row);
  }
  dm.destroy();
  return rows;
}