"""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"] @dataclass class Reward: kind: str # "cycles" | "card" | "mercy" value: object line: str # what the Warden says when you claim it @dataclass 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", "", 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} \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.", ), )