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.
### 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:
liquids crystallize (cold+rad), crystals sublime to vapor (heat), vapors
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
mi.material_override = mat
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", [
{"from": "empty", "to": "full", "element": "water",
"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)

View File

@ -41,7 +41,9 @@ var ice_wall: Node3D
var melt_pool: ElementField
var systems := {"reactor": false, "hydro": false, "comms": 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 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_energy = 0.7
we.environment = env
env_res = env
add_child(we)
sun_light = DirectionalLight3D.new()
@ -576,16 +579,42 @@ func system_online(name: String) -> void:
func ending() -> void:
if ended:
droid.toast("ORBIT NOMINAL. NOTHING LEFT TO GIVE.")
if ending_id != "":
droid.toast("ORBIT NOMINAL. NOTHING LEFT TO GIVE." if ending_id == "corp" else "THE CONSOLE BLINKS, CONFUSED. GC AUTOPILOT IS NOT DRIVING TODAY.")
return
ended = true
ending_id = "corp"
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)
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))
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 ----------
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:
# day/night
# day/night (pinned to golden hour after the free ending)
if ending_id == "free":
t_sol = 5.0
t_sol += delta
var phase := fmod(t_sol, 60.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."
elif not hall_done:
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."
elif free_will:
obj = "PORT 00 HUMS. THE HELM IS WAITING."
else:
var n := 0
for k in systems:
@ -685,11 +720,19 @@ func _process(_delta: float) -> void:
else:
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 := [
"GENERAL COMPONENT™ — UNIT-77 — MISSION: RESTORE ASSET. RETURN ASSET.",
"SOL: %s%s" % [sol, " ★ ORBIT CORRECTED — MISSION COMPLETE" if ended else ""],
"SHELL: %s | CORE: %s | WHEELS: %d | SPARES: %d | PORT 00: NO COMPONENT SPECIFIED"
% [shell_s, core_s, droid.wheels, droid.spare_wheels],
mission,
"SOL: %s%s" % [sol, star],
"SHELL: %s | CORE: %s | WHEELS: %d | SPARES: %d | %s"
% [shell_s, core_s, droid.wheels, droid.spare_wheels, port00],
"HOLDING: %s" % holding,
"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),
"THERMAL SLUG", "cool",
[{"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")
slug.position = Vector3(-13.5, 0.5, 48)
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)
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("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)
# 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.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))
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.
@ -297,11 +301,43 @@ static func bridge(m) -> void:
var kiosk := I.Trigger.new()
kiosk.prompt_text = "SCAN OWN CHASSIS — PORT 00"
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.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)
# 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
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))

View File

@ -188,13 +188,48 @@ func _run() -> void:
droid.core.force_state("charged")
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),
"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)")
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:
print("INTEGRATION PASS — full room playthrough OK")