makepad/examples/gamemaker/resources/fixtures/racing.splash
Admin c13c867547 Arcade M1b: libs/game/blocks — car/character/plane, brains, race kit; racing game in 72 lines
The thesis proof: examples/gamemaker/resources/fixtures/racing.splash is a
complete playable racing game in 72 lines (12-corner oval from a waypoint
loop, 4 cars, gates, standings, restart) with no physics, no AI and no lap
bookkeeping in script. Blocks run engine-side at 60Hz: Blocks::pre_step
(intent -> motion) before step_world, post_step after; Blocks is Clone and
snapshots beside GameWorld so a failed eval rolls both back together.

- game.car (4 suspension raycasts on a box3d rigid chassis), game.character
  (drives the existing mover sweep + owns idle/walk/run blending),
  game.plane; game.drive/autodrive/speed
- Brains: game.wander/chase/patrol/caught — the fixture's hand-rolled AI,
  absorbed engine-side
- Race kit: spawnpoint, checkpoint, place, race, standings, lap/rank/
  finished, score/score_of (Shared-tier data, ready for replication)

raycast_vehicle audit (defects documented in car.rs, still live in xr):
libm sin/cos in steering (unreplicable), a wrong side-impulse denominator
(iaj.dot(iaj) where it should be (I^-1 aJ).aJ), and an unguarded division.
Kept the structure, replaced Bullet's friction solver with an arcade force
model: suspension acts at the contact point, grip and drive through the
centre of mass, steering as yaw torque — no lateral force can generate
roll, so it is stable by construction rather than by roll_influence fudge.

Engine bug fixed at the source: GameWorld::new() left rng at 0 and
xorshift64* is a fixed point at zero, so rand() returned 0 forever for any
world built through the sim API. reset_content seeds on every eval, which
hid it from gamemaker entirely; found by a wander brain that never left home.

Tape probe byte-identical; racing fixture evals clean and drives (AI follow
the line, gates bank in order).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 00:54:22 +02:00

72 lines
2.6 KiB
Text

// Speedway — a complete 4-car race built only from blocks.
// The engine drives the cars, tracks the laps and sorts the standings; this
// file is layout and rules. Compare with sandbox3d.splash, where every actor
// hand-rolled its own physics and AI.
let LAPS = 3
let TRACK = [
vec3(0, 0, -60), vec3(34, 0, -52), vec3(52, 0, -30), vec3(56, 0, 0),
vec3(52, 0, 30), vec3(34, 0, 52), vec3(0, 0, 60), vec3(-34, 0, 52),
vec3(-52, 0, 30), vec3(-56, 0, 0), vec3(-52, 0, -30), vec3(-34, 0, -52),
]
let COLORS = [#xe8594f, #x4f8fe8, #x4fe87a, #xe8d24f]
let NAMES = ["You", "Rosa", "Kim", "Bex"]
game.sky({})
game.terrain({size: 260, cells: 81, smooth: true, seed: 4, freq: 0.006, amp: 3,
plaza: {r: 120, ramp: 20, h: 0}, color: #x6b8f4a})
// Track surface: a slab under every corner, plus barriers on the outside.
for p in TRACK {
game.box({pos: vec3(p.x, 0.1, p.z), size: vec3(22, 0.2, 22), color: #x3b3f47, collide: false})
let out = p.normalized()
game.box({pos: vec3(out.x * 15 + p.x, 1.2, out.z * 15 + p.z),
size: vec3(6, 2.4, 6), color: #xd8d8d8, tag: "wall"})
}
// Start grid + gates. One spawnpoint and one checkpoint per corner.
for i in 0..TRACK.len() {
game.checkpoint({pos: vec3(TRACK[i].x, 3, TRACK[i].z), size: vec3(11, 6, 11)})
}
for i in 0..4 {
game.spawnpoint({pos: vec3(-6 + i * 4, 1.5, -60 + (i % 2) * 6), yaw: 0})
}
// Four cars: seat 0 is the player, the rest follow the racing line.
let cars = []
for i in 0..4 {
let car = game.car({color: COLORS[i], player: i == 0, top_speed: 26 - i * 0.5})
game.place(car, i)
game.label(car, NAMES[i])
if i > 0 {
game.autodrive(car, {points: TRACK, pace: 0.86 + i * 0.03})
}
cars.push(car)
}
game.camera({chase: cars[0], height: 3.2, boom: 13, pitch: -0.22, lag: 0.25})
game.race({laps: LAPS})
let done = false
game.on_tick(|dt, input| {
// Standings board, leader first.
let board = ""
let order = game.standings()
for i in 0..order.len() {
let s = order[i]
board = board + (i + 1) + ". " + game.tag(s.entity) + " lap " + min(s.lap + 1, LAPS) + "/" + LAPS + "\n"
}
game.text("board", board, {anchor: "top_left"})
game.bar("speed", game.speed(cars[0]) / 26, {anchor: "bottom"})
if !done && game.finished(cars[0]) {
done = true
game.text("center", "P" + game.rank(cars[0]) + " — press R to race again", {})
game.jingle("C5 E5 G5 C6", 140)
}
if input.reset_pressed {
done = false
game.text("center", "", {})
for i in 0..4 { game.place(cars[i], i) }
game.race({laps: LAPS})
}
})