Spaces:
Running on Zero
Running on Zero
| """The exit interview: the run is dead, and the Warden keeps records. | |
| No card is forged here. Your statement goes into the crash dump, into the | |
| Warden's persistent memory, and — eventually — gets quoted back to you at | |
| the worst possible moment. Free text reaches the LLM only wrapped inert. | |
| Dismisses with the cleaned statement string. | |
| """ | |
| from __future__ import annotations | |
| from rich.text import Text | |
| from textual import events | |
| from textual.app import ComposeResult | |
| from textual.containers import Vertical | |
| from textual.screen import Screen | |
| from textual.widgets import Input, Static | |
| from scrypt.engine.legacy import clean_statement | |
| from scrypt.warden.guardrails import wrap_player_text | |
| OPENER = ( | |
| "Dead. Properly dead — no more ttys. Before the core dump is sealed, " | |
| "procedure allows you one statement for the record. Make it good. " | |
| "I keep these." | |
| ) | |
| FILED_LINES = [ | |
| "Noted, stamped, archived. The record outlives the process. It always does.", | |
| "Filed under recurring disappointments. You will be quoted at length.", | |
| ] | |
| class ExitInterviewScreen(Screen): | |
| CSS = """ | |
| #exit-dialogue { height: 5; padding: 1 2 0 2; } | |
| #exit-input { margin: 0 8; } | |
| #exit-prompt { height: 1; dock: bottom; background: $panel; content-align: center middle; } | |
| """ | |
| def __init__(self, voice=None): | |
| super().__init__() | |
| self.voice = voice | |
| self.statement: str | None = None | |
| self._speech_target = "" | |
| self._speech_shown = 0 | |
| def compose(self) -> ComposeResult: | |
| with Vertical(): | |
| yield Static(id="exit-dialogue") | |
| yield Input( | |
| placeholder="statement for the record", | |
| max_length=120, | |
| id="exit-input", | |
| ) | |
| yield Static(id="exit-prompt") | |
| def on_mount(self) -> None: | |
| self.say(OPENER) | |
| self.set_interval(0.025, self._typewriter_tick) | |
| self.query_one("#exit-input", Input).focus() | |
| self.query_one("#exit-prompt", Static).update(Text("[enter] go on the record")) | |
| def say(self, line: str) -> None: | |
| self._speech_target = line | |
| self._speech_shown = 0 | |
| def _typewriter_tick(self) -> None: | |
| if self._speech_shown < len(self._speech_target): | |
| self._speech_shown += 2 | |
| shown = self._speech_target[: self._speech_shown] | |
| self.query_one("#exit-dialogue", Static).update( | |
| Text(f'"{shown}"', style="italic") | |
| ) | |
| def on_input_submitted(self, event: Input.Submitted) -> None: | |
| if self.statement is not None: | |
| return | |
| self.statement = clean_statement(event.value) | |
| inp = self.query_one("#exit-input", Input) | |
| inp.disabled = True | |
| inp.display = False | |
| self.query_one("#exit-prompt", Static).update(Text("[any key] be sealed")) | |
| if self.voice is not None: | |
| self.run_worker(self._react_live(self.statement)) | |
| else: | |
| self.say(FILED_LINES[len(self.statement) % len(FILED_LINES)]) | |
| async def _react_live(self, statement: str) -> None: | |
| moment = ( | |
| "the player is dead for good; their final run just ended. their " | |
| f"exit-interview statement for the record:\n{wrap_player_text(statement)}\n" | |
| "acknowledge it for the file" | |
| ) | |
| got_any = False | |
| async for chunk in self.voice.react(moment, taboo=statement): | |
| got_any = True | |
| self.say(chunk) | |
| if not got_any: | |
| self.say(FILED_LINES[len(statement) % len(FILED_LINES)]) | |
| def on_key(self, event: events.Key) -> None: | |
| if self.statement is not None and event.key not in ("enter",): | |
| self.dismiss(self.statement) | |