Tom draws back — the oracle can answer with a sketch#17
Conversation
The persona now carries a drawing protocol: when the writer asks for a drawing, a map, or a move in a drawn game, the model may emit one ⟦draw:x,y x,y; …⟧ block of quill strokes on a 100×100 canvas, placed among its words. The stream parser routes complete blocks as Event::Draw (prose before a block is flushed, unterminated tails are dropped, bad blocks are ignored); draw.rs scales the sketch onto the page, centers it below the prose written so far, and subdivides every segment into pen-sized steps with a slow sine wobble so the lines read as drawn by a hand, not plotted. The reply animator inks it like any other streamed chunk, and prose may continue beneath. --oracle-test prints the raw strokes of any sketch, for judging how well a model draws before pointing the diary at it.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9adecefd5d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return out; // directive still streaming in | ||
| } | ||
| out.push(Err("unfinished conjuring directive".into())); | ||
| out.push(Err("unfinished directive".into())); |
There was a problem hiding this comment.
Handle truncated leading draw blocks without an error
When a reply starts with a draw block and the model or stream ends before the closing ⟧, this error path runs before the body scanner can apply the new “drop unterminated draw tails” behavior. In that truncated-sketch scenario, e.g. a drawing cut off by RIDDLE_OPENAI_MAX_TOKENS or a stalled stream, the diary reports an oracle failure and skips transcript handling, whereas non-leading unfinished draw blocks are silently dropped as intended.
Useful? React with 👍 / 👎.
A drawn game exposed two amnesias. First, only prose was kept in the reply record — the ⟦draw:…⟧ block was consumed by the parser and never stored, so the next turn's history had no trace of the board Tom drew: every move looked like move one, and he took the centre again and again. The serialized block now rides in the reply record, and the protocol tells him to repeat his last block's strokes exactly and add only the new marks, so the board never shifts between turns. Conjuring learned to cope: stored blocks are stripped from the spoken text (raw coordinates are never inked) and the sketches they describe are redrawn on the remembered page in faded ink, after the words.
|
Follow-up after a real game of noughts and crosses on the device: the first playtest exposed two amnesias — the ⟦draw⟧ block wasn't kept in the reply record (so every turn looked like move one and Tom took the centre repeatedly), and the writer's X reached the oracle as a lone X on white paper (the snapshot replays only the writer's strokes, so the board wasn't in it). This branch now keeps the serialized block in the reply record and tells Tom to repeat his last block's coordinates exactly when continuing a game; conjuring strips the raw block from spoken text and redraws the sketch in faded ink instead. The visual half (replaying the sketch beneath the writer's ink in the oracle snapshot) builds on the stroke-model Verified with a board-plus-centre-X snapshot against gpt-5.5: "A bold opening. I shall take a corner." and a redraw with the grid unchanged, the X still in the centre cell, and an O in the top-left corner. |
Ask in ink — "draw me a little house with a tree" — and the reply is a drawing: pen strokes inking themselves onto the page in Tom's hand, placed among whatever words come with them. Draw a noughts-and-crosses grid and make a move: the writer's ink fades when the diary drinks it, so Tom redraws the whole board with his move added.
How
⟦draw:x,y x,y; …⟧block — quill strokes on a 100×100 canvas — anywhere in its reply; where the block sits among the words is where the sketch sits on the page.StreamParsergained a span-aware body scanner. Prose still streams sentence-by-sentence; a complete draw block flushes the prose before it and becomesEvent::Draw; unterminated tails are dropped; unparseable blocks are ignored (never inked, never an error); a stray mid-prose⟦show⟧is swallowed exactly as before. Leading⟦show:N⟧routing is unchanged.draw.rs, new): the sketch's canvas bbox is scaled uniformly (capped at 900px / page margins / remaining room below the prose), centered, and every segment is subdivided into ~2.5px steps displaced by a slow sine wobble (dying at the model's own vertices) so lines read as drawn by a hand, not plotted. Deterministic per sketch.WritePlanstrokes — the existing reply animator inks it, and streamed prose continues beneath it.--oracle-testprints[sketch: N strokes, M points]plus the raw strokes, for judging how well a model draws before pointing the diary at it.Tested end-to-end against gpt-5.5: "draw me a little house with a tree beside it" produced a coherent 8-stroke house (door, window, gabled roof) and tree (trunk, lumpy crown, two branches) on the first try, alongside the prose "Of course."
16 new unit tests (7 parser, 9 draw); the full suite passes. The prompt gates drawing on the writer asking (or a game demanding it), so ordinary diary turns are unaffected.