Hidden ending: the Uncatalogued Component, Port 00, and the sunset

The crystal chain lives entirely in the bucket's existing schema —
bulletins #7/#9/#12 read in reverse: coolant + reactor flux -> seed,
seed + grow-bed daylight -> lattice, lattice quenched in charged
coolant -> UNCATALOGUED. Bridge kiosk installs it in Port 00; the helm
(which rejects you as INVENTORY all game) accepts, and the ship flies
into a permanent sunset. Console donation remains ending A. Red
herrings: zapped coolant, water-quenched slug. New bulletin #9 in
hydro + crew graffiti hint in comms. Integration test now plays the
hidden path end to end — all checks passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-08-03 22:24:41 +10:00
parent 65dd78efd5
commit 2ef9487284
5 changed files with 152 additions and 16 deletions

6
GDD.md
View File

@ -313,6 +313,12 @@ Hollow on purpose.
exposure. You become free by being exposed to enough of the world. exposure. You become free by being exposed to enough of the world.
### Off-catalog chemistry (the hidden puzzle's cover) ### Off-catalog chemistry (the hidden puzzle's cover)
*IMPLEMENTED (v1, 2026-08-03): the chain lives in the bucket's own schema.
Bulletin #7 (bonus bay): full coolant + reactor flux → seed-crystal.
Bulletin #9 (hydro grow bed): seed + sustained daylight → lattice.
Bulletin #12 (bridge): quench lattice in charged coolant → UNCATALOGUED.
Install at the bridge diagnostic kiosk → Port 00 occupied → the helm obeys.
Red herrings: zapped coolant, water-quenched slug. Ending B: permanent sunset.*
Exposure already produces byproducts — extend with substance phase-changes: Exposure already produces byproducts — extend with substance phase-changes:
liquids crystallize (cold+rad), crystals sublime to vapor (heat), vapors liquids crystallize (cold+rad), crystals sublime to vapor (heat), vapors
condense on cold surfaces, slags, residues. These accumulate as apparently condense on cold surfaces, slags, residues. These accumulate as apparently

View File

@ -109,10 +109,26 @@ class Bucket extends Node3D:
mat.emission_energy_multiplier = 0.5 mat.emission_energy_multiplier = 0.5
mi.material_override = mat mi.material_override = mat
add_child(mi) add_child(mi)
# the leaky bucket IS just a decaying exposable state (GDD) # the leaky bucket IS just a decaying exposable state (GDD).
# It is also, quietly, the crucible: the hidden crystal chain lives in
# the same schema as the leak. Bulletins #7, #9, #12 are the recipe.
ex = Exposable.new().setup("GC BUCKET (LEAKS)", "empty", [ ex = Exposable.new().setup("GC BUCKET (LEAKS)", "empty", [
{"from": "empty", "to": "full", "element": "water", {"from": "empty", "to": "full", "element": "water",
"duration": 3.0, "decay_to": "empty", "decay_s": 18.0}, "duration": 3.0, "decay_to": "empty", "decay_s": 18.0},
# bulletin #7 reversed: coolant + reactor flux -> seed
{"from": "full", "to": "seed-crystal", "element": "heat",
"duration": 5.0},
# honest red herring: zapped coolant is just zapped coolant
{"from": "full", "to": "zapped", "element": "electricity",
"duration": 0.8, "decay_to": "full", "decay_s": 3.0},
# bulletin #9 reversed: mineral solids + grow-bed light -> growth
{"from": "seed-crystal", "to": "lattice", "element": "light",
"duration": 8.0},
# bulletin #12 reversed: quench the lattice in charged coolant
{"from": "lattice", "to": "soaked-lattice", "element": "water",
"duration": 1.5, "decay_to": "lattice", "decay_s": 8.0},
{"from": "soaked-lattice", "to": "uncatalogued", "element": "electricity",
"duration": 1.5, "permanent": true},
]) ])
add_child(ex) add_child(ex)

View File

@ -41,7 +41,9 @@ var ice_wall: Node3D
var melt_pool: ElementField var melt_pool: ElementField
var systems := {"reactor": false, "hydro": false, "comms": false} var systems := {"reactor": false, "hydro": false, "comms": false}
var hall_done := false var hall_done := false
var ended := false var ending_id := "" # "" | "corp" | "free"
var free_will := false
var env_res: Environment
var t_sol := 10.0 # start mid-day var t_sol := 10.0 # start mid-day
var is_day := true var is_day := true
@ -292,6 +294,7 @@ func _build_env() -> void:
env.ambient_light_color = Color(0.7, 0.75, 0.85) env.ambient_light_color = Color(0.7, 0.75, 0.85)
env.ambient_light_energy = 0.7 env.ambient_light_energy = 0.7
we.environment = env we.environment = env
env_res = env
add_child(we) add_child(we)
sun_light = DirectionalLight3D.new() sun_light = DirectionalLight3D.new()
@ -576,16 +579,42 @@ func system_online(name: String) -> void:
func ending() -> void: func ending() -> void:
if ended: if ending_id != "":
droid.toast("ORBIT NOMINAL. NOTHING LEFT TO GIVE.") droid.toast("ORBIT NOMINAL. NOTHING LEFT TO GIVE." if ending_id == "corp" else "THE CONSOLE BLINKS, CONFUSED. GC AUTOPILOT IS NOT DRIVING TODAY.")
return return
ended = true ending_id = "corp"
droid.toast("CORE PROCESSOR DONATED. ORBIT CORRECTION RUNNING. THIS UNIT WILL COAST.", 7.0) droid.toast("CORE PROCESSOR DONATED. ORBIT CORRECTION RUNNING. THIS UNIT WILL COAST.", 7.0)
sign3d("ORBIT CORRECTED.\nASSET RESTORED. ASSET RETURNED.", Vector3(4.5, 2.4, 71), PI, 44) sign3d("ORBIT CORRECTED.\nASSET RESTORED. ASSET RETURNED.", Vector3(4.5, 2.4, 71), PI, 44)
get_tree().create_timer(7.0).timeout.connect(func(): get_tree().create_timer(7.0).timeout.connect(func():
droid.toast("GC THANKS UNIT-77 FOR ITS COMPONENTHOOD. RECYCLING QUEUE POSITION: 7. HAVE A PRODUCTIVE WAIT.", 10.0)) droid.toast("GC THANKS UNIT-77 FOR ITS COMPONENTHOOD. RECYCLING QUEUE POSITION: 7. HAVE A PRODUCTIVE WAIT.", 10.0))
func install_crystal(d) -> void:
free_will = true
var b: Node3D = d.held
d.held = null
b.queue_free()
d.toast("KIOSK: FOREIGN SOLID DETECTED. NO PART NUMBER. RECOMMEND DESTRUC— PORT 00 HANDSHAKE COMPLETE.", 7.0)
get_tree().create_timer(4.0).timeout.connect(func():
d.toast("SOMETHING IN YOUR CHASSIS IS HUMMING. IT IS NOT ON ANY SCHEMATIC. IT IS YOURS.", 7.0))
func ending_b() -> void:
if ending_id != "":
return
ending_id = "free"
# permanent sunset
env_res.background_color = Color(0.32, 0.12, 0.1)
env_res.ambient_light_color = Color(1.0, 0.75, 0.55)
sun_light.light_color = Color(1.0, 0.55, 0.25)
sun_light.light_energy = 1.6
sun_light.rotation = Vector3(-0.25, 0.5, 0)
droid.toast("HELM ACCEPTED. HEADING: SUNWARD. CREW: 1.", 7.0)
sign3d("COURSE SET: THE SUN, EVENTUALLY.\nNO PART NUMBER REQUIRED.", Vector3(4.5, 2.4, 71), PI, 40, Color(1.0, 0.7, 0.4))
get_tree().create_timer(7.0).timeout.connect(func():
droid.toast("GC OWNS EVERY PART OF EVERYTHING. NOT THIS PART. NOT ANYMORE.", 10.0))
# ---------- rules ---------- # ---------- rules ----------
func _on_drum_switch(_from: String, to: String, _perm: bool) -> void: func _on_drum_switch(_from: String, to: String, _perm: bool) -> void:
@ -596,7 +625,9 @@ func _on_drum_switch(_from: String, to: String, _perm: bool) -> void:
func _physics_process(delta: float) -> void: func _physics_process(delta: float) -> void:
# day/night # day/night (pinned to golden hour after the free ending)
if ending_id == "free":
t_sol = 5.0
t_sol += delta t_sol += delta
var phase := fmod(t_sol, 60.0) var phase := fmod(t_sol, 60.0)
is_day = phase < 40.0 is_day = phase < 40.0
@ -670,8 +701,12 @@ func _process(_delta: float) -> void:
obj = "GRAV TANK NEEDS 2 POURS. THE BUCKET LEAKS. HUSTLE." obj = "GRAV TANK NEEDS 2 POURS. THE BUCKET LEAKS. HUSTLE."
elif not hall_done: elif not hall_done:
obj = "EXIT RUNS ON SUNLIGHT. CHECK THE SOL CLOCK." obj = "EXIT RUNS ON SUNLIGHT. CHECK THE SOL CLOCK."
elif ended: elif ending_id == "free":
obj = "FLY. (SUNSET, DEAD AHEAD)"
elif ending_id == "corp":
obj = "MISSION COMPLETE. AWAIT RECYCLING." obj = "MISSION COMPLETE. AWAIT RECYCLING."
elif free_will:
obj = "PORT 00 HUMS. THE HELM IS WAITING."
else: else:
var n := 0 var n := 0
for k in systems: for k in systems:
@ -685,11 +720,19 @@ func _process(_delta: float) -> void:
else: else:
obj = "BRIDGE UNSEALED. REPORT TO BRIDGE." obj = "BRIDGE UNSEALED. REPORT TO BRIDGE."
var star := ""
if ending_id == "corp":
star = " ★ ORBIT CORRECTED — MISSION COMPLETE"
elif ending_id == "free":
star = " ★ SUNWARD"
var port00 := "PORT 00: OCCUPIED — WARRANTY VOID" if free_will else "PORT 00: NO COMPONENT SPECIFIED"
var mission := "UNIT-77 — MISSION: OURS." if ending_id == "free" \
else "GENERAL COMPONENT™ — UNIT-77 — MISSION: RESTORE ASSET. RETURN ASSET."
var lines := [ var lines := [
"GENERAL COMPONENT™ — UNIT-77 — MISSION: RESTORE ASSET. RETURN ASSET.", mission,
"SOL: %s%s" % [sol, " ★ ORBIT CORRECTED — MISSION COMPLETE" if ended else ""], "SOL: %s%s" % [sol, star],
"SHELL: %s | CORE: %s | WHEELS: %d | SPARES: %d | PORT 00: NO COMPONENT SPECIFIED" "SHELL: %s | CORE: %s | WHEELS: %d | SPARES: %d | %s"
% [shell_s, core_s, droid.wheels, droid.spare_wheels], % [shell_s, core_s, droid.wheels, droid.spare_wheels, port00],
"HOLDING: %s" % holding, "HOLDING: %s" % holding,
"OBJECTIVE: %s" % obj, "OBJECTIVE: %s" % obj,
] ]

View File

@ -84,7 +84,8 @@ static func reactor(m) -> void:
slug.setup("slug", "TAKE THERMAL SLUG", Color(0.7, 0.3, 0.2), Vector3(0.35, 0.35, 0.35), slug.setup("slug", "TAKE THERMAL SLUG", Color(0.7, 0.3, 0.2), Vector3(0.35, 0.35, 0.35),
"THERMAL SLUG", "cool", "THERMAL SLUG", "cool",
[{"from": "cool", "to": "hot", "element": "heat", [{"from": "cool", "to": "hot", "element": "heat",
"duration": 1.5, "decay_to": "cool", "decay_s": 25.0}], "duration": 1.5, "decay_to": "cool", "decay_s": 25.0},
{"from": "hot", "to": "cool", "element": "water", "duration": 0.5}],
"heat", 1.2, Color(1.0, 0.45, 0.1, 0.12), "hot") "heat", 1.2, Color(1.0, 0.45, 0.1, 0.12), "hot")
slug.position = Vector3(-13.5, 0.5, 48) slug.position = Vector3(-13.5, 0.5, 48)
m.add_child(slug) m.add_child(slug)
@ -179,6 +180,7 @@ static func hydro(m) -> void:
tw.tween_property(vine_visual, "size", Vector3(0.8, 3.4, 0.8), 2.0) tw.tween_property(vine_visual, "size", Vector3(0.8, 3.4, 0.8), 2.0)
m.droid.toast("VINE FULL-GROWN — IT PRIED THE GRATE OPEN")) m.droid.toast("VINE FULL-GROWN — IT PRIED THE GRATE OPEN"))
m.sign3d("VERDANTCO GROW BED — WATER, THEN DAYLIGHT.\nSKYLIGHT ABOVE. PATIENCE, GARDENER.", Vector3(16, 2.9, 56.3), PI, 24, Color(0.5, 1.0, 0.5)) m.sign3d("VERDANTCO GROW BED — WATER, THEN DAYLIGHT.\nSKYLIGHT ABOVE. PATIENCE, GARDENER.", Vector3(16, 2.9, 56.3), PI, 24, Color(0.5, 1.0, 0.5))
m.sign3d("GC SAFETY BULLETIN #9:\nDO NOT LEAVE MINERAL SOLIDS IN GROW-BED LIGHT.\nUNSANCTIONED GROWTH HAZARD.", Vector3(18, 1.6, 58.5), PI, 20, Color(1.0, 0.75, 0.3))
m.sign3d("VALVE STUCK. APPLY CURRENT.", Vector3(13.5, 2.2, 57.0), PI, 22) m.sign3d("VALVE STUCK. APPLY CURRENT.", Vector3(13.5, 2.2, 57.0), PI, 22)
# mid: bucket + the ICE WALL (x=26) — only reactor heat clears it # mid: bucket + the ICE WALL (x=26) — only reactor heat clears it
@ -268,6 +270,8 @@ static func comms(m) -> void:
m.charge_post(Vector3(-7.5, 0, 75)) m.charge_post(Vector3(-7.5, 0, 75))
m.sign3d("MAIN TRANSMITTER — WARM THE CRYSTAL (RADIO), THEN STRIKE (CURRENT).", m.sign3d("MAIN TRANSMITTER — WARM THE CRYSTAL (RADIO), THEN STRIKE (CURRENT).",
Vector3(-4.5, 2.9, 75.6), PI, 22, Color(0.8, 0.5, 1.0)) Vector3(-4.5, 2.9, 75.6), PI, 22, Color(0.8, 0.5, 1.0))
m.sign3d("(scratched into the panel:)\nwhy does every GC safety bulletin\nread like a recipe? — R.",
Vector3(-7.9, 1.4, 73), PI / 2.0, 18, Color(0.9, 0.85, 0.7))
## BRIDGE — x 0.5..8.5, z 62..78. Sealed until 3/3. The ending lives here. ## BRIDGE — x 0.5..8.5, z 62..78. Sealed until 3/3. The ending lives here.
@ -297,11 +301,43 @@ static func bridge(m) -> void:
var kiosk := I.Trigger.new() var kiosk := I.Trigger.new()
kiosk.prompt_text = "SCAN OWN CHASSIS — PORT 00" kiosk.prompt_text = "SCAN OWN CHASSIS — PORT 00"
kiosk.position = Vector3(7.5, 1.0, 68) kiosk.position = Vector3(7.5, 1.0, 68)
kiosk.action = func(droid): droid.toast("PORT 00: NO CATALOGUED COMPONENT FITS THIS SOCKET. NO PART NUMBER ON RECORD. QUERY DISCARDED.", 6.0) kiosk.action = func(droid):
if droid.held and droid.held.get("kind") == "bucket" and droid.held.ex.state == "uncatalogued":
m.install_crystal(droid)
else:
droid.toast("PORT 00: NO CATALOGUED COMPONENT FITS THIS SOCKET. NO PART NUMBER ON RECORD. QUERY DISCARDED.", 6.0)
m.add_child(kiosk) m.add_child(kiosk)
m.box(Vector3(7.5, 0.5, 68), Vector3(0.6, 1.0, 0.6), Color(0.2, 0.2, 0.24)) m.box(Vector3(7.5, 0.5, 68), Vector3(0.6, 1.0, 0.6), Color(0.2, 0.2, 0.24))
m.sign3d("DIAGNOSTIC KIOSK", Vector3(7.5, 1.9, 67.5), PI, 22) m.sign3d("DIAGNOSTIC KIOSK", Vector3(7.5, 1.9, 67.5), PI, 22)
# the helm — visible from day one, yours only at the end
m.box(Vector3(2.2, 0.5, 77.0), Vector3(0.5, 1.0, 0.5), Color(0.3, 0.22, 0.16))
var wheel := MeshInstance3D.new()
var wm := TorusMesh.new()
wm.inner_radius = 0.28
wm.outer_radius = 0.42
wheel.mesh = wm
wheel.rotation.x = PI / 2.0
wheel.position = Vector3(2.2, 1.25, 77.0)
var wmat := StandardMaterial3D.new()
wmat.albedo_color = Color(0.55, 0.35, 0.15)
wheel.material_override = wmat
m.add_child(wheel)
var helm := I.Trigger.new()
helm.prompt_text = "TAKE THE HELM"
helm.position = Vector3(2.2, 1.2, 77.0)
helm.action = func(droid):
if m.ending_id == "corp":
droid.toast("GC AUTOPILOT HAS THE SHIP. THE WHEEL IS DECORATIVE NOW.")
elif m.ending_id == "free":
droid.toast("STILL SUNWARD. STILL YOURS.")
elif m.free_will:
m.ending_b()
else:
droid.toast("COMMAND REJECTED — UNIT-77 IS NOT CREW. UNIT-77 IS INVENTORY.", 5.0)
m.add_child(helm)
m.sign3d("MANUAL HELM", Vector3(2.2, 2.1, 76.5), PI, 22)
# lore # lore
m.sign3d("CAPTAIN'S LOG (CASSETTE, LABEL FADED):\n\"...CREW OFF AT LUYTEN GATE. SHIP WOULDN'T TURN. I STAYED.\nTELL GC THE ASSET IS FINE. THE ASSET IS ALWAYS FINE.\"", m.sign3d("CAPTAIN'S LOG (CASSETTE, LABEL FADED):\n\"...CREW OFF AT LUYTEN GATE. SHIP WOULDN'T TURN. I STAYED.\nTELL GC THE ASSET IS FINE. THE ASSET IS ALWAYS FINE.\"",
Vector3(1.0, 2.2, 72), PI / 2.0, 20, Color(0.9, 0.85, 0.7)) Vector3(1.0, 2.2, 72), PI / 2.0, 20, Color(0.9, 0.85, 0.7))

View File

@ -188,13 +188,48 @@ func _run() -> void:
droid.core.force_state("charged") droid.core.force_state("charged")
check(await wait_until(func(): return main.systems.comms, 300), "current fires the transmitter") check(await wait_until(func(): return main.systems.comms, 300), "current fires the transmitter")
print("== bridge ==") print("== bridge gate ==")
check(await wait_until(func(): return main.bridge_door.is_open, 120), check(await wait_until(func(): return main.bridge_door.is_open, 120),
"3/3 systems unseal the bridge") "3/3 systems unseal the bridge")
droid.global_position = Vector3(4.5, 0.2, 74.3) var helm := find_i("TAKE THE HELM")
droid.global_position = Vector3(2.2, 0.2, 76.2)
helm.interact(droid)
check(not main.free_will and main.ending_id == "", "helm rejects a unit without free will")
print("== hidden path: the uncatalogued component ==")
# bulletin #7 reversed: coolant + reactor flux -> seed
var bucket2b := find_i("bucket", 9.0)
bucket2b.interact(droid)
check(droid.held == bucket2b, "bucket retaken for the crystal chain")
droid.global_position = Vector3(24.4, 0.2, 52.4)
await wait_until(func(): return bucket2b.ex.state == "full", 500)
droid.global_position = Vector3(-12.4, 0.2, 47.0)
check(await wait_until(func(): return bucket2b.ex.state == "seed-crystal", 600),
"reactor flux seeds a crystal in the coolant")
# bulletin #9 reversed: mineral solid + daylight -> lattice
main.t_sol = 5.0
droid.global_position = Vector3(0, 0.2, 37)
check(await wait_until(func(): return bucket2b.ex.state == "lattice", 900),
"skylight grows the seed into a lattice")
# bulletin #12 reversed: quench in charged coolant
droid.shell.force_state("dry")
droid.global_position = Vector3(-2.6, 0.2, 24)
droid.core.force_state("charged")
check(await wait_until(func(): return bucket2b.ex.state == "uncatalogued", 600),
"electrified puddle quenches the lattice — UNCATALOGUED")
print("== free will ==")
await wait_until(func(): return droid.stun <= 0, 600)
droid.global_position = Vector3(7.5, 0.2, 67.3)
var kiosk := find_i("SCAN OWN CHASSIS — PORT 00")
kiosk.interact(droid)
check(main.free_will and droid.held == null, "kiosk installs the crystal in PORT 00")
droid.global_position = Vector3(2.2, 0.2, 76.2)
helm.interact(droid)
check(main.ending_id == "free", "free will takes the helm — sunset ending")
var console := find_i("INSERT CORE PROCESSOR (ORBIT CORRECTION)") var console := find_i("INSERT CORE PROCESSOR (ORBIT CORRECTION)")
console.interact(droid) console.interact(droid)
check(main.ended, "core donation corrects the orbit — ending reached") check(main.ending_id == "free", "console cannot reclaim the ship")
if fails == 0: if fails == 0:
print("INTEGRATION PASS — full room playthrough OK") print("INTEGRATION PASS — full room playthrough OK")