| 1 | # Codewhale: a whale living in code |
| 2 | |
| 3 | The current live experience is [one durable pet with attached views](SHARED.md): |
| 4 | Ratatui Watch/full habitat, browser, native companion and mobile Shared modes. |
| 5 | That guide contains build/run commands, source selection and the ownership |
| 6 | contract. The standalone demos and file studies described below stay isolated. |
| 7 | |
| 8 | |
| 9 | The dots are Codewhale's material. The whale is its recognizable home form; |
| 10 | real activity can reorganize that material into a living expression of the work. |
| 11 | This is an evolving audiovisual instrument, not a mascot that asks for attention. |
| 12 | |
| 13 | The current implementation has a persistent 980-particle world, thirteen event |
| 14 | categories, deterministic audio, replay and checkpoints. Version 2 reorganizes |
| 15 | the same particles into knots for reasoning, woven strands for code, branches for |
| 16 | filesystem activity, scanning layers for browsing, and circulating paths for |
| 17 | network traffic. Human input opens a junction in the field. It does not steer |
| 18 | the creature toward its owner. The whale re-forms as activity settles. |
| 19 | These are authored expressions of measured activity, not generated pictures of |
| 20 | arbitrary task content. Missing telemetry stays visibly unknown. |
| 21 | |
| 22 | ## Build from this repository |
| 23 | |
| 24 | Requires Node 22.13 or later and Python 3 for the local static server. |
| 25 | No sibling repository, investor folder, credentials or provider call is needed. |
| 26 | |
| 27 | ```sh |
| 28 | npm --prefix pet ci --ignore-scripts |
| 29 | npm --prefix pet run check |
| 30 | npm --prefix pet start |
| 31 | ``` |
| 32 | |
| 33 | Open <http://127.0.0.1:4632/pet.html>. Wild is a simulated creature; Event demo |
| 34 | is synthetic telemetry. Import trace / tape accepts event-v1, OTLP, existing |
| 35 | Codewhale session/Runtime journals and saved pet recordings. Sound requires an |
| 36 | explicit gesture. Still preserves a static semantic pose. Follow local tape |
| 37 | uses the browser's File System Access API; other browsers support import/export. |
| 38 | |
| 39 | ```sh |
| 40 | # Regenerate embedded QuickJS and Apple bundles from the same source. |
| 41 | npm --prefix pet run sync |
| 42 | # Full product terminal; use /pet and /pet export. |
| 43 | cargo build --locked -p codewhale-tui --bin codewhale-tui |
| 44 | # Small standalone braille renderer, without the full product link. |
| 45 | cargo fetch --locked --manifest-path pet/tui/Cargo.toml |
| 46 | cd pet |
| 47 | ./verify.sh --no-swift |
| 48 | ``` |
| 49 | |
| 50 | `./verify.sh` without the flag additionally requires Swift and compares all |
| 51 | three ports. The script fails for a missing tool or failed build; it does not |
| 52 | silently call an omitted platform verified. See [QA.md](QA.md) for native builds. |
| 53 | |
| 54 | The [Android application](android/README.md) builds with its Gradle wrapper and |
| 55 | JDK 17. Compose, native audio, checkpoint import/export and lifecycle behavior |
| 56 | are exercised on an Android 15 emulator; CI uploads the debug APK and test reports. |
| 57 | |
| 58 | In the full terminal, `/pet sound on` enables the shared score and |
| 59 | `/pet sound off` mutes it. Sound starts off each time the application |
| 60 | opens. Install FFmpeg with `ffplay` on PATH to use this optional output; Watch |
| 61 | and recording work without it. The terminal streams the core's stereo 48 kHz |
| 62 | PCM to one player process. Hiding Watch, opening a modal, quiet mode, or stale |
| 63 | telemetry presentation suspends output. Reopening starts at the current clock, |
| 64 | without playing the intervening history. Player failure mutes sound and reports |
| 65 | a warning while the world continues. `/pet sound` shows its status. |
| 66 | |
| 67 | ## One source of meaning |
| 68 | |
| 69 | `src/core/pet-telemetry.ts` derives 400 ms buckets from normalized Whalesong |
| 70 | event-v1. Measured span occupancy selects the channel; onset counts, repeat |
| 71 | density, error receipts, human requests and agent identities carry other facts. |
| 72 | Container spans are excluded. Open-ended spans without observation coverage, |
| 73 | disconnects and missing intervals are unknown, not successful idle time. |
| 74 | |
| 75 | `pet-world.ts` advances a 30 Hz creature clock and named seeded random streams. |
| 76 | It journals accepted telemetry, interactions, behaviors and persistent pod slots. |
| 77 | `pet-audio.ts` schedules voices from that same clock. Noise uses absolute sample |
| 78 | positions, so buffer partitioning cannot change the score. Audio mixing is a |
| 79 | host concern. `pet-native.ts` exposes this exact core to QuickJS/JavaScriptCore. |
| 80 | The TUI, Apple and Android hosts do not carry their own event bucketers or score schedulers. |
| 81 | Apple copies validated Float32 PCM channels directly from JavaScriptCore into |
| 82 | native buffers; it does not serialize sample arrays as JSON. AVAudioEngine |
| 83 | follows the world clock, rebases presentation after a stall, and discards stale |
| 84 | voices after a long gap. Device or rendering failures mute sound and report a |
| 85 | message while the world, persistence and export continue. |
| 86 | |
| 87 | The Rust particle implementation lives in the product's |
| 88 | `crates/tui/src/tui/ambient_life/pet_sim.rs`; this package's runner imports it. |
| 89 | Swift and Kotlin particle ports must match its conformance digests. Generated |
| 90 | native bundles are committed so the product Rust build needs no Node compiler. |
| 91 | Run `npm run sync` after changing core source; the generated-byte check in CI |
| 92 | rejects a stale bundle. Local Whalesong consumers use aliases to these same |
| 93 | canonical files, not separately maintained source copies. |
| 94 | |
| 95 | The original Whalesong importer, signal model, schema and browser storage code |
| 96 | are included because they are actual dependencies of the pet. Their original |
| 97 | Apache-2.0 [license](LICENSE) and [notice](NOTICE) are retained. Rust files imported |
| 98 | from the product retain that repository's license. |
| 99 | |
| 100 | ## Live recording |
| 101 | |
| 102 | The adapter only reads the existing Runtime journal endpoint |
| 103 | `GET /v1/threads/{id}/events`. It never starts a turn. |
| 104 | |
| 105 | ```sh |
| 106 | cd pet |
| 107 | node scripts/pet.mjs --runtime=http://127.0.0.1:7878 --thread=THREAD_ID --output=pet.jsonl |
| 108 | node scripts/pet.mjs --input=trace.jsonl --output=other.pet.jsonl --watch |
| 109 | # Restart an existing live recording at the same path: |
| 110 | node scripts/pet.mjs --runtime=http://127.0.0.1:7878 --thread=THREAD_ID --output=pet.jsonl --resume |
| 111 | node scripts/pet.mjs --demo --output=demo.pet.jsonl |
| 112 | ``` |
| 113 | |
| 114 | Choose an existing thread and an unused output path, or use `--resume` to restart |
| 115 | a stopped live recorder at its existing path. Optional authentication |
| 116 | comes from `CODEWHALE_RUNTIME_TOKEN`; tokens are rejected in URLs. Only plain |
| 117 | HTTP loopback IP origins are accepted. Redirects, invalid envelopes and cursor |
| 118 | holes are rejected; reconnects resume from the last accepted Runtime cursor. |
| 119 | The recorder requests the Runtime stream's opt-in replay-progress capability. |
| 120 | It remains unknown until durable replay and the queued live tail have drained; |
| 121 | receiving the first historical event does not make it current. Broadcast-lag |
| 122 | recovery returns the stream to replaying. Older Runtime/SDK combinations without |
| 123 | this capability stop input explicitly and leave the recording unobserved; use |
| 124 | the Runtime built from this source alongside the recorder. |
| 125 | The recorder seals the preceding observation interval against a fixed clock. |
| 126 | It retains request lifetimes and counts a delayed error once at receipt time. |
| 127 | Raw prompts, arguments, results and tokens do not enter the pet recording. |
| 128 | Live recording continues in segments. At 216,000 buckets (24 hours) or 64 MiB, |
| 129 | the recorder syncs the completed file, preserves it as |
| 130 | `OUTPUT.segment-000001.jsonl` (then `000002`, etc.), and atomically replaces the |
| 131 | same live pathname. Each segment starts at sequence zero and replays independently. |
| 132 | Use `--segment-buckets=N` to rotate sooner. Followers establish a new baseline |
| 133 | after replacement, then accept subsequent appends as current observations. |
| 134 | |
| 135 | The live importer retains unfinished lifetimes and 16 seconds of completed |
| 136 | events for the bucketer's recurrence window. It removes raw payloads immediately; |
| 137 | 250,000 events and 64 MiB bound retained metadata, not total session history. |
| 138 | Completed output segments remain on disk, so disk use grows with recorded history. |
| 139 | Rotation requires same-directory hard links and atomic replacement. Unsupported |
| 140 | storage, an archive-name collision or an external replacement stops recording |
| 141 | without overwriting the existing files. With `--resume`, the recorder validates |
| 142 | the previous complete tape, preserves its exact bytes in the next numbered |
| 143 | archive, and starts a new segment at the same live path. The first bucket is |
| 144 | unknown; fresh source observations follow. It never invents events or estimates |
| 145 | the duration of an outage from file timestamps. The companion's separately saved |
| 146 | habitat preserves its particles and clock across attachment. |
| 147 | |
| 148 | A private, empty `OUTPUT.writer-lock` sidecar uses Node's built-in SQLite OS lock |
| 149 | to exclude simultaneous recorders. Keep this file in place; its lock is released |
| 150 | on close or process death without deleting a stale PID file. It contains no |
| 151 | events. Use local storage with working OS locks, hard links and atomic rename. |
| 152 | Malformed, incomplete, oversized or non-file previous tapes are preserved and |
| 153 | rejected; use a new output path while retaining the original for recovery. |
| 154 | `--resume` applies only to live recording, and can also create an unused path. |
| 155 | |
| 156 | The thin wire is JSONL, one flat version-1 `PetBucket` per line: PetState plus |
| 157 | `sequence`, `simTimeMs`, `durationMs`, thirteen-element `onsets` and `activeMs`, |
| 158 | `errors`, `agentIds` and `waiting`. Sequence starts at zero in 400 ms steps. |
| 159 | Saved replay JSON contains this accepted tape and the interaction journal; |
| 160 | a versioned checkpoint also contains particle, random-stream and score cursors. |
| 161 | Recordings also store `expressionVersion`: new worlds use version 2; recordings |
| 162 | without this field retain the original version 1 particle and behavior rules. |
| 163 | Checkpoints must agree with the recording's expression version. Unsupported or |
| 164 | mismatched versions are rejected before changing the current world. This keeps |
| 165 | old saved habitats replayable while letting new worlds change their visual form. |
| 166 | The older TSV is a particle conformance tape and cannot preserve audio onsets. |
| 167 | |
| 168 | macOS watches `~/.codewhale/pet-state`. iOS watches `pet-state` in Documents. |
| 169 | Android uses More → Follow file study to select a seekable device document. |
| 170 | The browser's Follow local tape uses a user-granted File System Access handle. |
| 171 | All three file readers use the same shared live cursor: the first complete packet |
| 172 | establishes a baseline, and only an advancing sequence becomes an observation. |
| 173 | Duplicate input, a restarted sequence, or bytes read during suspension cannot |
| 174 | replay an old onset or human request. Missing/invalid input expires to unknown; |
| 175 | resuming advances beyond already accepted input without replaying its sound. |
| 176 | Apple watches appends and directory replacement. Android reads a bounded 256 KiB |
| 177 | tail on an IO worker, closes it on pause/background, and discards delayed delivery. |
| 178 | The file must be updated by a producer; selecting a completed tape does not make |
| 179 | it live. Use Import to replay that tape. Device files are not automatically synced |
| 180 | from the desktop recorder. |
| 181 | The current live chain has been exercised with typed synthetic Engine events |
| 182 | through the actual Runtime journal/SSE endpoint, recorder process and Apple |
| 183 | file watcher/host. The world receives a fresh human request and clears it after |
| 184 | its answer; prompt, question and answer text stay out of the recording. This |
| 185 | uses the existing mock Engine handle without calling a provider. The older |
| 186 | read-only Runtime 0.9.13 receipt predates the required progress capability and |
| 187 | does not establish current compatibility. |
| 188 | |
| 189 | ## Persistence and current limits |
| 190 | |
| 191 | The standalone browser commits checkpoint and recording together with an optimistic |
| 192 | IndexedDB revision. A saved live source reopens as Replay until explicitly |
| 193 | reattached. Legacy TUI `artifacts/pet/habitat.json` files remain recoverable; current Watch attaches to the companion. |
| 194 | Apple hosts keep source-specific files in Application Support/CodewhalePet. |
| 195 | Android keeps separate wild/demo/recording/live habitats in private app storage. |
| 196 | Native writes are private and atomic, use a writer lock and content revision, |
| 197 | and preserve corrupt files or external edits. Recovery is visible in the UI. |
| 198 | |
| 199 | Modern checkpoints hydrate without replaying historical simulation. Live resume |
| 200 | starts unknown and drops old human requests. Apple legacy wild preferences |
| 201 | migrate only after a successful checkpoint save. Native autosave is every five |
| 202 | seconds, so a crash may lose work since the last successful save. |
| 203 | |
| 204 | New worlds use version 2 recordings with an exact starting checkpoint and |
| 205 | absolute telemetry sequences. After 1,024 consumed buckets or 4,096 applied |
| 206 | inputs, the host saves completed history as an immutable segment before replacing |
| 207 | the active habitat. Only then does the running world retire that history. Its |
| 208 | particles, random streams and score continue without a reset. Gaps remain unknown; |
| 209 | future imported events stay in the active segment. Version 1 imports still work. |
| 210 | |
| 211 | Earlier recordings opens saved segments in the browser and exports them on Apple |
| 212 | and Android. TUI segments are JSON files beside `habitat.json` in the session's |
| 213 | `artifacts/pet` directory. Each segment replays independently from its starting |
| 214 | checkpoint; seeking cannot precede that point. Browser source changes archive |
| 215 | the entire outgoing recording, including unplayed imported events. Archived files |
| 216 | are retained, so disk use grows with recorded history even though active history |
| 217 | is bounded. |
| 218 | |
| 219 | Native autosaves and native imports are limited to 8 MiB; the QuickJS worker |
| 220 | has a 64 MiB memory limit. All four hosts export recordings in chunks, including |
| 221 | the exact checkpoint, with a 64 MiB file limit. Files over 8 MiB can be recovered |
| 222 | in the browser. Android finishes the export in a private staging file before |
| 223 | opening the user-selected destination. Apple source changes keep the current |
| 224 | visit when saving fails; leaving without saving requires an explicit choice. |
| 225 | |
| 226 | A 30-hour synthetic Still run of the shared core rotated 263 segments and kept |
| 227 | the active file below 0.45 MB, with exact checkpoint continuation after every |
| 228 | rotation. Apple and Android separately exercise archive publication, failed saves |
| 229 | and continued execution in their actual embedded engines. This is not 30 hours |
| 230 | of animated native-device or power testing. Current platform builds, replay, |
| 231 | live file delivery and measured audio output have separate receipts in [QA.md](QA.md). |
| 232 | Automatic desktop-to-phone transport is not included: mobile hosts consume a |
| 233 | local file using the shared contract. Physical-phone listening/battery quality |
| 234 | and current macOS popover inspection have not been established. The source is |
| 235 | available as a development build; this branch has not been released. |
| 236 |