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>
72 lines
2.6 KiB
Text
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})
|
|
}
|
|
})
|