ShitboxInfinity/tests/smoke.gd
m3ultra 63c32928ee Road Rage, oncoming boost, a real Crashbreaker fireball, title screen
The last two design-doc gameplay items land:
- Road Rage: 90 s, wreck as many rivals as you can. They recover on their own
  (no hold_wreck) so the supply never dries up, and a 220 m leash teleports
  strays back to the road ahead of the player. Bronze/silver/gold at 4/8/12.
- Oncoming: driving against the RacePath tangent while actually on the road
  (within 7 m of the line, above 12 m/s, heading dot < -0.55) trickles boost
  with a periodic toast. Runs in cruise, race and rage.

Crashbreaker finally looks the part: layered Fx.explosion (70-particle additive
fire bloom, rising smoke, 60-spark shower) inside a thin additive shockwave
shell -- the old opaque orange bubble drowned the fire out completely, so it
became a fast-fading front-culled dome instead. Plus a 0.8 camera shake.

Menu is a title screen: FLUX-generated key art (battered sedan, flying hubcap,
bridge, jacarandas -- the hubcap is canon now) behind a scrim, outlined golden
title. Art prompt asks for no lettering; the plate gibberish is FLUX being FLUX.

Smoke coverage for both mechanics. The oncoming test taught a harness lesson:
setting global_position on a continuous_cd body SWEEPS it there, wrecking the
car before the test began -- teleport via PhysicsServer3D.body_set_state.

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

236 lines
9.3 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])
# 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: needs a level with DragStart/DragEnd, so not the code-built carpark
Game.level_path = levels["southbank"]
print(" .. drag")
game = await _boot("drag")
_check(game.drag_len > 100.0, "no drag strip found (%.0f m)" % game.drag_len)
_check(game.rivals.size() == 1, "drag had no opponent")
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: rivals spawn and make progress along the path
print(" .. race")
game = await _boot("race")
_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 | drag shifts | derby %d cars | 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