Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"style": {
"type": "string",
"title": "Scene",
"description": "fire: a ░▒▓█ fire with sparks and smoke; warp: stars that speed up to hyperspace; avalon: a colony ship whose shield burns up the asteroids that hit it; balloon: a hot-air balloon that climbs from the grass to space; engine: a steampunk engine that runs up to full speed; falcon: a Falcon 9 carrying Dragon: its booster lands on its legs, Dragon comes home under parachutes to a splashdown; starship: a bigger rocket that hot-stages: its booster is caught by the tower's arms, the Ship splashes down; surf: a surfer and the swell; ski: a skier down the mountain; bubbles: bubbles fizzing up; train: a train through the countryside, faster with the work",
"description": "fire: a ░▒▓█ fire with sparks and smoke; warp: stars that speed up to hyperspace; avalon: a colony ship whose shield burns up the asteroids that hit it; balloon: a hot-air balloon that climbs from the grass to space; engine: a steampunk engine that runs up to full speed; falcon: a Falcon 9 carrying Dragon: its booster lands on its legs, Dragon comes home under parachutes to a splashdown; starship: a bigger rocket that hot-stages: its booster is caught by the tower's arms, the Ship splashes down; surf: a surfer and the swell; ski: a skier down the mountain; bubbles: bubbles fizzing up; train: a train through the countryside, faster with the work; earthrise: the Earth over the moon, rising and turning faster with the work; doom: a space marine fighting through a 1993 techbase, faster and harder with the work; doom3d: the same fight through his own eyes, in a taller band",
"default": "fire",
"options": [
"fire",
Expand All @@ -49,7 +49,10 @@
"surf",
"ski",
"bubbles",
"train"
"train",
"earthrise",
"doom",
"doom3d"
]
},
"idle": {
Expand Down
8 changes: 5 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ hooks/
fire.ts the fire scene's automaton (its heat grid), glyphs and 256-color ramps
fire-palette.ts the fire scene's truecolor ramps
<scene>.ts one file per scene: starfield (warp), colony, balloon + sky + clouds/,
engine, rocket (falcon, starship), surf, ski, bubbles, train
engine, rocket (falcon, starship), surf, ski, bubbles, train, doom (doom3d)
pi/
index.ts pi adapter (widget above the editor, /flow, ~/.pi/agent/flow.json)
picture.ts a scene's `picture` as a kitty image: whether the terminal draws them, its size, the escape sequence
session.ts pi's settings for a session: flow.json's defaults and the session's own (pure)
ansi.ts mapping.ts types.ts
sounds/ the soundscapes' clips (AAC), built by scripts/make-sounds.ts
Expand All @@ -58,16 +59,17 @@ A scene implements `Scene` (`hooks/styles.ts`). Its file exports a `SceneDef` (`
- **Dials**, set every frame by `SceneDriver.dial()`:
- `strength` 0–10: 0 draws nothing, every cell blank; 1 is calm idle; 10 is the busiest. Ease changes; never jump.
- `coverageBoost`: above 0 while subagents run (15 each; in manual mode too). Add company (more streams, thicker steam).
- `agents`: each running subagent on its own (`AgentDial`: a stable `id`, `state` `'working' | 'idle' | 'waiting' | 'done'`, `ok` once done, its `task` and `type`, `ms` run), in the order they started (in manual mode too: holding a level holds how busy it looks, not who's about); `[]` in pi. The adapter derives it (`Roster`, `agents.ts`) from `agent.list()` polls (teammates left out) and each agent's own events by `agentId`: a model step or streamed output or a tool call is working, a tool running keeps it working, nothing for `QUIET_MS` (20 s) or a held/between-turns status is idle, a dialog put to the person is waiting (a permission once `classic.PermissionRequest` shows its dialog, since auto mode's classifier settles most asks alone, matched to that loop's running call of the tool (by its input when the loop runs several), each noted as `tool.call` starts it, so another call ending never ends it; `AskUserQuestion`/`ExitPlanMode` from the start; it ends with the call, a refusal too since the permission prompt runs beneath `tool.call`, or its `ToolProgress` pill; one tracker, `Waits` in `waits.ts`, keeps every loop's waits for the roster and the `waiting` dial alike), its `turn.complete` (or its status ending, or its leaving the list) is done, kept `DONE_MS` (6 s) so its companion can leave. What's heard from a loop before a poll names it (tools running, waits) is kept and counts once it is. A scene that gives each agent a companion keeps a `Crew` (`crew.ts`): set `crew.room` to how many companions this layout can actually show (the spine fits fewer), `update(this.agents, this.coverageBoost)` once a frame, then draw `crew.mates`, every one of them on screen: each keeps its `slot` (its colors, its place; only slots below `room` are given, and a layout with less room sends the rest back to wait) while on screen, `here` eases 0 → 1 as it arrives and back to 0 as it leaves (then its slot frees for an agent waiting past `capacity`), `busy` eases 1 working / 0 resting, `waiting` and `leaving`/`ok` say the rest (`resting`, `beckon` for a waiting one's blink, `finished`, `failed`, `easeTo` and `crew.inSlot` say them alike in every scene). Arrive from an edge, work visibly, rest visibly (and show waiting on you), leave out of sight (failed, another way); never pop. An adapter with no list (only a count) still gets anonymous working companions from `coverageBoost`. Note each companion's cells with `crew.mark` while drawing and return `crew.marks` from `agentMarks()`: desktop puts the agent's hover card there. Fire, surf, ski, balloon, falcon, starship (a mini rocket each: a hosted `LaunchSite` flying the same code, a few frames late, in the host's world and pixel layer), engine, train and avalon (probes launched from the ship) have companions (the fire's are small fires of their own beside the main one, which narrows to make room: one automaton, its burners shaped column by column); warp and bubbles keep plain coverage (a first-person stream, anonymous bubbles: nothing stays put to be someone).
- `agents`: each running subagent on its own (`AgentDial`: a stable `id`, `state` `'working' | 'idle' | 'waiting' | 'done'`, `ok` once done, its `task` and `type`, `ms` run), in the order they started (in manual mode too: holding a level holds how busy it looks, not who's about); `[]` in pi. The adapter derives it (`Roster`, `agents.ts`) from `agent.list()` polls (teammates left out) and each agent's own events by `agentId`: a model step or streamed output or a tool call is working, a tool running keeps it working, nothing for `QUIET_MS` (20 s) or a held/between-turns status is idle, a dialog put to the person is waiting (a permission once `classic.PermissionRequest` shows its dialog, since auto mode's classifier settles most asks alone, matched to that loop's running call of the tool (by its input when the loop runs several), each noted as `tool.call` starts it, so another call ending never ends it; `AskUserQuestion`/`ExitPlanMode` from the start; it ends with the call, a refusal too since the permission prompt runs beneath `tool.call`, or its `ToolProgress` pill; one tracker, `Waits` in `waits.ts`, keeps every loop's waits for the roster and the `waiting` dial alike), its `turn.complete` (or its status ending, or its leaving the list) is done, kept `DONE_MS` (6 s) so its companion can leave. What's heard from a loop before a poll names it (tools running, waits) is kept and counts once it is. A scene that gives each agent a companion keeps a `Crew` (`crew.ts`): set `crew.room` to how many companions this layout can actually show (the spine fits fewer), `update(this.agents, this.coverageBoost)` once a frame, then draw `crew.mates`, every one of them on screen: each keeps its `slot` (its colors, its place; only slots below `room` are given, and a layout with less room sends the rest back to wait) while on screen, `here` eases 0 → 1 as it arrives and back to 0 as it leaves (then its slot frees for an agent waiting past `capacity`), `busy` eases 1 working / 0 resting, `waiting` and `leaving`/`ok` say the rest (`resting`, `beckon` for a waiting one's blink, `finished`, `failed`, `easeTo` and `crew.inSlot` say them alike in every scene). Arrive from an edge, work visibly, rest visibly (and show waiting on you), leave out of sight (failed, another way); never pop. An adapter with no list (only a count) still gets anonymous working companions from `coverageBoost`. Note each companion's cells with `crew.mark` while drawing and return `crew.marks` from `agentMarks()`: desktop puts the agent's hover card there. Fire, surf, ski, balloon, falcon, starship (a mini rocket each: a hosted `LaunchSite` flying the same code, a few frames late, in the host's world and pixel layer), engine, train, avalon (probes launched from the ship) and doom and doom3d (marines of a co-op squad) have companions (the fire's are small fires of their own beside the main one, which narrows to make room: one automaton, its burners shaped column by column); warp and bubbles keep plain coverage (a first-person stream, anonymous bubbles: nothing stays put to be someone).
- `tint`: `'smoke'` (a failed command or compaction) or `'blue'` (context nearly full). Must be clearly visible in every scene, at every level, in both layouts, and to colour-blind eyes: move lightness or blue-yellow, not only red-green (white to cyan, say, is a change of red alone, and red-green colour blindness can't see it). `npm run cvd` measures it.
- `night`: for scenes with a night version. Ease a `kNight` toward it with `easeNight` rather than switching in one frame. Use `night.ts` so every night matches, and falls at the same pace.
- `waiting`: Claude waits on the person (a dialog is up for them: a permission, its question, a plan to approve; auto mode only, never in pi). Any loop's dialog counts (a background subagent's too); the main turn's 30 s clock stops for the main loop's own waits and for a dialog up, not for a subagent's ask the mode is still deciding, and the main turn ending forgets only its own. A wait is its session's: the adapter forgets them when the session ends, so a /clear or a resume never carries one over. The level drops to a calm 2 by itself (`WAIT_LEVEL`). Every scene eases a `kWait` toward it (`easeWait`) and ends `grid()` with `waitTone(out, kWait, t)`: the frame turns sepia and breathes, the same in every scene, so it reads at a glance and never like smoke or blue (`PixelScene` does both; `d.wait` says how far). Then settle and hold your own way, never as if working: a place the level stands for holds (the balloon hovers, a rocket keeps its stage and doesn't launch), what moves comes to rest where that's natural (the skier stops, the engine runs down, the stars stop, the train draws up at a signal or a platform). A light can shine through the sepia, only breathing (`waitTone`'s `lamps`; in a `PixelScene`, paint it with `px.lamp`), as the train's signals do. A warm scene can skip the sepia (`waitTone(..., 0)`) but must breathe, as the fire's coals do.
- **Sound** (`hooks/sound.ts`, see *Soundscapes* below): a scene's bed is layers of clips (`LAYERS`) at gains from the level and its `ambience()`, mixed ahead for each of its moods (`MOODS`); its events (`sounds`, an array the scene pushes `{ kind, v }` onto as things happen on screen, through `hear`, which caps it, so an adapter that never takes them stays bounded; pi takes and drops them each frame) play a clip (`EVENTS`) or, small and dense ones, a synthesized burst (`VOICES`). A scene without layers is silent.
- **Sizes**: the band is 5 rows × 80–250 columns, above the prompt. The spine is a pane about 13–22 columns × 30–60 rows. Every scene must look right in both.
- **Sizes**: the band is 5 rows × 80–250 columns, above the prompt. The spine is a pane about 13–22 columns × 30–60 rows. Every scene must look right in both. A scene with a view taller than the band's (`Scene.taller`: doom3d, the marine's own eyes) is given more above the prompt: in pi flow.json's `rows` (14 unless it says, at most half the terminal), in Claude Code as many as the prompt leaves, up to 14 (`TALL_ROWS`); `npm run check` times it at 250 × 14. It must still look right at 5 (a short terminal, the picker's thumbnail). Its first-person view, wider than tall, is a panorama (each column its own ray, about 140° across a wide band) rather than the spine's flat projection, so the far edges don't stretch.
- **Drawing**:
- Most scenes paint a pixel layer at 2 × 2 pixels per cell, then fold it into quadrant glyphs with `fitQuad`. A cell holds only two colors; give `fitQuad` a sprite's pixels as its `keep` so a small figure never drops out of its own cell.
- Braille (`BRAILLE`, 2 × 4 dots a cell) is for fine specks: spray, stars, bubbles.
- `DEFAULT_COLOR` (0x01000000) is the terminal's own color, i.e. transparent.
- A scene can also draw itself at real pixels (`Scene.picture(w, h, budget)`: RGBA, the open sky transparent), which pi shows as a kitty image in a terminal that draws them (`pi/picture.ts`); earthrise alone does so far, as a halftone like its original (each cell of a square grid a dot sized by its light, Bayer-dithered, in the original's palette), its ground built at the dots' grid, not at every pixel. A new size may take a while to make: `picture` makes `budget` of it a call and returns undefined until it's done (pi makes it between frames, showing the cells meanwhile). What took long goes in `pictureCache()` under `pictureKey(w, h)`, and pi keeps it in `~/.cache/flow` (`restorePicture` takes it back): bump the scene's version of it when what it makes changes.
- **Limits**:
- Claude Code's `Raster` paints at most 1024 distinct (fg, bg) pairs a frame and nearest-maps the rest, so quantize gradients.
- Keep `step()` + `grid()` under ~2 ms at 250 × 5 and 22 × 60; frames run at ~14 fps (8 when calm).
Expand Down
Loading