SOURCE / PINNED RELEASE
Made of little things.
Supersonic RC Revive
- Release
- 372375d18e3f…
- Author-recorded commit
- 6fbf09f88a0a…
- License
- LICENSE
- Author’s source reference
- nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.napplet.soy%2F/n-143146b0d6f
Archive hash verified: 76e4eb30c67a6f45…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
// "Bedroom Grand Prix": the custom level. An original layout (not traced from
// the 2004 game) sized for the ported car: top speed ≈ 460 in/s, checkpoint
// pass radius 300, ≤ 10 s between checkpoints in Checkpoint Chase.
//
// Room: x −4500…4500, y −6500…6500, floor z = 0, walls 1600 high. One lap
// runs anticlockwise from the start line on the east side:
// 1 toy-block chicane → 2 up the ramp onto the toy chest → down to 3 (NE)
// → 4 over the book-gap jump → 5 (NW, quarter pipe nearby) → 6 → 7 on the
// bridge (or under it) → 8 (SW) → 9 through the shoebox tunnel → 10 cone
// slalom → back past the start.
import { box, cylinder, piece, type Piece, quad, quarterPipe, ramp, type Vec3 } from './mesh.ts';
import { rng } from './textures.ts';
export interface Marker {
name: string;
position: Vec3;
/** Heading in degrees about +Z (0 = facing +Y). Only the start uses it. */
heading?: number;
}
export interface LevelMaterial {
/** sRGB hex. */
color: string;
texture?: string;
}
export const LEVEL_MATERIALS: Record<string, LevelMaterial> = {
floor: { color: '#ffffff', texture: 'planks' },
wall: { color: '#a9c8e8' },
ceiling: { color: '#f3f0e8' },
skirting: { color: '#f4f1ea' },
rug: { color: '#ffffff', texture: 'rug' },
checker: { color: '#ffffff', texture: 'checker' },
stripes: { color: '#ffffff', texture: 'stripes' },
chest: { color: '#c0503c' },
chestTop: { color: '#e8b04a' },
rampTop: { color: '#3f86d8' },
rampSide: { color: '#2a5a9c' },
blockRed: { color: '#e04848' },
blockBlue: { color: '#3c7ce0' },
blockYellow: { color: '#f2c230' },
blockGreen: { color: '#46b45a' },
bookRed: { color: '#b8343c' },
bookGreen: { color: '#2f7d54' },
bookNavy: { color: '#2c3c78' },
bookCream: { color: '#eadcb8' },
pipe: { color: '#40b8b0' },
bridge: { color: '#8e5cc8' },
pillar: { color: '#6a3fa0' },
shoebox: { color: '#e6dccb' },
shoeboxLid: { color: '#d0573f' },
cone: { color: '#ff7a1c' },
shelf: { color: '#9a6a44' },
};
const BLOCKS = ['blockRed', 'blockBlue', 'blockYellow', 'blockGreen'];
const BOOKS = ['bookRed', 'bookGreen', 'bookNavy', 'bookCream'];
export interface Level {
pieces: Piece[];
markers: Marker[];
}
export function buildLevel(): Level {
const pieces: Piece[] = [];
const add = (p: Piece) => (pieces.push(p), p);
const r = rng(2026);
// --- room shell: floor and inward-facing walls (one static trimesh) -----------
const X = 4500;
const Y = 6500;
const H = 1600;
const room = add(piece('room', 'mesh'));
quad(room, 'floor', [-X, -Y, 0], [X, -Y, 0], [X, Y, 0], [-X, Y, 0], { cell: 125, uvScale: 512 });
const wall = (a: Vec3, b: Vec3) => {
quad(room, 'skirting', a, b, [b[0], b[1], 40], [a[0], a[1], 40], { cell: 250 });
quad(room, 'wall', [a[0], a[1], 40], [b[0], b[1], 40], [b[0], b[1], H], [a[0], a[1], H], { cell: 250, visualOnly: false });
};
wall([X, -Y, 0], [-X, -Y, 0]);
wall([-X, -Y, 0], [-X, Y, 0]);
wall([-X, Y, 0], [X, Y, 0]);
wall([X, Y, 0], [X, -Y, 0]);
// A ceiling so the chase camera never sees past the walls.
const ceiling = add(piece('ceiling', 'none', false));
ceiling.castsShadow = false;
quad(ceiling, 'ceiling', [-X, Y, H], [X, Y, H], [X, -Y, H], [-X, -Y, H], { cell: 500 });
// Rugs and the start line: flat, visual and ray-visible only.
const decal = (name: string, mat: string, x0: number, y0: number, x1: number, y1: number, z: number) => {
const p = add(piece(name, 'none'));
quad(p, mat, [x0, y0, z], [x1, y0, z], [x1, y1, z], [x0, y1, z], { cell: 125 });
return p;
};
decal('rug_west', 'rug', -3900, -900, -2300, 2900, 0.5);
decal('rug_middle', 'rug', -900, -1800, 900, 1800, 0.5);
const start = add(piece('start_line', 'none', false));
quad(start, 'checker', [2500, -4720, 0.6], [3500, -4720, 0.6], [3500, -4600, 0.6], [2500, -4600, 0.6], { uvScale: 60 });
// --- 1: toy-block chicane between the start and checkpoint 1 ---------------------
const block = (name: string, x: number, y: number, size: number, z = 0) => {
const p = add(piece(name));
const m = BLOCKS[Math.floor(r() * BLOCKS.length)];
box(p, [x - size / 2, y - size / 2, z], [x + size / 2, y + size / 2, z + size], m, { cell: 100, skipBottom: z === 0 });
return p;
};
block('block_c1', 2650, -3900, 220);
block('block_c2', 2650, -3900, 160, 220);
block('block_c3', 3400, -3300, 220);
block('block_c4', 2300, -2700, 200);
block('block_c5', 3700, -2700, 180);
// --- 2: toy chest with an up ramp (south) and a down ramp (north) --------------------
const CHEST_H = 360;
const chest = add(piece('toy_chest'));
box(chest, [1800, -500, 0], [4200, 2500, CHEST_H], { top: 'chestTop', sides: 'chest' }, { cell: 120, skipBottom: true });
const up = add(piece('ramp_chest_up'));
ramp(up, 2500, 3500, -2300, -500, '+y', 0, CHEST_H, { top: 'rampTop', sides: 'rampSide' }, 0, { cell: 120 });
const down = add(piece('ramp_chest_down'));
ramp(down, 2500, 3500, 2500, 4300, '-y', 0, CHEST_H, { top: 'rampTop', sides: 'rampSide' }, 0, { cell: 120 });
// A little kicker on the chest lid for air before the down ramp.
const lid = add(piece('chest_kicker'));
ramp(lid, 2700, 3300, 1700, 2000, '+y', CHEST_H, CHEST_H + 40, { top: 'stripes', sides: 'chest' }, CHEST_H, { cell: 100, uvScale: 80 });
// Bookshelf on the east wall north of the chest.
const shelf = add(piece('bookshelf'));
box(shelf, [4150, 3000, 0], [4500, 5800, 900], 'shelf', { cell: 150, skipBottom: true });
for (let i = 0; i < 14; i++) {
const b = add(piece(`shelf_books_${i}`));
const y = 3100 + i * 190;
box(b, [4020, y, 0], [4150, y + 150 + r() * 30, 120 + r() * 120], BOOKS[i % 4], { cell: 100, skipBottom: true });
}
// --- 4: the book-gap jump along the north wall (heading −X) ---------------------------
const JUMP_H = 260;
const launch = add(piece('ramp_jump_launch'));
ramp(launch, 900, 1550, 4900, 5900, '-x', 0, JUMP_H, { top: 'rampTop', sides: 'rampSide' }, 0, { cell: 110 });
const lip = add(piece('ramp_jump_lip', 'none'));
quad(lip, 'stripes', [900, 4900, JUMP_H + 0.5], [960, 4900, JUMP_H + 0.5 - 24], [960, 5900, JUMP_H + 0.5 - 24], [900, 5900, JUMP_H + 0.5], { uvScale: 80 });
const landing = add(piece('ramp_jump_landing'));
ramp(landing, -400, 550, 4900, 5900, '+x', 0, JUMP_H, { top: 'rampTop', sides: 'rampSide' }, 0, { cell: 110 });
// Books lying in the gap: falling short is a bump, not a wall.
for (let i = 0; i < 4; i++) {
const b = add(piece(`gap_books_${i}`));
const y = 4920 + i * 245;
box(b, [580 + r() * 20, y, 0], [880 - r() * 20, y + 230, 50 + i * 8], BOOKS[(i + 1) % 4], { cell: 100, skipBottom: true });
}
// --- 5: quarter pipe in the NW corner ----------------------------------------
const pipe = add(piece('quarter_pipe', 'mesh'));
quarterPipe(pipe, -4400, -2700, 5900, 560, 1, 'pipe', 12, { cell: 110 });
// West side: a scatter of blocks on the rug.
block('block_w1', -3700, 2500, 160);
block('block_w2', -2700, 2100, 200);
block('block_w3', -3300, 300, 180);
block('block_w4', -2500, -400, 140);
// --- 7: the bridge, two pillars and a deck; drive over it or under it -----------------
const BX0 = -2400;
const BX1 = -1400;
const DECK = 220;
const bridgeUp = add(piece('bridge_ramp_north'));
ramp(bridgeUp, BX0, BX1, -300, 700, '-y', 0, DECK, { top: 'bridge', sides: 'pillar' }, 0, { cell: 110 });
const deck = add(piece('bridge_deck'));
box(deck, [BX0, -2000, DECK - 24], [BX1, -300, DECK], { top: 'bridge', sides: 'pillar', bottom: 'pillar' }, { cell: 110 });
for (const [name, y0, y1] of [['bridge_pillar_n', -520, -300], ['bridge_pillar_s', -2000, -1780]] as const) {
for (const [x0, x1] of [[BX0, BX0 + 220], [BX1 - 220, BX1]]) {
const p = add(piece(`${name}_${x0}`));
box(p, [x0, y0, 0], [x1, y1, DECK - 24], 'pillar', { cell: 80, skipBottom: true });
}
}
const bridgeDown = add(piece('bridge_ramp_south'));
ramp(bridgeDown, BX0, BX1, -3000, -2000, '+y', 0, DECK, { top: 'bridge', sides: 'pillar' }, 0, { cell: 110 });
// --- middle: a fun box (ramp up, flat, ramp down) for stunts -----------------------
const funUp = add(piece('funbox_up'));
ramp(funUp, -500, 500, -1500, -900, '+y', 0, 160, { top: 'rampTop', sides: 'rampSide' }, 0, { cell: 110 });
const funTop = add(piece('funbox_top'));
box(funTop, [-500, -900, 0], [500, 300, 160], { top: 'stripes', sides: 'rampSide' }, { cell: 110, skipBottom: true, uvScale: 160 });
const funDown = add(piece('funbox_down'));
ramp(funDown, -500, 500, 300, 900, '-y', 0, 160, { top: 'rampTop', sides: 'rampSide' }, 0, { cell: 110 });
// --- 9: shoebox tunnel along the south wall (heading +X) ------------------------------
const TX0 = -1700;
const TX1 = -300;
const TUNNEL_H = 420;
for (const [name, y0, y1] of [['tunnel_wall_s', -6260, -6140], ['tunnel_wall_n', -5260, -5140]] as const) {
const p = add(piece(name));
box(p, [TX0, y0, 0], [TX1, y1, TUNNEL_H], 'shoebox', { cell: 110, skipBottom: true });
}
const roof = add(piece('tunnel_roof'));
box(roof, [TX0 - 40, -6300, TUNNEL_H], [TX1 + 40, -5100, TUNNEL_H + 50], 'shoeboxLid', { cell: 110 });
// --- 10: cone slalom, then back past the start ----------------------------------
for (let i = 0; i < 6; i++) {
const p = add(piece(`cone_${i}`));
const x = 300 + i * 330;
const y = -5000 + (i % 2 ? 260 : -260);
cylinder(p, x, y, 45, 0, 130, { sides: 'cone', top: 'stripes' }, 12, { cell: 60 });
}
// A heap of blocks in the SE corner to tuck prizes behind.
block('block_se1', 4100, -6100, 300);
block('block_se2', 3750, -6150, 200);
block('block_se3', 4150, -5750, 180);
// --- markers: checkpoint node z is star height + 50 (checkpoint_gametype subtracts 50)
const cp = (i: number, x: number, y: number, surface: number) => ({ name: `checkpoint_${i}`, position: [x, y, surface + 150] as Vec3 });
const prize = (i: number, x: number, y: number, surface = 0) => ({ name: `prizes_${i}`, position: [x, y, surface + 30] as Vec3 });
const markers: Marker[] = [
{ name: 'ssrc_start', position: [3000, -5100, 30], heading: 0 },
cp(1, 3000, -3150, 0),
cp(2, 3000, 800, CHEST_H),
cp(3, 2400, 5400, 0),
cp(4, -1100, 5400, 0),
cp(5, -3500, 4300, 0),
cp(6, -1900, 1500, 0),
cp(7, -1900, -1150, DECK),
cp(8, -3400, -4400, 0),
cp(9, -1000, -5700, 0),
cp(10, 1700, -5000, 0),
// Prizes: off the racing line, in corners, behind and on top of things.
prize(1, 4250, -6350),
prize(2, 3900, -5900, 0),
prize(3, 2000, -300, CHEST_H),
prize(4, 4000, 2300, CHEST_H),
prize(5, 2000, 2300, CHEST_H),
prize(6, 3950, 3100),
prize(7, 3950, 5700),
prize(8, 4300, 6300),
prize(9, 1200, 6300),
prize(10, 730, 5530, 66),
prize(11, -300, 6300),
prize(12, -2600, 6300),
prize(13, -3550, 6470, 490),
prize(14, -4300, 6300),
prize(15, -4300, 3800),
prize(16, -3700, 2500, 160),
prize(17, -2700, 2100, 200),
prize(18, -4300, 900),
prize(19, -1900, -1150, 0),
prize(20, -1900, -700, DECK),
prize(21, -2600, -3200),
prize(22, -4300, -2500),
prize(23, -4300, -6300),
prize(24, -3000, -6300),
prize(25, -1000, -5900),
prize(26, 0, -6300),
prize(27, 1500, -6300),
prize(28, 2650, -3900, 380),
prize(29, 3400, -3300, 220),
prize(30, 1000, -4000),
prize(31, 0, -300, 160),
prize(32, 0, 1400),
prize(33, -1500, 3200),
prize(34, 1300, 3000),
prize(35, 1500, -2200),
prize(36, 1600, 900),
prize(37, 4300, -1500),
prize(38, 4350, 600),
prize(39, -900, 0),
prize(40, 4100, -6100, 300),
];
return { pieces, markers };
}
