ShitboxInfinity/tests/smoke.gd
m3ultra 21032191a3 Raceday: Willowbank quarter mile + the Queensland Raceway Paperclip
Two proper venues off the real Ipswich pair, both built in code.

Willowbank Dragway (levels/willowbank_dragway): a true 402 m dual-lane
strip -- concrete launch pad, FLUX-rubbered lanes, crowd grandstand with
canopy, red control tower, distance boards at 60ft/eighth/1000ft,
checkered finish under a scoreboard gantry, sand trap and tyre wall,
smashable SL_ light towers and a staging paddock of parked fleet cars.
Between the lanes: a working christmas tree, six bulbs driven by game.gd
through material_override emission.

Drag mode grew the event around it. Every drag now stages at the tree
(three ambers, green at 2.7 s); rolling >2 m early is a RED LIGHT that
bogs your launch. The opposition is a six-rung ladder of preset locals
(Nanna's Shopping Run through The Willowbank Widowmaker), each with its
own fleet car, power and reaction time. Beating your current rung climbs
the ladder and pays an escalating purse ($250-$1600, top dog repeats);
crossing at all pays $50; and every car keeps a persistent quarter-mile
PB worth $150 when beaten. Losing no longer ends the run early -- you
still cross for your ET, with a 10 s grace if you dawdle. Ladder rung
and PBs persist in the garage save (schema extended compatibly).

Queensland Raceway (levels/qld_raceway): the National Circuit digitised
from the official track map and scaled to its exact 3126 m lap. The road
is one generated quad strip with trimesh collision; kerbs, gravel traps
and tyre stacks derive automatically from centreline curvature, sponsor
hoardings space themselves down the straights, and the SF straight gets
the checkered line, gantry, pit wall and the mandatory red pit complex.
Plus the pond. Race/cruise/rage all run on its RacePath.

Trap for the diary: Godot front faces wind CLOCKWISE. The first cut of
the road ribbon wound CCW and rendered invisible from above while
colliding perfectly -- the smoke suite passed and the screenshot showed
four cars gridded on open grass.

Three new FLUX textures (crowd, dragstrip, sand; the sand prompt tripped
Cloudflare's NSFW filter, of all things, and fell back to local). Smoke
suite covers tree staging, the held rival, rung-0 identity, green-bulb
emission, red-light innocence, PB/rung persistence and the 3126 m lap.
Also swept stale shot_*.png.import debris and ignored the pattern.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 11:43:08 +10:00

294 lines
13 KiB
GDScript

extends SceneTree
## Headless smoke test:
## /Applications/Godot.app/Contents/MacOS/Godot --headless --path . --script tests/smoke.gd
## Registry finds content; each mode boots and simulates; the car drives; traffic
## gets smashed when driven through.
func _initialize() -> void:
_run()
func _run() -> void:
var cars := Registry.cars()
var levels := Registry.levels()
_check(not cars.is_empty(), "no cars found")
_check(not levels.is_empty(), "no levels found")
for p in cars.values():
var s: CarStats = load(p)
_check(s != null and s.engine_power > 0.0, "bad stats: %s" % p)
if s.model_path != "":
_check(load(s.model_path) is PackedScene, "bad model: %s" % s.model_path)
# datto is procedural (has det_* parts); imported models legitimately have none
Game.car_path = cars.get("datto_120y", cars.values()[0])
# the carpark is the controlled arena; city levels have walls that complicate the ram test
Game.level_path = levels.get("woolies_carpark", levels.values()[0])
# garage economy: scratch save (NEVER the real one), earn/buy roundtrip,
# stats + bolt-ons applied to the player, then reset so later stages run stock
print(" .. garage")
Garage.save_path = "user://garage_smoketest.save"
Garage._reset_for_test()
_check(Garage.bucks() == 0, "scratch garage not empty")
Garage.earn(4000)
_check(Garage.bucks() == 4000, "earn didn't bank")
_check(Garage.buy("datto_120y", "spoiler"), "couldn't buy the wing")
_check(Garage.buy("datto_120y", "turbo"), "couldn't buy the turbo")
_check(not Garage.buy("datto_120y", "spoiler"), "bought the same wing twice")
_check(not Garage.buy("datto_120y", "exhaust"), "bought a $2000 pipe with $500")
_check(Garage.bucks() == 500, "change is wrong (%d)" % Garage.bucks())
var base_power: float = (load("res://cars/datto_120y/stats.tres") as CarStats).engine_power
var gg: Game = await _boot("cruise")
_check(gg.car.stats.engine_power > base_power * 1.15, "turbo didn't boost power")
_check(gg.car.stats.grip > (load("res://cars/datto_120y/stats.tres") as CarStats).grip + 0.5,
"wing added no grip")
_check(gg.car.stats.resource_path == "", "upgrades mutated the SHARED stats resource")
gg.free()
# the shed: tunes are gated on tools, and retorque hardens the panels
Garage.earn(2000)
_check(not Garage.buy_tune("datto_120y", "retorque"), "tuned without owning the tool")
_check(Garage.buy_tool("sockets"), "couldn't buy the socket set")
_check(Garage.buy_tune("datto_120y", "retorque"), "tool owned but tune refused")
_check(not Garage.buy_tune("datto_120y", "retorque"), "same tune sold twice")
gg = await _boot("cruise")
_check(gg.car.part_dv > Car.PART_DV + 1.0, "retorque didn't harden panels (%.1f)" % gg.car.part_dv)
gg.free()
# raceday persistence: ladder rung and per-car quarter-mile PBs
_check(Garage.drag_rung() == 0, "fresh save not on ladder rung 0")
Garage.drag_advance()
_check(Garage.drag_rung() == 1, "ladder didn't advance")
_check(Garage.record_drag_pb("datto_120y", 14.5), "first ET not a PB")
_check(not Garage.record_drag_pb("datto_120y", 15.0), "slower ET counted as a PB")
_check(Garage.record_drag_pb("datto_120y", 13.9), "faster ET refused as PB")
_check(absf(Garage.drag_pb("datto_120y") - 13.9) < 0.01, "PB not stored")
Garage._reset_for_test() # later stages must run stock
# cruise: throttle 3s, car must move
print(" .. cruise")
var game: Game = await _boot("cruise")
if game.car.stats.model_path != "":
_check(game.car._parts.size() >= 8, "model has no detachable det_* parts")
var start: Vector3 = game.car.global_position
Input.action_press("throttle")
for i in 180:
await physics_frame
var dist := start.distance_to(game.car.global_position)
_check(dist > 5.0, "car didn't move (%.1f m)" % dist)
Input.action_release("throttle")
game.free()
# crash mode: traffic spawned, ram the nearest one, score must rise
print(" .. crash")
game = await _boot("crash")
_check(game.traffic.size() == Game.TRAFFIC_COUNT["crash"], "traffic missing")
var victim: TrafficCar = game.traffic[0]
await physics_frame # traffic takes position on its first physics tick
Input.action_press("throttle")
for attempt in 8: # re-aim: victim keeps moving along the loop
if not victim.live:
break
var to_victim := (victim.global_position - game.car.global_position)
game.car.global_position = victim.global_position - victim.global_basis.z * 8.0
game.car.look_at(victim.global_position)
to_victim = (victim.global_position - game.car.global_position).normalized()
game.car.linear_velocity = to_victim * 18.0
for i in 30:
await physics_frame
if not victim.live:
break
Input.action_release("throttle")
_check(not victim.live, "rammed traffic never went dynamic")
_check(game.score > 0.0, "crash mode scored nothing")
var crash_score := game.score
game.free()
# junction: convoys spawn, driving straight in causes the crash -> settle ->
# crashbreaker -> tally
var jcts := Registry.junctions().filter(func(j): return j.level_id == "woolies_carpark")
_check(not jcts.is_empty(), "no woolies junction event")
Game.junction = jcts[0].data
print(" .. junction")
game = await _boot("junction")
_check(game.traffic.size() >= 8, "junction convoys missing (%d)" % game.traffic.size())
Input.action_press("throttle")
for i in 950:
await physics_frame
if game.jstate == "settle" and not game.breaker_used:
Input.action_release("throttle")
Input.action_press("boost")
elif game.breaker_used:
Input.action_release("boost")
if game.over:
break
Input.action_release("throttle")
Input.action_release("boost")
_check(game.jstate != "run", "never crashed into the convoy")
_check(game.breaker_used, "crashbreaker never fired")
_check(game.over and game.score > 0.0, "junction never tallied (score %d)" % int(game.score))
var jct_score := game.score
Game.junction = {}
game.free()
# outrun: roaming rivals exist in cruise; pulling alongside at speed arms the
# duel, and the win path pays out and clears state
game = await _boot("cruise")
_check(game.rivals.size() == Game.ROAM_RIVALS, "no roaming rivals in cruise")
var rr: Rival = game.rivals[0]
await physics_frame
game.car.global_position = rr.global_position + Vector3(3, 0.3, 0)
game.car.linear_velocity = Vector3(0, 0, -12) # above the 8 m/s arming speed
for i in 5:
await physics_frame
if game.outrun_rival != null:
break
_check(game.outrun_rival == rr, "outrun didn't arm on proximity")
var bm0: float = game.car.boost_max
game._outrun_end(true, "test")
_check(game.outrun_rival == null and game.outruns_won == 1, "outrun win didn't settle")
_check(game.car.boost_max > bm0, "outrun win paid no boost")
_check(game.outrun_cd > 0.0, "no re-arm cooldown after outrun")
game.free()
# pursuit: smashing raises heat, heat summons cops, fleeing loses them
print(" .. pursuit")
game = await _boot("cruise")
for i in 10:
game.add_heat(Game.HEAT_PER_SMASH)
_check(game.cops.size() >= Game.COPS_MIN, "heat didn't summon cops")
game.car.global_position += Vector3(900, 0, 900)
for i in 700:
await physics_frame
if game.cops.is_empty():
break
_check(game.escapes == 1 and game.cops.is_empty(), "escape never resolved")
_check(game.heat_cd > 0.0, "no cooldown after escape")
game.free()
# drag: the Willowbank quarter mile -- tree staging, ladder rival, then the box
Game.level_path = levels["willowbank_dragway"]
print(" .. drag")
game = await _boot("drag")
_check(absf(game.drag_len - 402.0) < 5.0, "quarter mile measures %.0f m" % game.drag_len)
_check(game.rivals.size() == 1, "drag had no opponent")
_check(game.drag_state == "stage", "no tree staging phase")
_check(game.drag_rung_now == 0, "fresh ladder not starting at rung 0")
_check(game.rivals[0].stats.resource_path.contains("morry"),
"rung 0 rival isn't Nanna (%s)" % game.rivals[0].stats.resource_path)
_check(game._tree_bulbs.size() == 6, "christmas tree bulbs missing (%d)" % game._tree_bulbs.size())
await physics_frame
_check(game.rivals[0].power_scale == 0.0, "rival not held at the tree")
# hold still like a disciplined launch: no red light, wait out the ambers
var tree_spin := 0
while game.drag_state != "run" and tree_spin < 400:
await physics_frame
tree_spin += 1
_check(game.drag_state == "run", "tree never went green")
_check(not game.jumped, "stationary car scored a red light")
var green_m := (game._tree_bulbs["TreeGreen"] as MeshInstance3D).material_override as StandardMaterial3D
_check(green_m.emission_enabled, "green bulb didn't light")
Input.action_press("throttle")
for i in 10:
await physics_frame
_check(game.revs > 0.0, "revs never climbed")
var g0: int = game.gear
# shift the moment revs enter the window. First gear redlines in ~1.6 s, so
# idling here first pins revs at 1.0 and the shift correctly scores as blown.
var spin := 0
while game.revs < Game.SHIFT_LO and spin < 600: # bounded: never hang the suite
await physics_frame
spin += 1
_check(game.revs >= Game.SHIFT_LO and game.revs < Game.REDLINE,
"revs %.2f not inside the shift window [%.2f, %.2f)" % [game.revs, Game.SHIFT_LO, Game.REDLINE])
Input.action_press("boost")
await physics_frame
Input.action_release("boost")
await physics_frame
_check(game.gear == g0 + 1, "shift didn't change gear")
_check(game.clean_shifts == 1 and game.blown_shifts == 0,
"in-window shift scored as blown (%d/%d)" % [game.clean_shifts, game.blown_shifts])
Input.action_release("throttle")
game.free()
Game.level_path = levels.get("woolies_carpark", levels.values()[0])
# derby: opponents spawn in a ring and each hunts something
print(" .. derby")
game = await _boot("derby")
_check(game.derby.size() == Game.DERBY_CARS, "derby field wrong size")
await physics_frame
for d in game.derby:
_check(d.target != null and d.target != d, "derby car has no/self target")
game.free()
# road rage: rivals spawn, a takedown near the player counts and pays
print(" .. rage")
game = await _boot("rage")
_check(game.rivals.size() == Game.RAGE_RIVALS, "rage field wrong size")
var prey: Rival = game.rivals[0]
await physics_frame
game.car.global_position = prey.global_position + Vector3(3, 0.2, 0)
await physics_frame
prey.wreck_started.emit()
_check(game.rage_downs == 1, "rage takedown didn't count (%d)" % game.rage_downs)
game.time_left = 0.05
for i in 10:
await physics_frame
_check(game.over, "rage never tallied")
game.free()
# oncoming: rolling against the road tangent at speed earns boost
game = await _boot("cruise")
await physics_frame
var curve := game.race_path.curve
var road_pt := game.race_path.to_global(curve.sample_baked(20.0))
var tan := (game.race_path.to_global(curve.sample_baked(24.0)) - road_pt).normalized()
# Teleport via the physics server: setting global_position on a
# continuous_cd body SWEEPS it there, slamming through everything en route
# -- the first version of this test wrecked the car before it could earn.
var xf := Transform3D(Basis.looking_at(-tan, Vector3.UP), road_pt + Vector3.UP * 0.75)
PhysicsServer3D.body_set_state(game.car.get_rid(), PhysicsServer3D.BODY_STATE_TRANSFORM, xf)
game.car.wrecked = false
game.car.boost = 0.0
game.car._prev_lv = -tan * 16.0 # don't let the velocity jump read as a crash
game.car.linear_velocity = -tan * 16.0
for i in 45:
await physics_frame
game.car.linear_velocity = -tan * 16.0 # hold course against drag
_check(game.car.boost > 0.05, "oncoming earned no boost (%.3f)" % game.car.boost)
game.free()
# race: grid up at Queensland Raceway -- the paperclip is ~3126 m of Path3D
Game.level_path = levels["qld_raceway"]
print(" .. race")
game = await _boot("race")
var lap: float = game.race_path.curve.get_baked_length()
_check(absf(lap - 3126.0) < 60.0, "paperclip lap measures %.0f m" % lap)
_check(game.rivals.size() == Game.RIVAL_COUNT, "rivals missing")
var p0: float = game.rivals[0].progress
for i in 240:
await physics_frame
var moved := false
for r in game.rivals:
if absf(r.progress - p0) > 3.0 or r.linear_velocity.length() > 3.0:
moved = true
_check(moved, "no rival is driving")
game.free()
print("smoke ok: %d cars, %d levels | cruise %.1f m | crash $%d | junction $%d | outrun arms+pays | pursuit spawns+escapes | quarter mile stages+shifts | derby %d cars | paperclip race AI driving" %
[cars.size(), levels.size(), dist, int(crash_score), int(jct_score), Game.DERBY_CARS])
quit(0)
func _check(cond: bool, msg: String) -> void:
## NOT assert(): a failed assert in a headless SceneTree script halts the
## script but leaves the tree running, so the process spins forever instead
## of failing. One bad assertion cost 53 minutes of wall clock before this.
if cond:
return
push_error(msg)
print("SMOKE FAIL: %s" % msg)
quit(1)
func _boot(mode: String) -> Game:
Game.mode = mode
var game: Game = (load("res://src/game.tscn") as PackedScene).instantiate()
root.add_child(game)
await process_frame # _ready is deferred until the main loop runs
return game