exposure/test/selfcheck.gd
type-two d414ccd6e4 Phase 0 grey-box: playable room, one schema, 19-check headless playthrough
Exposable (the one state-machine schema) + ElementField (distance-based
emitters) drive everything: wall components, the droid's shell/core, the
leaky bucket. Room chain: wheel donation -> be-the-wire -> electrified
puddle/relay -> leaky-bucket ferry -> rotating cargo drum (bonus) ->
solar-gated exit on a 60s day/night cycle. Calculator-green HUD with
PORT 00 tease.

test/selfcheck.gd: 11 schema checks. test/integration.gd: full-room
headless playthrough, 19 checks, all passing.

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

81 lines
2.6 KiB
GDScript

extends SceneTree
## Headless check of the one schema everything runs on.
## Run: godot --headless --path . -s test/selfcheck.gd
var fails := 0
func check(cond: bool, msg: String) -> void:
if not cond:
fails += 1
printerr("FAIL: " + msg)
func _initialize() -> void:
var Ex = load("res://src/exposable.gd")
# solar-style: transition after duration, not before; sustain; decay
var solar = Ex.new().setup("solar", "unpowered", [
{"from": "unpowered", "to": "powered", "element": "light",
"duration": 0.5, "decay_to": "unpowered", "decay_s": 1.5},
])
solar.expose("light", 0.3)
check(solar.state == "unpowered", "no transition before duration met")
solar.expose("light", 0.3)
check(solar.state == "powered", "transition at duration")
# sustain: keep exposing, decay must not fire
for i in 3:
solar._process(1.0)
solar.expose("light", 0.016)
check(solar.state == "powered", "sustained exposure refreshes decay")
# stop exposing: decays back
solar._process(1.6)
check(solar.state == "unpowered", "decays back after exposure stops")
# wrong element is ignored
solar.expose("water", 5.0)
check(solar.state == "unpowered", "wrong element ignored")
# progress bleed-off: partial exposure resets after idle gap
var solar2 = Ex.new().setup("solar2", "unpowered", solar.transitions)
solar2.expose("light", 0.3)
solar2._process(1.0)
solar2.expose("light", 0.3)
check(solar2.state == "unpowered", "partial progress bleeds off after gap")
# fuse-style permanence
var fuse = Ex.new().setup("fuse", "intact", [
{"from": "intact", "to": "blown", "element": "electricity",
"duration": 0.1, "permanent": true},
])
fuse.expose("electricity", 0.2)
check(fuse.state == "blown" and fuse.locked, "permanent transition locks")
fuse.force_state("intact")
check(fuse.state == "blown", "locked state refuses force_state")
# bucket-style: fill, then leak empty
var bucket = Ex.new().setup("bucket", "empty", [
{"from": "empty", "to": "full", "element": "water",
"duration": 3.0, "decay_to": "empty", "decay_s": 18.0},
])
for i in 40:
bucket.expose("water", 0.1)
check(bucket.state == "full", "bucket fills under stream")
bucket._process(19.0)
check(bucket.state == "empty", "bucket leaks back to empty")
# force_state clears decay
var b2 = Ex.new().setup("b2", "empty", bucket.transitions)
for i in 40:
b2.expose("water", 0.1)
b2.force_state("empty")
check(b2.decay_remaining() < 0, "force_state clears decay")
b2._process(0.1)
check(b2.state == "empty", "poured bucket stays empty")
if fails == 0:
print("SELFCHECK PASS (11 checks)")
else:
printerr("SELFCHECK: %d FAILURES" % fails)
quit(1 if fails > 0 else 0)