Visit The Shed and one of the locals is waiting on the driveway with a
broken shitbox and cash: their actual car model parked there under a
floating Label3D job sign -- who they are, what they reckon is wrong
("it makes a noise at church speed"), the honest fault name, and the
offer. Pull up beside it and hold still to take the job; the wrench
minigame opens on THEIR car.
The roster is the extended universe: Nanna, P-Plate Pepper, Mullet
Merv, Torque'n Tezza, the Self-Described Bathurst Legend, Your Mate
Dazza (don't tell Shazza), Karen from Number 12, and the Widowmaker,
who says nothing and pays cash. Roster and fault rotate
deterministically off jobs_done.
Settlement: full pay at 60+, a $50 tip at 85+, half below 60 -- and
SHED REP moves with the workmanship (+3 clean, -6 botched, cap 40),
scaling the next offer from 70% of shop rate at rep 0 to 100% at cap.
Rep shows on the menu and garage header. Customer jobs bank NO quality
buffs: the customer's car id might be a car the player owns, and
fixing Merv's Kingswood must not secretly tune yours -- the wrench
consumes Garage.ui_customer and routes _finish to customer_done()
instead of clear_fault/set_quality.
The shed guards the spawn (parent is Game and mode == cruise) so the
wrench scene's backdrop copy of the level never summons a customer
mid-job. The spare parked shitbox moves to the kerb; the driveway
belongs to commerce now.
Smoke: customer waits after a shed visit, holding beside the spot takes
the job (flag-guarded for headless), the driven wrench job pays, rep
rises, the driveway clears, and the customer's own-car quality stays
untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
445 lines
26 KiB
Markdown
445 lines
26 KiB
Markdown
# 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.
|
||
|
||
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.
|
||
|
||
### 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.
|