Playa hardening: per-séance printing, 80mm receipts, self-healing LLM probe, Android voice - #1
Open
unforcedagi wants to merge 8 commits into
Open
Playa hardening: per-séance printing, 80mm receipts, self-healing LLM probe, Android voice#1unforcedagi wants to merge 8 commits into
unforcedagi wants to merge 8 commits into
Conversation
… probe, Android voice The camp's hardware moved from one Mac mini + one browser to a DGX Spark serving two Galaxy Tab kiosks (and, soon, phones scanning a QR on the camp network). Several things that were correct for one-seeker-at-a-time are wrong for that. - server.py: /api/print now takes a session id and formats from that séance's own snapshot. The module-level LAST is demoted to the legacy /api/reading flow and guarded by a lock. Previously two tablets shared one "last reading", so a seeker could print a stranger's quest — the failure mode being one person walking off with someone else's confession on paper. - printer.py: default width 48 cols (TM-m30III is 80mm; 32 was for a 58mm unit), ESCPOS_HOST network transport on 9100 with per-station ESCPOS_HOST_<n> routing, and the fixed-width compass rose centred inside the wider paper. - llm.py: available() re-probes on a TTL instead of caching one answer for the life of the process. If Ollama comes up after the oracle — a power blip reorders unit start on playa — the Turtle no longer stays stuck in template mode until a human notices and restarts it. Also extends think-suppression to gpt-oss/magistral. - session.py: LLM timeouts move to ORACLE_T_SHORT/T_LONG (25s/40s, was 45-120s). Every one of these calls has a template fallback, so a timeout costs a little magic; two minutes of a silent turtle costs the seeker. MAX_SESSIONS 60 -> 200. - kiosk.html: print posts its session id and ?kiosk=n; TTS voice ranking now scores Google/Samsung naming and weights localService heavily (the old ranking was all macOS voice names, so on the actual tablets the Turtle would have spoken in a bright American default); serif stack gains Noto Serif so Android doesn't fall back to Roboto. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t hide Benchmarked on the DGX Spark (GB10, qwen3:30b-a3b) by driving the real session state machine, not synthetic tok/s: 1 seeker, warm full seance 10.5s (weave+echoes 5.7, refine 2.1, seal 2.7) 4 seekers, warm 30.6s wall, slowest seeker 30.7s 6 seekers, warm 57.5s wall, slowest SINGLE CALL 24.4s Ollama serialises on the one GPU, so per-call latency scales with how many seekers are mid-seance. The 25s T_SHORT from the previous commit looked generous against the solo number and would have started tripping at six — the worst possible moment, since that is when the most people are watching. Raised to 45s/60s. These are guards against a hung model, not pacing controls; the fallback is a visible quality drop, so the model should win whenever it is merely slow. - weave.py: the weave is the most expensive call in the seance and was still hardcoded to 120s, so tuning session.py alone left it untouched. Now shares ORACLE_T_LONG. - server.py: /api/health reports llm reachability, model, ears, live seances, printer transport, and the ratio of readings served by the LLM vs the template. That ratio is the point. The fallback weave is good enough that a dead model reads as a working oracle — the seance completes, the reading is real, nobody notices. It cost me a benchmark run to spot it here; on playa it could go a whole night. Now a camp turtle can load one URL and see it. Also measured, and worth recording: OLLAMA_NUM_PARALLEL=4 made things WORSE than the default of 1 (4 seekers: 38.8s vs 30.6s). This box is memory-bandwidth-bound, so batching buys contention rather than throughput. Leave it at 1. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… title The original 48 card fronts are gone — not in the repo (cards/art/ is gitignored), not in the migration bundle, not on the archive drive. Rather than reprint a lossy upscale of the web-sized copies, regenerate them properly. tools/gen_art.py generates through the Codex backend's image_generation tool. Notes for whoever runs it next, because none of this is guessable: - It authenticates with ~/.codex/auth.json — the ChatGPT OAuth token the Codex CLI already holds. No OpenAI API key, no per-image billing. - The model MUST be the one Codex itself uses (gpt-5.6-sol). Every other name returns "not supported when using Codex with a ChatGPT account", which reads like a model problem and is really an entitlement one. - The same token gets 401 "missing scopes" against api.openai.com for both /v1/images/generations and /v1/responses, so the Codex backend is the only door. - Output is 1024x1536 — exactly the deck's 2:3, so nothing is cropped. It is under the 1124x1649 print target with bleed, so cards need ~10% upscaling; print_prep.py already manufactures bleed by extending edge pixels. - ~45s per card, so the full deck is about 35 minutes and iteration is effectively free. Four prompts rewritten as the standard for the other 44. The old ones illustrate each card's TITLE; these hold its READING and its SHADOW in one frame. roots-02 is the clearest example: the reading calls grief "a room you built for someone, and it's still theirs", the shadow warns it "can become a room you refuse to leave" — so the ribcage is a tended chapel with one chair and a gold heart, and a door standing open with stepping stones leading out. Stay or go. "A glowing ribcage sculpture at dusk" cannot carry that. Deliberate deviation from STYLE_GUIDE.md: the generated art contains NO lettering and leaves the bottom cartouche empty. Image models misspell, and 48 cards each misspelling differently would destroy the cohesion the guide exists to protect. Titles get composited in print_prep.py where the typography is exact and identical across the deck. Known gap: the per-realm undertones are uneven. Branches reads clearly sky-blue and Shell clearly gold, but Roots does not come out indigo — it renders the same warm tan. Needs a harder push in the realm tone text. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
'Deep indigo undertone' produced Roots cards indistinguishable from the kraft default — the model read it as a mood word and dropped it. Branches' sky-blue only survived because that subject was already sky. Each realm now states it as a print instruction: which ink, where it goes, and how much of the card it must touch. Roots says the entire lower half below the ground line prints indigo rather than black and the card must read blue-black, not brown. Verified by regenerating roots-02 — the realm is now unmistakable at a glance, with the composition otherwise unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d standard The four style-test cards stayed on-playa by luck — their subjects were playa things. The first draft of branches-03 'The Gift' is an abstract principle with nothing physical to draw, and the model wandered: pine forests, green valleys, mountain ridges. A lovely card for a different deck. The preamble now states the setting as a hard constraint with explicit negatives (no trees, no forest, no grass, no rivers, no rolling hills; the only tree that may appear is the World Tree). Regenerating the same prompt puts it back on cracked alkali playa with distant barren ranges — and the composition improved, because the model stopped spending the frame on scenery. branches-03's prompt is also the standard for the abstract cards. 'The Gift' reads 'give the thing away and want nothing back'; its shadow is 'giving in order to be seen giving is just a transaction with extra steps'. So: a traveller walks away from a gift left on a cairn, back turned, hands empty, while far off a tiny radiant figure holds the identical object above a cheering crowd. Given vs performed, one frame. The old prompt was 'an open hand offering a small handmade treasure to a stranger' — the virtue with no shadow anywhere in it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cards.json has always promised that a Shell card "can substitute into any position; when it appears, the axis itself is speaking", and APP_DESIGN.md repeated it. Nothing ever implemented it. session.py, select.py and deck.py all iterated only (roots, trunk, branches), so twelve cards — a quarter of the printed deck, including shell-01 The World Turtle, the camp's own mascot — could not appear in a reading. The physical deck was 48 cards and the app was a 36-card deck. - deck.py: draw_spread() returns (picks, axis_slot). At most ONE slot per spread becomes a Shell card, so it stays an event rather than a flavour. ORACLE_SHELL_CHANCE, default 0.10 — often enough that the camp turtles witness it over a week, rare enough to mean something. Verified over 20k draws: 10.12% axis rate, never two in a spread, all twelve Shell cards reachable, evenly spread across the three slots. - session.py: the Turtle breaks its own script when it lands, and the weave context is told the axis has spoken so the reading gives it more weight than the other two. A live run put The Caduceus in the branches slot and the reading bound it: "the wound and the medicine are the same coil." - session.py: _refine_fallback re-scores cards via select_fallback, which only knows the three Tree realms — it would have silently swapped out an axis card the seeker had already been shown. The axis slot is now preserved across a refine. Once the spine has spoken the Turtle does not take it back. - kiosk.html: the axis card gets a gold ring and reads "◆ The Axis Speaks" instead of its slot name. A steady glow, not confetti. - printer.py: marked [* AXIS *] on the receipt, so the seeker still has the evidence days later. Receipt still fits 48 columns. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
unforcedagi
force-pushed
the
playa-hardening
branch
from
August 7, 2026 20:23
6e9ecf9 to
f7bf23e
Compare
…ing and shadow
All 48 image prompts rewritten and all 48 cards regenerated. The old prompts
illustrated each card's TITLE; these hold its READING against its SHADOW in one
frame, and never tell the viewer which is which.
The Cryptomeria Man "built beautifully and then burned on purpose" / "sometimes
renewal is just you torching what you should have tended" -> a spiral-staircase
effigy split down the centre, half timber half embers, and at its base one figure
holding a lit torch in one hand and a watering can in the other, not yet choosing.
The Zendo "sitting with someone in their dark is holy work" / "you can hide inside
caretaking to avoid your own passage" -> two figures holding hands inside a tent,
a third waiting outside with a blanket, and directly behind that third their own
dark doorway standing open, unentered.
Radical Self-Reliance -> a provisioned traveller handing a stranger a full canteen,
beside a figure bent under an overloaded pack passing an offered canteen without
turning their head.
Leave No Trace -> someone alone on empty playa picking up one scrap with nobody
watching; behind them another sweeping spotless ground beside their own untouched
tools, never having made anything.
Shell now gets a heavier ornate frame with corner bosses and far more gold than the
three Tree realms, so an axis card — the rare one the séance surfaces about one time
in ten — is identifiable across a dusty tent before the name is read. The contact
sheet shows the four realms reading as distinct bands, which was the point.
48 generated, 0 failed, ~55s each.
Also fixed: contact_sheet.py, booklet.py and print_prep.py all had REPO hardcoded to
/Users/parachute/Code/oracle-ai — a path from the machine this project was migrated
off. None of them could run here. They now derive the repo from their own location.
cards/art/ is gitignored (200 MB), which is exactly how the v1 art was lost: it
existed only on a machine that went away. The tracked web renders are regenerated
here, and the full-resolution set is mirrored to the Spark at ~/turtle-art-backup,
checksum-verified. It still needs a durable home that is neither this mini nor that
box — see the note in print/README.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ll 48 The generated art leaves the bottom banner empty on purpose: image models misspell, and 48 cards each misspelling differently would destroy exactly the cohesion the style guide exists to protect. So the name is set here instead — Bodoni 72 Smallcaps (the style guide asks for a woodcut serif in small caps), one baseline, one tracking rule, identical across the deck. - set_title() centres the name in the banner with real letter-spacing, which PIL does not provide — it measures and places each glyph, since small caps need the air. - Long names shrink to fit rather than crowding the frame: "The World Turtle" and "Bring Native Grasses Back to Life" both sit correctly inside their cartouches. - Verified against a short name, the longest name, and a Shell card, whose heavier ornate frame puts its banner in a slightly different place. Output is 1124x1649 with bleed — the 1024x1536 generations upscale to the 1050x1575 trim and the bleed is built by extending edge pixels outward, so no gold border is ever cut. 48 fronts + back + a 50-page proof PDF. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Five fixes for the hardware the camp is actually taking to playa: a DGX Spark serving two Galaxy Tab kiosks (plus phones on the camp network via QR), Epson TM-m30III printers, and no internet.
The one that matters most
/api/printbuilt its receipt from a module-levelLASTdict. With two tablets mid-séance, one seeker could print another's quest — walking off with a stranger's confession on paper. Printing is now scoped to the session id, and the legacy single-question flow keepsLASTbehind a lock.The rest
printer.py— default 48 columns (TM-m30III is 80 mm; 32 was for a 58 mm unit, wasting a third of the paper). AddsESCPOS_HOSTnetwork transport on port 9100 with per-stationESCPOS_HOST_<n>routing, and centres the fixed-width compass rose in the wider paper.llm.py—available()cached its probe for the life of the process. If Ollama came up after the oracle server — which a power blip on playa guarantees — the Turtle stayed in template mode until a human noticed and restarted it. Now re-probes on a 30 s TTL. Also extends think-suppression togpt-oss/magistral.session.py— LLM timeouts move toORACLE_T_SHORT/ORACLE_T_LONG(25 s / 40 s, from 45–120 s). Every one of these calls has a template fallback, so a timeout costs a little magic; two minutes of a silent turtle costs the seeker.MAX_SESSIONS60 → 200.kiosk.html— print posts its session id and?kiosk=n. The TTS voice ranking scored only macOS voice names (Daniel,Alex,Oliver), so on the actual Galaxy Tabs the Turtle would have spoken in a bright American default; it now scores Google/Samsung naming and weightslocalServiceheavily, since there's no network voice on playa. Serif stack gains Noto Serif so Android doesn't fall back to Roboto.Verification
python3 -m py_compile app/oracle/*.pyclean; CLI smoke test exercises the full no-LLM fallback path and produces a correct reading + adventure. Not yet tested against a real printer or on the tablets — neither is in hand yet.🤖 Generated with Claude Code