ShitboxInfinity/README.md
m3ultra 1753b4c0b0 SPOTTO: knock the fuel cap off a yellow one
Burnout's yellow warning diamonds are the game telling you it noticed,
and half of why it feels good. src/callout.gd builds them: diamond,
outlined text, punch-scale in (overshoot then snap, which reads as
IMPACT), stack of three with the older ones drifting up and out.
TAKEDOWN!, NEAR MISS, BIG AIR with the airborne time -- and the new
Australian one.

Roughly one traffic car in six is now painted yellow, deterministically
per car so junction convoys stay learnable puzzles, via a surface
OVERRIDE -- painting the shared fleet material would have turned every
copy of that model yellow across the whole city, the same trap the
garage's stat upgrades hit.

Sideswipe any car hard enough and its FUEL CAP pings off: a real
physics prop, chrome (or yellow), spat off the correct flank behind the
midline with sparks, tumbling into the road for ten seconds. Do it to a
yellow one and it's SPOTTO! -- 0.45 boost, cash or crash-score, a
running session tally, and a sting pitched up 35%. Merely near-missing
a yellow one at speed still earns the shout, because that IS the game
as played in the back of every Falcon on the Bruce Highway.

Sideness is judged in the victim's local frame before it goes dynamic
(|x| vs |z|), so a rear-ender is a shunt, not a sideswipe -- asserted,
along with one-cap-per-car and the cap prop actually existing.

Airtime rides along: car.gd tracks time with no wheel down and emits
landed() past 0.55 s, which pays boost and shouts BIG AIR.

Callout placement note: Control.position is relative to the PARENT, not
the anchor, so anchoring the row and then tweening position:y yanked
every badge to the top of the screen. Screen-space maths instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 13:51:09 +10:00

509 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Shitbox Infinity
**Shitbox Infinity** (nee 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/<id>/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.
The tuner is also the event *validator*. An event that scores nothing across every
run isn't badly tuned, it's unplayable, and it prints `!!` for exactly that. Three
separate causes turned up that way, none visible by reading the generator:
convoy streams clipped so wide that you thread the intersection through a 20 m gap;
a launch point on the street centreline threading cleanly *between* the two convoy
lanes; and `int(L / headway)` flooring to one car on a short stream, leaving nine
convoy cars where the working events have twenty. Re-run it after any change to the
generator, the launch geometry, or convoy spacing — all three feed the numbers.
Read the spread it prints, not just the targets. **Cliffs Corner** and **Bridge
Approach** both show median == max at a low ceiling (~$4.7k), which means the run
reliably ends on the first thing you hit and the convoy is never reached — they're
winnable and honestly tuned, but they're scenery crashes rather than pile-ups. Both
sit on curved approaches where the launch street bends away from the intersection.
Worth hand-picking better crossings for those two.
- **Race** — 3 laps vs 3 rubber-banded rivals on the level's `RacePath`. The
proper venue is **Queensland Raceway** (`levels/qld_raceway`) — "the
Paperclip", the National Circuit digitised from the official track map and
scaled to its real 3126 m lap: three tilted parallel straights, the T2
hairpin, the inner loop, the T6 fishhook, kerbs and gravel traps derived
automatically from curvature, sponsor hoardings down the straights, the pond,
and the red pit complex. The road is one generated quad strip with trimesh
collision (wound clockwise: Godot culls CCW faces, and the first cut of the
ribbon was invisible from above while colliding perfectly).
- **Road Rage** — Burnout 3's headline mode: 90 seconds, wreck as many rivals
as you can. They recover and keep driving so the supply never dries up, and a
leash teleports strays back into play. Bronze/silver/gold at 4/8/12.
- **Oncoming** — in Cruise, Race and Road Rage, driving against the traffic
flow while actually on the road earns boost. The oldest deal in Burnout.
- **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.
The proper venue is **Willowbank Dragway** (`levels/willowbank_dragway`, built
in code like the carpark): a real 402 m with a concrete launch pad, rubbered
lanes, crowd grandstand, red control tower, sand trap and tyre wall — and a
working **christmas tree**. Every drag now stages at the tree: three ambers
half a second apart, launch on the green. Roll more than 2 m early and it's a
**RED LIGHT** — you launch bogged while the tree makes sure everyone saw.
The opposition is a **ladder** of six preset locals, weakest first: NANNA'S
SHOPPING RUN, P-PLATE PEPPER, MULLET MERV, TORQUE'N TEZZA, a SELF-DESCRIBED
BATHURST LEGEND and THE WILLOWBANK WIDOWMAKER. Each has its own car, power and
reaction time (Nanna sits at the green for most of a second; the Widowmaker
leaves with you). Beat your current rung to climb and collect the purse
($250 up to $1600, and the top dog repeats). Crossing at all pays $50, and
every car keeps a persistent **personal best ET** — beating it pays $150 and
is recorded in the garage save. Losing doesn't end the run early: you still
get to cross for your ET.
- **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.
## Speed, and SPOTTO
Burnout 3 ran about 110 km/h and felt like 300, so **everything that sells
speed is a lie** ([src/speed_fx.gd](src/speed_fx.gd) + `_speed_cam` in
game.gd): radial blur, Sonic-style speed lines, chromatic aberration folded
into the blur as per-channel sample radii, a tunnel vignette on boost; a
camera that drops and hugs the boot at pace, lags on hard acceleration,
dutch-rolls into the steering and micro-shakes; FOV running 72°→96° with
speed, +12° boosting and a +9° **punch** the instant boost lights; motes
streaming past the lens; and a wind loop riding the same curve. One input
(km/h), squared response, cut in at 55 and maxed at 165 — town speed looks
normal, then it piles on. Boost is deliberately the *visual event*.
**Callouts** ([src/callout.gd](src/callout.gd)) are Burnout's yellow warning
diamonds — the game telling you it noticed, which is half of why Burnout feels
good. Stack of three, punch-scaled in, older ones drifting up: TAKEDOWN!,
NEAR MISS, BIG AIR (with the time), FUEL CAP! and —
**SPOTTO.** Roughly one traffic car in six is painted yellow (a surface
*override*, never the shared fleet material). Sideswipe any car hard enough
and its **fuel cap pings off** into the road with sparks — a real physics prop
that rolls away. Do it to a yellow one and that's the national game: SPOTTO!,
big boost, cash or crash-score, and a running tally for the session. A
rear-ender doesn't count (that's a shunt, and the suite asserts it), and
merely near-missing a yellow one at speed still earns the shout, because
that *is* the game as played in the back of every Falcon on the Bruce Highway.
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.
## The Garage
Earn **bucks** playing (takedowns $150, outrun wins $400, escapes $500, derby
kills $100, medals up to $1000, plus a cut of crash-mode damage) and spend them
in the GARAGE on performance parts of deeply questionable provenance: the
K-MART MEGA WING, GO-FASTER STRIPES, the SERVO FLAME KIT, BLINGY MAGS, SERVO
NEONS, the BUNNINGS DRAINPIPE and the TURBO THRU THE BONNET. Every part gives a
real stat bump AND bolts a procedural eyesore onto the car -- progression you
can see from across the carpark. Fully kitted is roughly +35% power, +1.1 grip
and +0.55 boost.
**The Shed** is the second path: workshop tools bought once, globally --
SUPERCHEAP WHEEL RAMPS, the CASHIES CODE READER (one previous owner, screen
cracked), an ALDI SOCKET SET, a BUNNINGS AIR COMPRESSOR, the CASHIES CHAIN
HOIST (on wheels, definitely not stolen) and a SERVO ADDITIVE CRATE. Each tool
unlocks a per-car **tune-up** at labour prices: dodgy wheel alignment, check
engine delete ("the light WAS the problem"), 60 PSI in every tyre, a head skim
and lumpy cam, an octane booster habit -- and RETORQUE EVERYTHING, which is the
one thing parts can't buy: panels that hang on through bigger hits (it raises
the car's shed-a-panel and wreck thresholds). Parts buy bling; the shed buys
labour.
**Faults and the repair minigame.** What you break follows the car home:
grenading the gearbox (three blown shifts in one drag run) or blowing the head
gasket (cooking the engine on the limiter) records a persistent **fault**, and
the car boots WOUNDED — down on power, leaking oil patches or steaming — until
it's fixed. The garage screen offers two routes: pay the shop's
"someone else's problem" rate, or **GET UNDER IT** in the shed for free. That
opens the repair minigame ([src/repair_ui.gd](src/repair_ui.gd)): diagnose off
the dash warning lights first (the CASHIES CODE READER names the fault through
its cracked screen — "D!IV!TR!IN" — and points at the right light; without it
you're on vibes, and a wrong guess adds penalty turns to every nut), then work
through drag / click / twist steps — hold-and-circle the mouse to spin nuts
(righty-tighty enforced; a full turn the wrong way earns "OTHER WAY."), pump
the jack by clicking, drag the dead box to the bench. Owning the right shed
tool shortens the job: sockets halve every nut, ramps halve the jacking, the
hoist deletes the 60 kg gearbox-drag step entirely, and finishing without the
right tools leaves three bolts over. The whole minigame routes through a
testable API (`pick_light`/`twist`/`pump`/`drop_at`), so the smoke suite plays
a complete toolless gearbox swap headless.
Purchases are per-car (tools are global) and persist in `user://garage.save`,
which also carries the drag **ladder rung**, per-car **quarter-mile PBs** and
outstanding **faults**.
**The wrench minigame** ([src/wrench.gd](src/wrench.gd)) is the 3D half of
repairs, phase 1: flat tyre, oil change and dead battery happen on YOUR car,
parked in YOUR shed, through five mouse verbs on 3D anchor points -- GRAB
(part follows the cursor, drop it at the target), TWIST (circle the mouse;
torque steps have a seat window and blowing past it STRIPS the thread, adding
an extraction step), PUMP (rhythm clicks), YANK (flick), and STEADY (keep the
cursor in a wandering ring). Every job scores 0-100 for workmanship; finish at
85+ and the fix is a live buff via Garage.apply (grip for tyres, power for
oil, boost for battery) until that fault recurs -- your hands are the tune.
Faults have gameplay sources: a red light at the tree flat-spots the fronts,
every 4th wreck rattles the sump loose (oil light), and getting busted kills
the battery, because the divvy van jump-starts it wrong out of spite. The
whole thing routes through an API (`twist/pump/grab/drop_at/yank_ok/
steady_tick/clamp_on/pick_light`), so the smoke suite plays a clean flat-tyre
job (quality 90+, buff verified on next boot) and a sabotaged oil job
(stripped thread, extraction step) headless.
Phase 2 finishes the port: **gearbox and gasket are wrench jobs now** (the 2D
panel is gone) and both open with **diagnosis** -- three lit warning lights,
one honest, the code reader pointing at it through its cracked screen; a wrong
pick makes every nut in the job run a turn longer. The gearbox job is the
hero: no hoist means dragging 60 kg out by hand and holding the steady ring so
it doesn't land on you, while owning the crane turns it into the ceremony
(hook the chain, pump it out -- and at tier 3 the winch hooks itself). Every
tool line now has **three tiers** (Cashies -> Supercheap -> SNAP-OFF
PROFESSIONAL, buy again to upgrade): better sockets mean fewer turns and a
wider torque window, the tier-2 compressor's **rattle gun turns wheel nuts
into BRRRT clicks** (torque windows keep the hand skill -- guns strip), ramps
speed the jacking, the fluids shelf speeds pours, and the tier-3 scan tool
skips diagnosis entirely. Workmanship reaches the strip: an 85+ gearbox
rebuild takes an **extra blown shift** before grenading, an 85+ gasket job
**raises the overheat ceiling** -- both asserted in the suite.
Phase 3 makes the shed a business: **driveway customers**. Visit The Shed and
one of the locals is waiting on the driveway -- Nanna's Morry ("it makes a
noise at church speed"), Your Mate Dazza's ute ("don't tell Shazza"), Karen
from Number 12 ("I will be watching. the entire time") -- their actual car
parked there with a floating job sign naming the fault and the offer. Pull up
beside it and hold still to take the job; the wrench opens on THEIR car. Cash
on completion, a $50 tip at 85+, half pay below 60 -- and **SHED REP** moves
with the workmanship (+3 clean, -6 botched, capped at 40), scaling what the
next customer offers (70% of shop rate at rep 0 up to 100% at cap). Roster
and fault rotate deterministically off jobs done. Customer jobs bank no
quality buffs -- their car id might be a car you own, and fixing Merv's
Kingswood must not secretly tune yours.
**The Shed is also a place** (`levels/the_shed`): a drivable suburban home base
— brick house, paling fence, hills hoist mid-spin with the washing still on it,
wheelie bins at the gate, and the corrugated double shed on its slab. The tools
you own stand in it as the real TRELLIS'd cashies models (unowned tools leave
an oil-stained outline where they'll go), the rest of the library is permanent
clutter, and the pegboard is FLUX. Roll into the painted bay in Cruise and hold
still a beat — the garage opens. No RacePath, no traffic: the one quiet level
in the game, on purpose.
Upgrades apply to a duplicate of the CarStats resource -- the .tres is shared
with traffic and parked spawns of the same model, and tuning your Lazza must
not secretly tune every Lazza in the city. Bolt-ons attach after the deformer
snapshots the mesh, so the wing survives every crash, as is right and proper.
## 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/<id>/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 -- "<file>" <car_id> <length_m> [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=<minlon>,<minlat>,<maxlon>,<maxlat>' -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 "<spawn way name>" "<loop street 1,2,3,4>")
Blender -b -P tools/build_level_osm.py -- area.json <level_id> <origin_lat> <origin_lon> $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 <name>`; 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.
### The mountains (real DEM hillclimbs)
`mt_cootha` and `mt_gravatt` are the OSM pipeline pointed at actual mountains:
real terrarium elevation (Coot-tha crests at 287 m, Gravatt at 196 m — the DEM
range matches the survey figures), the real Sir Samuel Griffith Drive and
Mount Gravatt Outlook Drive stitched from their OSM way segments into the
RacePath (the `trace:` mode now joins all same-named ways end-to-end and
samples densely — one segment at 16 points cut hairpins cross-country), and
each level carries its own FLUX sky panorama with the Brisbane skyline on the
horizon (`levels/<id>/sky_<time>.jpg` overrides the global sky, any time of
day — Gravatt has a sunset one). Bushland levels pass `grass` as the builder's
9th arg: grass terrain (at 3 m tiling — 18 reads as macro-photo lawn), and a
forest fill of ~2600 extra gums, 65% hugged to within ~150 m of the roads
where the driver actually sees them. Carpark polygons are now PAVED draped
asphalt (a summit lookout carpark is a burnout pad, not a dirt patch) and
`man_made` masts/towers become lattice towers — Gravatt's summit telecom
tower stands over its carpark. Race is 3 laps of the mountain; the summit
carparks come pre-stocked with parked shitboxes.
### The Gateway (big-bridge support)
`gateway` is the Sir Leo Hielscher Bridges and their industrial hinterland.
Big concrete bridges taught the builder new tricks, all driven by the bridge
arg's new form `Name:hump:style` (Story Bridge stays `Bradfield Highway`,
16 m truss): the **box style** computes deck height as a continuous field from
**distance to the river** (smoothstep to +57 m over the water) — the Gateway
is 13 OSM ways across twin carriageways, and both a per-way hump and a
whole-bridge axis projection produced 52 m walls; the river being the thing
bridged is the only profile that can't seam. Concrete **piers** rise under
every bridge-tagged span, **guardrails** with collision chase the deck edges
(a 64 m deck with open edges is a cliff with lane markings), and the Spawn,
DragStart/End and RacePath markers all ride the deck field. The payoff: a
**1,422 m drag strip up and over the bridge** — the longest in the game — with
the ladder, tree and gearbox all live at altitude. Race mode's auto-traced
loop weaves the interchange ramps and is honestly chaotic; a hand-authored
carriageway loop is the known fix if bridge racing matters later.
### Cashies tool props (dealgod → TRELLIS)
`assets/tools/*.glb` are real secondhand-shop stock: photos pulled from the
dealgod.pro products database (~82k tool listings with images), curated one
clean shot per class, then run through the MODELBEAST farm with
[tools/cc_to_glb.py](tools/cc_to_glb.py) — `bg_remove_local` cutout first
(the farm's own docs call that the big quality jump), then `trellis_mac`
image→3D (1024 tier, 1024 texture ≈ prop-grade, a few MB each). The dragway
paddock strews the whole library as shuntable RigidBodies via
`TOOL_SPECS` in [dragway.gd](levels/willowbank_dragway/dragway.gd) — real-world
size per class (TRELLIS output is unit-ish) and honest masses, so clipping the
70 kg engine crane at speed is a different event to punting a grease gun.
The library is deliberately game-agnostic: photo in, scaled GLB out — reusable
for every future project. Textures inherit whatever branding was in the source
photo (they're secondhand listings), which is fine for props but don't promote
one to hero-asset status without checking what's written on it.
`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/<id>/` 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/<id>/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
```
Covers all seven modes (cruise, crash, junction, outrun, pursuit, drag, derby,
race) in about 30 s, printing a `.. stage` line as it enters each one.
Failures go through `_check()`, never bare `assert()`. A failed assert in a
headless `SceneTree` script halts the script but leaves the tree running, so the
process spins forever at 100% CPU instead of failing — it looks exactly like a
slow test. That cost 53 minutes of wall clock to spot once; the stage lines and
`_check()` exist so it can't happen twice.
Only one Godot instance may touch the project at a time: a second contends for
the `.godot` import lock and can kill the first mid-run.
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
```
## Sound
Every WAV in `assets/sounds/` is synthesized from maths by
[tools/gen_sounds.py](tools/gen_sounds.py) — sines, noise and envelopes, no
recordings, so the whole kit is licence-free by construction and reproducible
from its seed like everything else in this repo. (Run it through Blender: it's
only there for the bundled numpy, no bpy involved.)
Loop discipline: anything the game loops (engine, skid, boost, siren) is built
from integer-Hz components over exactly 1 s so the seam is mathematically
silent; noise beds get a crossfaded seam. Loop flags are set in code
([sfx.gd](src/sfx.gd)), not per-file import settings, so a regenerated wav never
silently loses its loop.
Wiring: every car runs a pitch-tracked engine loop (the arcade law: idle burble
at rest, screaming near top speed, a bump while boosting) plus a skid loop off
the slip value; the player adds a boost whoosh. Crashes pick small/big by jolt
strength with glass on a wreck, pursuit cops wail a two-tone siren (derby
brawlers don't), the Crashbreaker booms, the gearbox clunks or bogs, and event
results sting up or down.
If the kit ever wants sweetening beyond synthesis, use **CC0 sources only**
(kenney.nl, OpenGameArt filtered to CC0) — CC-BY means shipping attribution
forever and NC-licensed audio doesn't belong in this repo at all.
## Design doc
`docs/BurnoutInfinityShitbox.pdf` — research report on the original game. Still to come:
a DEM elevation pass (so the Story Bridge deck actually rises), hand-picked crossings
for the two scenery-crash junctions, and splitting wheel meshes out of the 13 imported
cars so they spin like the procedural fleet's do.