ShitboxInfinity/tests/smoke.gd
m3ultra 955413b735 Wrench phase 3: driveway customers -- the shed becomes a business
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>
2026-07-31 19:12:15 +10:00

532 lines
23 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")
# tool tiers: buying again upgrades; effects are exercised by wrench jobs
Garage.earn(1500 + 3000)
_check(Garage.tool_tier("sockets") == 1, "owned sockets not tier 1")
_check(Garage.buy_tool("sockets"), "tier 2 upgrade refused")
_check(Garage.buy_tool("sockets"), "tier 3 upgrade refused")
_check(Garage.tool_tier("sockets") == 3 and not Garage.buy_tool("sockets"),
"tier cap broken (tier %d)" % Garage.tool_tier("sockets"))
_check(Garage.has_tool("sockets"), "tiered tool lost has_tool")
# faults: break it, boot wounded, pay the shop for one, wrench the other
print(" .. repair")
Garage.add_fault("datto_120y", "gearbox")
Garage.add_fault("datto_120y", "gasket")
_check(Garage.faults("datto_120y").size() == 2, "faults didn't record")
gg = await _boot("cruise")
_check(gg.car.stats.engine_power < base_power * 0.9, "wounded car boots at full power")
_check(gg.car.leaking, "gearbox-faulted car isn't leaking")
gg.free()
Garage.earn(1000)
_check(Garage.shop_fix("datto_120y", "gasket"), "shop wouldn't take the money")
_check(not Garage.faults("datto_120y").has("gasket"), "shop fix didn't clear the fault")
# the gearbox goes through the 3D wrench: diagnose (wrong first, penalty),
# then the toolless 60 kg drag path -- no hoist owned on this save
Garage.ui_stats_path = "res://cars/datto_120y/stats.tres"
Garage.ui_fault = "gearbox"
var ui = (load("res://src/wrench.tscn") as PackedScene).instantiate()
root.add_child(ui)
await process_frame
_check(ui.steps[0]["verb"] == "diagnose", "gearbox job skipped diagnosis")
_check(ui.steps.size() == 8, "toolless gearbox sheet wrong (%d steps)" % ui.steps.size())
var right := -1
for i in ui.light_options.size():
if ui.light_options[i] == Garage.FAULTS["gearbox"]["light"]:
right = i
ui.pick_light((right + 1) % 3)
_check(ui.penalty_turns > 0.0 and ui.quality < 100.0, "wrong diagnosis cost nothing")
ui.pick_light(right)
_check(ui.step_index == 1, "right light didn't advance the job")
await _drive_wrench(ui, false)
_check(ui.done, "gearbox job never finished (step %d)" % ui.step_index)
_check(not Garage.faults("datto_120y").has("gearbox"), "wrench fix didn't clear the fault")
_check(Garage.quality("datto_120y", "gearbox") >= 85.0, "clean-ish gearbox scored %d"
% int(ui.quality))
ui.free()
# a gasket job at 85+ raises the overheat ceiling; gearbox 85+ adds a strike
Garage.add_fault("datto_120y", "gasket")
Garage.ui_fault = "gasket"
ui = (load("res://src/wrench.tscn") as PackedScene).instantiate()
root.add_child(ui)
await process_frame
await _drive_wrench(ui, false)
_check(ui.done and Garage.quality("datto_120y", "gasket") >= 85.0,
"gasket job didn't finish clean (%d)" % int(ui.quality))
ui.free()
Game.level_path = levels["willowbank_dragway"]
gg = await _boot("drag")
_check(gg.box_limit == 4, "85+ gearbox rebuild paid no extra strike (%d)" % gg.box_limit)
_check(gg.gasket_cap > 1.1, "85+ gasket job raised no ceiling (%.2f)" % gg.gasket_cap)
gg.free()
Game.level_path = levels.get("woolies_carpark", levels.values()[0])
# THE SHED home base: owned tools materialise (sockets were bought above,
# ramps were not), and rolling into the bay and holding still opens the garage
print(" .. shed")
Game.level_path = levels["the_shed"]
gg = await _boot("cruise")
_check(gg.garage_bay != null, "shed has no GarageBay")
var owned_props: Array = gg.find_children("*", "RigidBody3D", true, false).filter(
func(n): return n.has_meta("shed_tool"))
_check(owned_props.size() == 1 and owned_props[0].get_meta("shed_tool") == "sockets",
"owned-tool display wrong (%d shed_tool props)" % owned_props.size())
var bay_at: Vector3 = gg.garage_bay.global_position
var bay_xf := Transform3D(Basis.IDENTITY, bay_at + Vector3.UP * 0.75)
PhysicsServer3D.body_set_state(gg.car.get_rid(), PhysicsServer3D.BODY_STATE_TRANSFORM, bay_xf)
gg.car.linear_velocity = Vector3.ZERO
gg.car._prev_lv = Vector3.ZERO
for i in 150:
await physics_frame
gg.car.linear_velocity = Vector3.ZERO
if gg.garage_requested:
break
_check(gg.garage_requested, "rolling into the bay never opened the garage")
gg.free()
# phase 3: a driveway customer waits, gets their car fixed, and pays
print(" .. customer")
var cust := Garage.customer()
_check(not cust.is_empty(), "no customer turned up at the shed")
gg = await _boot("cruise")
_check(gg.customer_spot != null, "customer car has no CustomerSpot")
var cs_xf := Transform3D(Basis.IDENTITY, gg.customer_spot.global_position + Vector3.UP * 0.75)
PhysicsServer3D.body_set_state(gg.car.get_rid(), PhysicsServer3D.BODY_STATE_TRANSFORM, cs_xf)
gg.car.linear_velocity = Vector3.ZERO
gg.car._prev_lv = Vector3.ZERO
for i in 150:
await physics_frame
gg.car.linear_velocity = Vector3.ZERO
if gg.job_taken:
break
_check(gg.job_taken and Garage.ui_customer, "holding by the customer never took the job")
_check(Garage.ui_fault == cust["fault"], "job fault mismatch")
gg.free()
var bucks_before: int = Garage.bucks()
var rep_before: int = Garage.rep()
var cw = (load("res://src/wrench.tscn") as PackedScene).instantiate()
root.add_child(cw)
await process_frame
_check(cw.customer, "wrench didn't know it was a customer job")
await _drive_wrench(cw, false)
_check(cw.done, "customer job never finished (step %d)" % cw.step_index)
_check(Garage.bucks() > bucks_before, "customer didn't pay")
_check(Garage.rep() > rep_before, "clean customer job earned no rep")
_check(Garage.customer().is_empty(), "customer never left the driveway")
_check(Garage.quality(String(cust["car"]), String(cust["fault"])) == 0.0,
"customer job polluted own-car quality")
cw.free()
Game.level_path = levels.get("woolies_carpark", levels.values()[0])
# wrench phase 1: the 3D shed jobs. A clean flat-tyre run scores 90+ and
# buffs the car; a sabotaged torque window strips a thread on the oil job.
print(" .. wrench")
Garage.add_fault("datto_120y", "flat")
Garage.ui_stats_path = "res://cars/datto_120y/stats.tres"
Garage.ui_fault = "flat"
var wr = (load("res://src/wrench.tscn") as PackedScene).instantiate()
root.add_child(wr)
await process_frame
_check(wr.steps.size() == 7, "flat-tyre job sheet wrong (%d steps)" % wr.steps.size())
await _drive_wrench(wr, false)
_check(wr.done, "flat-tyre job never finished (step %d)" % wr.step_index)
_check(wr.quality >= 90.0, "clean flat run scored %d" % int(wr.quality))
_check(not Garage.faults("datto_120y").has("flat"), "flat fault not cleared")
_check(Garage.quality("datto_120y", "flat") >= 85.0, "workmanship not recorded")
wr.free()
gg = await _boot("cruise") # 85+ fix = grip buff on the next boot
var dstats: CarStats = load("res://cars/datto_120y/stats.tres")
_check(gg.car.stats.grip > dstats.grip + 0.7, "85+ fix paid no grip buff (%.2f vs %.2f)"
% [gg.car.stats.grip, dstats.grip])
gg.free()
Garage.add_fault("datto_120y", "oil")
Garage.ui_fault = "oil"
wr = (load("res://src/wrench.tscn") as PackedScene).instantiate()
root.add_child(wr)
await process_frame
await _drive_wrench(wr, true)
_check(wr.done, "oil job never finished (step %d)" % wr.step_index)
_check(wr.quality <= 90.0, "stripped thread cost nothing (%d)" % int(wr.quality))
_check(wr.steps.size() == 8, "extraction step never appeared (%d steps)" % wr.steps.size())
wr.free()
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())
# TRELLIS'd cashies tools: once the library exists, the paddock is strewn
var tool_dir := DirAccess.open("res://assets/tools")
if tool_dir != null:
var glbs := 0
for f in tool_dir.get_files():
if f.ends_with(".glb"):
glbs += 1
if glbs > 0:
var strewn := 0
for n in game.find_children("*", "RigidBody3D", true, false):
if n.has_meta("cc_tool"):
strewn += 1
_check(strewn == glbs, "tool library has %d GLBs but %d spawned" % [glbs, strewn])
await physics_frame
_check(game.rivals[0].power_scale == 0.0, "rival not held at the tree")
# burnout in the box: DRIFT+GAS warms the tyres and must NOT roll the beams
Input.action_press("drift")
Input.action_press("throttle")
for i in 80:
await physics_frame
if game.drag_state != "stage":
break
_check(game.tyre_temp > 0.25, "burnout warmed nothing (%.2f)" % game.tyre_temp)
_check(not game.jumped, "burnout rolled the beams (red light)")
_check(game.car.burnout_on, "burnout fx never engaged")
Input.action_release("drift")
Input.action_release("throttle")
# 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])
# grenade the box: three panic shifts nowhere near the window
for i in 3:
game.revs = 0.10
Input.action_press("boost")
await physics_frame
Input.action_release("boost")
await physics_frame
_check(game.box_blown, "gearbox survived three blown shifts (%d strikes)" % game.box_strikes)
_check(game.car.leaking, "grenaded box isn't leaking oil")
var locked: int = game.gear
Input.action_press("boost")
await physics_frame
Input.action_release("boost")
await physics_frame
_check(game.gear == locked, "blown box still shifts")
# and cook the coolant: past 1.0 the head gasket lets go
game.eng_temp = 1.05
await physics_frame
_check(game.gasket_blown, "overcooked engine kept its head gasket")
# finishing the run sends the carnage home as persistent faults
game._drag_finish(true)
_check(Garage.faults("datto_120y").has("gearbox") and Garage.faults("datto_120y").has("gasket"),
"carnage didn't follow the car home as faults")
Garage._reset_for_test() # don't leave a wounded car for later stages
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 _drive_wrench(wr, sabotage: bool) -> void:
## Plays any wrench job through its API. sabotage blows the first torque
## window wide open to prove that threads strip.
var guard := 0
var sabotaged := false
while not wr.done and guard < 30:
guard += 1
var st: Dictionary = wr.steps[wr.step_index]
var need: float = (float(st.get("turns", 0.0)) + wr.penalty_turns) * TAU
match String(st["verb"]):
"diagnose":
for i in wr.light_options.size():
if wr.light_options[i] == Garage.FAULTS[wr.fault]["light"]:
wr.pick_light(i)
break
"pump":
for i in int(st["clicks"]):
wr.pump()
"twist":
if bool(st.get("window", false)):
if sabotage and not sabotaged:
sabotaged = true
wr.twist(float(st["dir"]) * (need + 4.0))
else:
wr.twist(float(st["dir"]) * (need + 0.3))
wr.twist_release()
else:
wr.twist(float(st["dir"]) * (need + 0.2))
"yank":
wr.yank_ok()
"grab":
wr.grab(String(st["item"]))
wr.drop_at(wr._drop_target(String(st["at"])))
"steady":
while not wr.done and wr.steps[wr.step_index]["verb"] == "steady":
wr.steady_tick(true, 0.5)
"clamps":
wr.clamp_on("red")
wr.clamp_on("black")
"wait":
wr.skip_wait()
await physics_frame
await physics_frame
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