# Burnout Infinity: Shitbox Burnout 3-style arcade racer, Aussie shitboxes, Woolies carparks. Godot 4.6 + Blender, native Apple Silicon. ## Run ``` /Applications/Godot.app/Contents/MacOS/Godot --path . ``` Controls: WASD/arrows drive, Shift drift, Space boost, R reset (or retry after Crash Mode), Esc menu. ## Modes - **Cruise** — free roam with traffic, plus two rivals lapping the circuit. Pull alongside one at speed and it's an **Outrun** (NFSU2 rules, our code): first to gap the other by 250 m wins. Straight-line distance decides *when* it ends — so ducking down a side street is a real escape — but path order decides *who* won, so reversing away from the circuit is a loss. Winning pays takedown-tier boost. R mid-duel is a forfeit. - **Crash Mode** — 60 seconds, rack up damage dollars ramming traffic and parked cars. - **Crash Junctions** — pick a "CRASH JCT" entry in the level list: launch into scripted cross-traffic convoys at a real intersection, then Aftertouch-steer your wreck (arrows) and detonate the Crashbreaker (space). Damage tally + bronze/silver/gold. Events live in `levels//junctions.json`, regenerated in one go by [tools/build_all_junctions.sh](tools/build_all_junctions.sh) — which is also the only record of *which two streets each event is*, so keep the spec list in it current. Medal targets come from [tools/tune_junctions.gd](tools/tune_junctions.gd), which plays every event headless with varied launches and sets bronze/silver/gold at the 35th/65th/88th percentile of what was actually achieved. They were previously one uniform 15/35/60k guess across every event, which measurement showed was nonsense — achievable damage ranges from a couple of thousand to nearly $100k depending on the intersection. Percentiles rather than the max, so gold is a good run and not a freak one. ``` Godot --headless --path . --script tools/tune_junctions.gd -- runs=14 ``` Two traps worth knowing. It reloads the level for every run, so a full pass is slow — budget tens of minutes. And **nothing else may touch Godot or the levels while it runs**: a second Godot instance contends for the `.godot` import lock, and a level rebuild mid-flight silently feeds it half-written levels, which reads as "this event scores zero" rather than as an error. - **Race** — 3 laps vs 3 rubber-banded rivals on the level's `RacePath`. - **Drag** — NFSU2's event, our code: a straight-line run down the level's longest genuinely straight stretch of wide road (the builder finds it and drops `DragStart`/`DragEnd` markers, so no level needs hand-authored coordinates). The gearbox *is* the game — revs climb, **space shifts**, and shifting inside the window pays while an early shift or sitting on the limiter bogs you. One opponent alongside. - **Derby** — Destruction Derby in the Woolies carpark: five opponents, last one running wins. They use the pursuit AI, each hunting a different car so it's a brawl rather than a mob, and leaving the ring for 8 s forfeits. - **Pursuit** — not a menu mode; it happens *to* you in Cruise. Smashing things fills a heat meter, and at full heat the police turn up and hunt you. Open 250 m for 6 s to lose them, or get pinned for 4 s and you're busted. Heat decays if you behave, so picking a fight is a choice rather than a timer. The boost loop is Burnout 3's: earn boost by drifting, near-missing traffic, and smashing things. Wrecking a rival near you = TAKEDOWN — refills the meter and grows it (up to 4x). Hard crashes shed body panels, crumple the bodywork and trigger Impact Time slow-mo. ## Crash deformation and damage [src/deform.gd](src/deform.gd) implements the control-hull model from the design PDF: a 3×3×5 lattice of control points encloses the car, an impact shoves the nearby ones inward, and every mesh vertex follows by an inverse-distance-weighted blend (`W_i = 1/‖p_j − c_i‖^α`, α = 2). The trick that makes it free: those weights depend only on *original* positions, so they're precomputed once per car and each impact is a plain weighted sum — **0.3 ms**, no distance maths, no per-frame cost. Panel travel is clamped (`MAX_PULL`) per the PDF's bounding-box safety limits so bodywork crumples instead of folding through itself. Only the player and rivals carry a deformer; traffic and parked cars are plain bodies. Paint damage rides the same numbers: total control-point travel drives the albedo dulling toward dirty grey, the roughness climbing, and (past 35% damage) a scratch detail layer switching on — so the scuffs and the crumple can never disagree. Meshes *and* materials are deep-copied per car; share either and denting one Kingswood dents every Kingswood in the level. ## Cars A car is a folder: `cars//stats.tres` + optional `car.glb`. Models are built procedurally — specs live in [tools/build_cars.py](tools/build_cars.py), one dict per car (silhouette profile, dimensions, paint). Rebuild all GLBs with: ``` /Applications/Blender.app/Contents/MacOS/Blender -b -P tools/build_cars.py ``` Model convention (any GLB works, hand-modelled or generated): - Nose faces **+Y** in Blender (imports as Godot -Z forward). - Node origin 0.75 m above ground level (the controller's ride height). - Objects named `det_*` are detachable — the game rips them off on impact (doors, mirrors, bumpers, bonnet, boot). - Current fleet: 30 shitboxes, from the Datto 120Y to the FLOORED LAZZA 1.5 ('82 Laser hatch, rear louvres that shed slat-by-slat — John's actual first car, built from his photos). Piss-take names on purpose: real model names and badges are trademarks. To improve a silhouette: supply a side-on photo + front three-quarter photo, tweak the profile points in the spec, rebuild, screenshot. **Stats are low-key reversed**: the shitboxier the car was in real life, the better it performs here. The Excel X3 is the fastest car in the game; the muscle cars are all bark. Downloaded models (from `shitboxes/`, gitignored + .gdignore'd) convert via: ``` /Applications/Blender.app/Contents/MacOS/Blender -b -P tools/import_shitbox.py -- "" [yaw_deg] ``` Imported cars don't shed det_* panels until someone cuts their doors out in Blender. ## Real-world levels from OpenStreetMap Real street grids, our art style, clean collision, ODbL-licensed. Fetch a bbox from the OSM API, convert, build (queen_st_mall was made exactly like this): ``` curl -A 'BurnoutShitbox/1.0' 'https://api.openstreetmap.org/api/0.6/map?bbox=,,,' -o area.xml python3 tools/osm_xml_to_json.py area.xml area.json # see git history / osm_level_args.py ARGS=$(python3 tools/osm_level_args.py area.json "" "") Blender -b -P tools/build_level_osm.py -- area.json $ARGS ``` Don't reconstruct from Street View imagery (ToS + melted collision). Use photos as reference to hand-detail hero landmarks on the OSM base. Levels so far: woolies_carpark, queen_st_mall, southbank, albert_parklands, story_bridge (procedural steel truss along Bradfield Highway), kangaroo_point. Area JSONs are cached in `~/.cache/burnout_osm` (override with `$OSM_CACHE`); `tools/build_all_levels.sh` rebuilds every level from them. ### What the builder makes of the OSM data Beyond the street grid and building footprints, it uses the furniture OSM already carries — street lamps, benches, bins, bollards, bus stops and the whole footway network — so most of the detail is real Brisbane, not invented: - **Buildings** extrude with a glazed shopfront band at street level, a cantilevered awning over the footpath (three canvas colours, varied heights, ~70% of buildings — a whole street at one height in one colour reads as a painted stripe), a roof parapet, and a plant room on anything tall. - **Footpaths** come from the OSM footway ways, raised 14 cm with a chamfered kerb. The chamfer is deliberate: a hard vertical edge at 120 km/h reads as a crash to the jolt detector in car.gd. - **Roads** get dashed centre lines; `footway=crossing` becomes zebra bars laid flat on the road instead of a raised strip you'd launch off. - **Trees** are a trunk plus three offset welded spheres (figs) or radiating fronds (palms), scattered along kerbs on top of the ones OSM maps — Brisbane has far more street trees than are tagged. - **Smashables**: the builder drops marker empties and game.gd fills them at load with RigidBodies — `PP_` kerbside/carpark parking spots become shuntable fleet cars, and `SL_`/`BO_`/`WB_` (OSM street lamps, bollards, bins) become topple-able street furniture with a low centre of mass so a clipped lamp post goes over rather than helicoptering. All asleep until rammed, and skipped entirely in junction mode to keep those puzzles deterministic. Lamps and bollards are deliberately *not* baked into the level mesh — a static pole you drive through is worse than no pole. - **Brisbane colour**: red busway asphalt, jacaranda-purple canopy on ~1 in 5 street trees, Queenslander weatherboard + corrugated iron on `building=house` footprints, fictional shop-fascia signs above awnings and rooftop billboards in three designs (FLUX gibberish lettering on purpose — no real brands). - **Traffic signals** from `highway=traffic_signals` — pole, mast arm and two lantern heads, with emissive lenses so they read as lit in a shaded canyon. - **Power poles + sagging wires** down one side of residential streets, the single most Australian thing a suburban street can have. - **Southbank landmarks** (hardcoded per level id): the Wheel of Brisbane placed off its mapped ticket office, and the Grand Arbour's curling steel tendrils + bougainvillea following the actual mapped footway. - **Queen Street Mall's winged canopies** grow along pedestrian ways whose name starts with the `canopy` argument. Note OSM splits the mall into a 15 m stub named "Queen Street Mall" plus the 438 m pedestrian way named just "Queen Street", hence prefix matching. Variation is seeded off OSM ids, so rebuilds are reproducible. ### Textures `assets/textures/` is FLUX-generated. Regenerate the whole set with `tools/gen_textures.sh` or one with `tools/gen_textures.sh `; creds are read from `.env`, never printed. Cloudflare Workers AI first, then the **local MODELBEAST farm** as an automatic fallback ([flux_local.py](tools/flux_local.py), operator `flux_local`). This is not optional nicety: Cloudflare's free tier is 10,000 neurons/day and one full set regen exhausts it, after which every call 429s. The fallback is transparent — `gen_textures.sh` output looks identical either way. Two things worth knowing before you re-prompt: - FLUX returns a **near-white blank sheet** for anything described as pale ("off white metal panel" came back at 240/255 however it was worded), and a blown-out albedo renders as a featureless void in game. The spec table's last column is a target mean luma that clamps this deterministically. - `tools/check_textures.py` reports mean/contrast for the set and flags the duds. Run it after regenerating. The builder box-projects UVs at a per-material metres-per-tile scale; facades pick glass/brick/concrete/heritage/sandstone by height, building tag and id hash. `woolies_carpark` is the one level still built in GDScript rather than as a GLB ([carpark.gd](levels/woolies_carpark/carpark.gd)) — its parked shitboxes have to stay shuntable RigidBodies, which a static GLB can't carry. It picks its parked cars at random from the real fleet. Its Trolley Run junction runs convoys along the aisles at `z = 15/12` and `z = -21/-24`, so keep those lanes clear. ## Add a level Make a folder `levels//` containing `level.glb` (or `level.tscn`). Blender convention: - Solid objects: suffix the name with `-col` (Godot generates collision on import). - An Empty named `Spawn` where the car starts. - A curve/path named `RacePath` (loop) enables Race mode and traffic. - Export glTF Binary to `levels//level.glb`. It appears in the menu. ## Tests ``` /Applications/Godot.app/Contents/MacOS/Godot --headless --path . --import /Applications/Godot.app/Contents/MacOS/Godot --headless --path . --script tests/smoke.gd ``` Visual check (opens a window, saves a PNG): ``` /Applications/Godot.app/Contents/MacOS/Godot --path . --script tests/screenshot.gd -- car=datto_120y mode=crash frames=280 out=shot.png ``` ## Design doc `docs/BurnoutInfinityShitbox.pdf` — research report on the original game. Still to come: crash deformation (control-hull vertex displacement, equations in the PDF), oncoming-lane boost, Road Rage mode, sound, real level GLBs from Blender.