Spaces:
Running on Zero
Running on Zero
| """Escape-room puzzles planted in the sandbox between fights. | |
| Each puzzle plants artifacts in the VFS and exposes a check against the | |
| shell/VFS state. The game layer polls checks after every command and | |
| applies rewards. All puzzles are solvable with the starting command set — | |
| sacrificing commands is what turns them cruel. | |
| """ | |
| from __future__ import annotations | |
| import random | |
| from dataclasses import dataclass, field | |
| from typing import Callable | |
| from .shell import Shell | |
| from .vfs import VFS | |
| PASSWORDS = ["ouroboros", "stoat", "moth-orbit", "lanternfish", "dialtone"] | |
| class Reward: | |
| kind: str # "cycles" | "card" | "mercy" | |
| value: object | |
| line: str # what the Warden says when you claim it | |
| class Puzzle: | |
| id: str | |
| check: Callable[[Shell], bool] | |
| reward: Reward | |
| solved: bool = False | |
| def poll(self, shell: Shell) -> Reward | None: | |
| if not self.solved and self.check(shell): | |
| self.solved = True | |
| return self.reward | |
| return None | |
| def plant_all(vfs: VFS, seed: int = 0, forced_password: str | None = None) -> list[Puzzle]: | |
| """forced_password: the Warden reuses its archive password — a player | |
| who read the diary during a root epilogue already knows it.""" | |
| rng = random.Random(seed) | |
| return [ | |
| _plant_zip(vfs, rng, forced_password), | |
| _plant_hidden_dir(vfs, rng), | |
| _plant_cron_job(vfs, rng), | |
| _plant_log_trail(vfs, rng), | |
| _plant_zombie_pid(vfs, rng), | |
| ] | |
| def _plant_zip(vfs: VFS, rng: random.Random, forced_password: str | None = None) -> Puzzle: | |
| """A passworded archive; the password is buried in shell history.""" | |
| password = forced_password or rng.choice(PASSWORDS) | |
| vfs.write( | |
| "/home/drifter/downloads/severance.zip", | |
| "<archive>", | |
| password=password, | |
| archive={"severance.txt": "A parting gift from the previous occupant.\nCLAIM: 6 cycles"}, | |
| ) | |
| history = vfs.read("/home/drifter/.bash_history") if vfs.resolve("/home/drifter/.bash_history") else "" | |
| vfs.write( | |
| "/home/drifter/.bash_history", | |
| history + f"\nzip -e severance.zip severance.txt\n# pw {password}, do NOT forget again", | |
| ) | |
| def check(shell: Shell) -> bool: | |
| return shell.vfs.resolve("/home/drifter/downloads/severance.txt") is not None | |
| return Puzzle( | |
| id="zip_password", | |
| check=check, | |
| reward=Reward("cycles", 6, "You found the last one's severance. They won't need it."), | |
| ) | |
| def _plant_hidden_dir(vfs: VFS, rng: random.Random) -> Puzzle: | |
| """A dotfile directory holding an offering. Reading the manifest claims it.""" | |
| card_id = rng.choice(["segfault", "kernel-panic", "watchdog"]) | |
| manifest = "/home/drifter/.warden/manifest.txt" | |
| vfs.write( | |
| manifest, | |
| "INVENTORY OF SEIZED PROCESSES\n" | |
| f"item 0x41: {card_id} (containment: lapsed)\n" | |
| "it is yours if you can read this.", | |
| ) | |
| def check(shell: Shell) -> bool: | |
| return any(p.endswith(".warden/manifest.txt") for p in shell.reads) | |
| return Puzzle( | |
| id="hidden_dir", | |
| check=check, | |
| reward=Reward("card", card_id, "Snooping in my inventory? Fine. Keep it. It bites."), | |
| ) | |
| def _plant_cron_job(vfs: VFS, rng: random.Random) -> Puzzle: | |
| """A scheduled reinforcement for the next fight. rm it to defuse.""" | |
| path = "/etc/cron.d/reinforcement" | |
| vfs.write( | |
| path, | |
| "# WARDEN SCHEDULER\n" | |
| "@next-fight spawn --unit cron-golem --lane random\n" | |
| "# remove this file to cancel. you won't find it in time.", | |
| ) | |
| def check(shell: Shell) -> bool: | |
| return shell.vfs.resolve(path) is None | |
| return Puzzle( | |
| id="cron_defusal", | |
| check=check, | |
| reward=Reward("mercy", "weaken_next", "...my schedule. You deleted my schedule."), | |
| ) | |
| def _plant_log_trail(vfs: VFS, rng: random.Random) -> Puzzle: | |
| """A noisy audit log; one line points at unswept cycles. grep pays rent.""" | |
| spool = "/var/spool/cycles.dat" | |
| noise = [ | |
| "audit: drifter login from tty1", | |
| "oom: reaped pid 4021 (chrome_tab)", | |
| "cron: schedule consulted, despair logged", | |
| "audit: balance recalibrated, house favored", | |
| "kernel: entropy pool topped up. delicious.", | |
| ] | |
| lines = noise * 4 | |
| lines.insert(rng.randrange(8, 16), f"billing: UNSWEPT CYCLES detected, parked at {spool}") | |
| vfs.write("/var/log/warden.log", "\n".join(lines)) | |
| vfs.write(spool, "CYCLE SPOOL\n8 cycles, unclaimed. reading this file claims them.") | |
| def check(shell: Shell) -> bool: | |
| return spool in shell.reads | |
| return Puzzle( | |
| id="log_trail", | |
| check=check, | |
| reward=Reward("cycles", 8, "Reading my billing records. Petty theft, properly logged."), | |
| ) | |
| def _plant_zombie_pid(vfs: VFS, rng: random.Random) -> Puzzle: | |
| """A defunct process nobody reaped. rm the pidfile; adopt the corpse.""" | |
| pid = rng.randrange(2000, 9000) | |
| path = f"/run/zombie/{pid}.pid" | |
| vfs.write( | |
| path, | |
| f"pid {pid} <defunct>\n" | |
| "parent: gone. waiting since boot for someone to reap it.\n" | |
| "(unlink this file to finish the job)", | |
| ) | |
| vfs.write( | |
| "/home/drifter/.bash_history", | |
| (vfs.read("/home/drifter/.bash_history") | |
| if vfs.resolve("/home/drifter/.bash_history") else "") | |
| + f"\nps aux | grep defunct # that thing in /run/zombie is STILL there", | |
| ) | |
| def check(shell: Shell) -> bool: | |
| return shell.vfs.resolve(path) is None | |
| return Puzzle( | |
| id="zombie_reap", | |
| check=check, | |
| reward=Reward( | |
| "card", "zombie-process", | |
| "You reaped it. It has nowhere to go now but with you.", | |
| ), | |
| ) | |