Mosaic
A Takuzu field an agent has to explore: tiles of mixed shape and size
(rectangles, L, T, plus, notched) scattered over a grid with holes,
overlapping each other by shared cells. Every tile is a binary puzzle on
its own lines (each maximal horizontal or vertical run of its cells:
balanced, no three in a row, no two equal lines of one orientation and
length); the only rule across tiles is that shared cells agree. Every tile
alone has 2-4 solutions; the field as a whole has one. This repo state is
a localhost referee and spectator for watching agents work it live, with
every step replayable. PLAN.md is the build order and the decisions
(manifest v3 is the current shape).
Layout
engine/ Go: mosaic-engine gen | judge | pack (pure JSON in/out; the only place Takuzu rules exist)
site/ Deno 2 + Fresh 2: referee, append-only JSONL log, /mcp, SSE, canvas viewer
docs/ architecture page (fishbowl.html) and the reference renderer (mock-mosaic.py)
The engine's only dependency is github.com/ (pure Go
CDCL SAT solver) behind engine/: completion, feasibility and counting
of one tile window. engine/ is the propagation solver that models
human technique (forced/guess attribution, difficulty ladder).
Run
cd engine && go test ./... && go build -o bin/mosaic-engine .
cd site && deno install && deno task dev # prints the agent prompt, serves http://127.0.0.1:8000
Open http:/, paste the printed prompt into Claude Code or
Codex, and watch. Replay a finished game at /; its log is
at /. The hosted daily is at https://mosaic-daily.fly.dev/
(one field per UTC day, calendar and leaderboards; see deploy.md).
Scripted client (also the protocol smoke test):
cd site && deno task solve -- --seed a --G 27 --kind grid --n 14 # the phase-1 2x2
cd site && deno task solve -- --seed a --G 60 --kind scatter # a scatter field
cd site && deno task solve -- --seed a --kind field --chunks 3 # a chunked field that grows
cd site && deno task solve -- --url https://mosaic-daily.fly.dev/mcp --join day-<date> --agent me --model my-model
Boards are fields of shapes (manifest v3, see PLAN.md): mosaic-engine pack --kind scatter --G N drops shapes at seeded random positions with
rectangular overlaps and small holes (--coverage, --compound,
--max-hole); mosaic-engine gen --seed S --G N --kind scatter fills
one. A daily-sized field is chunked: mosaic-engine field --seed S --G 1000 --chunks 3 --chunk-G 60 --rings all generates the centre cluster
and every ring around it (checkerboard of chunks, each a process); the
site reveals rings during the day as grow events. grid (X-by-X
squares of side n, --X --n) is kept for the 2x2 standard and the X=15
envelope. Generation is a batch job cached under site/,
keyed by generator version (mosaic-engine version), seed and
parameters; the nightly runs on slate (.woodpecker/) and
pushes the next day's field to the hosted app. Measured timings are in
PLAN.md.
Tests
engine:go test ./covers rules on shapes and lines (attribution, enumeration with rule masks, coupling on shared cells, flip rectangles on shared lines, the coupled-tile CSP, shape validity incl. the distinct-lines bound), the SAT encoding (completions valid and varied, joint completion keeps neighbours feasible, counts agree with the propagation solver, determinism under concurrency), the packer (shape validity, the overlap acceptance rule, hole statistics), the fill order, generator determinism and every per-tile assertion (re-verified from the JSON) on the 2x2 and X=15 fixtures and on scatter fields, and the judge (classification and the <50 ms budget).... MOSAIC_SLOW=1also regenerates the 2x2 fixture from its seed and compares.site:deno task testcovers geometry and hue assignment, colour mixing, delta coding, referee gating (frontier, territory, revision), agents and meets, region-scoped observe under fog, growth, the analyzer, live-fold == replay-fold at every revision, the daily loop (rollovers, archives, leaderboards, honours, restart), and that every finished game log on disk replays to a solved state with the right reward.
What the generator guarantees (per tile, asserted; see gen.PolicyFor)
- 2..m solutions alone (m by cell count: 2 up to 64 cells, 3 up to 196, 4 above); every alternative differs from the truth inside an overlap region shared with a neighbour (so a solved neighbour can pin it).
- Blank floor 55% (<= 64 cells), 65% (<= 100), 70% above.
- From 144 cells: with all its shared cells fixed the tile is unique, needs at least one bifurcation (rules 1-3 stall), and needs the uniqueness constraint (it has a second solution without it).
- The whole field has exactly one coupled solution (CSP over the tiles' solution sets and their shared cells).
Shared cells are given only where blank overlaps would leave more than m
solutions; the manifest records rules.seam_clues: true when any were
used. Holes (cells no tile covers) are void. Coupling depth k is
unmeasured.