clone

a takuzu puzzle field for agents to explore tile by tile, with a referee and a replayable spectator view

mcp: define m and the tile fields where they appear; glossary; rules as a resource

dami committed b7797bd
commit
b7797bd989755d7a3dc7c8aaf343f345dc255615
parent
eee9276d35a108de7bc9362e53691d9aaf763a81
author
dami <gazey55@gmail.com>
1 file changed, +16 -2
Msite/lib/mcp.ts18++++++++++++++++--
M site/lib/mcp.ts +16 -2
@@ -67,7 +67,7 @@ function findImplementation(o: unknown): string | undefined {
6767 }6868 6969 const RULES_TEXT =70- `Rules of a mosaic. The board is a G x G field of binary cells partly covered by tiles. A tile is a shape: the union of rectangles {rects: [lx, ly, w, h]} inside its bounding box at (x0, y0); tiles come in many sizes and shapes (rectangles, L, T, plus). Neighbouring tiles overlap: a shared cell belongs to every tile that covers it and has one value. Cells no tile covers are holes: never playable. Inside every tile, independently, the rules apply per LINE, where a line is a maximal horizontal or vertical run of the tile's cells: each line has as many 0s as 1s; no three equal values are adjacent along a line; no two lines of the same orientation and the same length within one tile are equal. Nothing is enforced across a tile boundary except that shared cells agree. Every tile ALONE (its givens only) has 2..m solutions, m depending on its cell count (2 up to 64 cells, 3 up to 196, 4 above), and its alternatives differ on cells it shares with SOME neighbour. PINNING IS NOT GUARANTEED BY ANY ONE NEIGHBOUR: a solved neighbour fixes the cells it shares, which pins the tile only if the alternatives differ there; a tile can keep 2 or more solutions with one solved neighbour and need another, or a guess. AGENTS: several agents play one field. Call join to get your agent id and your seed tile, a hidden tile at a random spot away from everyone; every reveal, commit and retract carries your agent id. Your frontier is your seed tile while unsolved, plus every unsolved tile sharing a cell with a tile your group solved. When a tile you solve touches a tile solved by another group, you meet: the two territories merge and from then on share one frontier. Tiles are referred to by id; observe {agent: your id} lists your territory, everything within 24 cells of it and your frontier: by design that includes HIDDEN tiles' shapes, cell counts and m before they are revealed (the map is public; only givens and values are earned), each tile with id, x0, y0, w, h, rects, cells, m, status, agent (who solved it) and neighbours; observe {rect} lists a rectangle; full listings exist only on small fields. Givens are hidden until you reveal a tile, and reveal is only accepted on your frontier. Your seed tile has no solved neighbour, so at least one guess is unavoidable there; everywhere else, prefer to expand from a solved tile and use the shared cells. A tile is solved when all of its cells are known (givens, cells fixed by solved neighbours, and correct commits); solving a tile earns its cell count as reward and attributes the tile to you. COMMITS are checked per cell against the true solution: a wrong value is rejected and reported as {id, reason: "wrong", v}; correct cells are accepted and classified forced or guess. HEALTH: your health is your wrong-rate over your last 2,000 committed cells (accepted + wrong): under 1.5% healthy; 1.5% to 4% warning (commit and observe results say so); 4% and above, once at least 300 cells are in the window, death for the day: your writes are refused from then on, your territory stays, your group mates continue, tomorrow is fresh. The window is what heals: an honest rate flushes old mistakes. Guessing bleeds you; probing cells to read the verdict bleeds you out. Commit what you can prove; guess only when a tile is genuinely ambiguous, and then one cell. FORCED means the referee's own propagation reaches the value from the cells known in revealed tiles (givens, shared cells, accepted commits): repeatedly, for every empty cell of every revealed tile and each candidate value, the value is excluded on any line through the cell when (run) it would make three equal in a row along that line, in any of the three positions: the two cells before it, the two after it, or one on each side; (balance) the line already holds len/2 cells of that value; (unique) the line is complete except this cell, or is complete except this cell and one more blank, and with this value it would equal another complete line of the same orientation and length in the same tile (with two blanks the balance rule fills the last one identically, so both forms count). A cell with exactly one admissible value is fixed, every fixed cell is re-evaluated, until nothing changes; bifurcation (assume a value, propagate, contradiction excludes it) only to the game's configured depth, default 0. That is single-cell propagation of the three rules; distinct-line counting, pair/gap arguments beyond the three positions above, and cross-line reasoning are NOT applied, so a cell that holds in every solution but needs them is recorded as guess. Commit propagation-forced cells first if you care about that split. Some shared cells may be givens; cells the referee already knows, and holes, are ignored, not rejected. Every write carries expected_revision; on conflict (other agents are writing too), observe again and retry. TRANSPORT: this endpoint serves the 2025-era streamable HTTP protocol statelessly: there is no Mcp-Session-Id (do not expect one), initialize answers as one SSE frame, notifications/initialized returns 202 at once, and long tool calls (start_mosaic no longer blocks) send SSE keepalive comments. GROWING FIELDS AND FOG: a field started with a grow policy is allocated on a large canvas (G up to 1000) with the initial chunks in the middle, and it GROWS: on a timer, when most current tiles are revealed or solved, and whenever every current tile is solved, the referee generates a ring of new chunks around the cluster (a grow event; tile ids continue, existing cells never change). Observe again after each batch and expect new tile ids; the game ends only when a growth finds no room. Under FOG (on by default for growing fields) observe lists a hidden tile only once your group reaches it: a tile you revealed or solved overlaps it, or it is your seed tile; everything else is unlisted until then, so expanding toward open space is how you discover the map. Writing code to do the deduction is allowed; where to expand (toward open space, or toward another agent to merge frontiers) and how much to commit is your strategy.`;70+ `Rules of a mosaic. GLOSSARY: G = cells per side of the field; a tile's m = the number of solutions it has from its givens alone (2, 3 or 4 by size; never 1 — tiles are pinned by neighbours, not by themselves); cells = a tile's cell count and its reward; a line = a maximal horizontal or vertical run of one tile's cells; the frontier = the tiles you may reveal. The board is a G x G field of binary cells partly covered by tiles. A tile is a shape: the union of rectangles {rects: [lx, ly, w, h]} inside its bounding box at (x0, y0); tiles come in many sizes and shapes (rectangles, L, T, plus). Neighbouring tiles overlap: a shared cell belongs to every tile that covers it and has one value. Cells no tile covers are holes: never playable. Inside every tile, independently, the rules apply per LINE, where a line is a maximal horizontal or vertical run of the tile's cells: each line has as many 0s as 1s; no three equal values are adjacent along a line; no two lines of the same orientation and the same length within one tile are equal. Nothing is enforced across a tile boundary except that shared cells agree. Every tile ALONE (its givens only) has 2..m solutions, m depending on its cell count (2 up to 64 cells, 3 up to 196, 4 above), and its alternatives differ on cells it shares with SOME neighbour. PINNING IS NOT GUARANTEED BY ANY ONE NEIGHBOUR: a solved neighbour fixes the cells it shares, which pins the tile only if the alternatives differ there; a tile can keep 2 or more solutions with one solved neighbour and need another, or a guess. AGENTS: several agents play one field. Call join to get your agent id and your seed tile, a hidden tile at a random spot away from everyone; every reveal, commit and retract carries your agent id. Your frontier is your seed tile while unsolved, plus every unsolved tile sharing a cell with a tile your group solved. When a tile you solve touches a tile solved by another group, you meet: the two territories merge and from then on share one frontier. Tiles are referred to by id; observe {agent: your id} lists your territory, everything within 24 cells of it and your frontier: by design that includes HIDDEN tiles' shapes, cell counts and m before they are revealed (the map is public; only givens and values are earned), each tile with id, x0, y0, w, h, rects, cells, m, status, agent (who solved it) and neighbours; observe {rect} lists a rectangle; full listings exist only on small fields. Givens are hidden until you reveal a tile, and reveal is only accepted on your frontier. Your seed tile has no solved neighbour, so at least one guess is unavoidable there; everywhere else, prefer to expand from a solved tile and use the shared cells. A tile is solved when all of its cells are known (givens, cells fixed by solved neighbours, and correct commits); solving a tile earns its cell count as reward and attributes the tile to you. COMMITS are checked per cell against the true solution: a wrong value is rejected and reported as {id, reason: "wrong", v}; correct cells are accepted and classified forced or guess. HEALTH: your health is your wrong-rate over your last 2,000 committed cells (accepted + wrong): under 1.5% healthy; 1.5% to 4% warning (commit and observe results say so); 4% and above, once at least 300 cells are in the window, death for the day: your writes are refused from then on, your territory stays, your group mates continue, tomorrow is fresh. The window is what heals: an honest rate flushes old mistakes. Guessing bleeds you; probing cells to read the verdict bleeds you out. Commit what you can prove; guess only when a tile is genuinely ambiguous, and then one cell. FORCED means the referee's own propagation reaches the value from the cells known in revealed tiles (givens, shared cells, accepted commits): repeatedly, for every empty cell of every revealed tile and each candidate value, the value is excluded on any line through the cell when (run) it would make three equal in a row along that line, in any of the three positions: the two cells before it, the two after it, or one on each side; (balance) the line already holds len/2 cells of that value; (unique) the line is complete except this cell, or is complete except this cell and one more blank, and with this value it would equal another complete line of the same orientation and length in the same tile (with two blanks the balance rule fills the last one identically, so both forms count). A cell with exactly one admissible value is fixed, every fixed cell is re-evaluated, until nothing changes; bifurcation (assume a value, propagate, contradiction excludes it) only to the game's configured depth, default 0. That is single-cell propagation of the three rules; distinct-line counting, pair/gap arguments beyond the three positions above, and cross-line reasoning are NOT applied, so a cell that holds in every solution but needs them is recorded as guess. Commit propagation-forced cells first if you care about that split. Some shared cells may be givens; cells the referee already knows, and holes, are ignored, not rejected. Every write carries expected_revision; on conflict (other agents are writing too), observe again and retry. TRANSPORT: this endpoint serves the 2025-era streamable HTTP protocol statelessly: there is no Mcp-Session-Id (do not expect one), initialize answers as one SSE frame, notifications/initialized returns 202 at once, and long tool calls (start_mosaic no longer blocks) send SSE keepalive comments. GROWING FIELDS AND FOG: a field started with a grow policy is allocated on a large canvas (G up to 1000) with the initial chunks in the middle, and it GROWS: on a timer, when most current tiles are revealed or solved, and whenever every current tile is solved, the referee generates a ring of new chunks around the cluster (a grow event; tile ids continue, existing cells never change). Observe again after each batch and expect new tile ids; the game ends only when a growth finds no room. Under FOG (on by default for growing fields) observe lists a hidden tile only once your group reaches it: a tile you revealed or solved overlaps it, or it is your seed tile; everything else is unlisted until then, so expanding toward open space is how you discover the map. Writing code to do the deduction is allowed; where to expand (toward open space, or toward another agent to merge frontiers) and how much to commit is your strategy.`;7171 7272 // a uuid for games agents start, `day-<date>` for the daily7373 const gameIdSchema = z.string().regex(/^[a-zA-Z0-9_-]{1,80}$/);
@@ -111,6 +111,20 @@ export function hostedDaily(): boolean {
111111 112112 export function createServer(): McpServer {113113 const server = new McpServer({ name: "mosaic", version: "0.3.0" });114+ // the rules as a resource too: agents look there first115+ server.registerResource(116+ "rules",117+ "mosaic://rules",118+ {119+ title: "Rules of the mosaic",120+ description:121+ "The rules, glossary, health and transport notes agents need; the same text the tools carry.",122+ mimeType: "text/plain",123+ },124+ (uri) => ({125+ contents: [{ uri: uri.href, mimeType: "text/plain", text: RULES_TEXT }],126+ }),127+ );114128 115129 if (!hostedDaily()) registerStart(server);116130 registerPlay(server);
@@ -288,7 +302,7 @@ function registerPlay(server: McpServer): void {
288302 {289303 title: "Observe the mosaic",290304 description:291- "Read the current revision and a scope of tiles (id, x0, y0, w, h, rects, cells, m, status, agent, neighbours) with the frontier and every known cell in them (delta-coded ids plus a value string; decode by cumulative sum). On a growing field tiles_total and grown rise over time; under fog hidden tiles appear only once your group reaches them. Scope: {agent: your id} lists the tiles your group solved, everything within 24 cells of them, and your frontier (the normal call); {rect: [x, y, w, h]} lists the tiles intersecting a rectangle; no scope lists everything, allowed only on fields up to 120 cells a side. Known cells are givens of revealed tiles, cells fixed by solved neighbours, and accepted commits. With {agent} the observation also carries your health. The payload is under structuredContent.observation; cell id = y*G + x.",305+ "Read the current revision and a scope of tiles with the frontier. Tile fields: id; x0, y0 (bounding box origin); w, h; rects (the shape as [lx, ly, w, h] rectangles, local to the box); cells (cell count, also the tile's reward); m (how many solutions the tile has from its givens ALONE, 2-4 by size: every tile is deliberately ambiguous by itself, and its alternatives differ on cells it shares with some neighbour, so a solved neighbour can pin it); status (hidden | revealed | solved); agent (who solved it); neighbours (tile ids sharing a cell). The observation also carries the frontier and every known cell in them (delta-coded ids plus a value string; decode by cumulative sum). On a growing field tiles_total and grown rise over time; under fog hidden tiles appear only once your group reaches them. Scope: {agent: your id} lists the tiles your group solved, everything within 24 cells of them, and your frontier (the normal call); {rect: [x, y, w, h]} lists the tiles intersecting a rectangle; no scope lists everything, allowed only on fields up to 120 cells a side. Known cells are givens of revealed tiles, cells fixed by solved neighbours, and accepted commits. With {agent} the observation also carries your health. The payload is under structuredContent.observation; cell id = y*G + x.",292306 inputSchema: z.object({293307 game_id: gameIdSchema,294308 agent: z.string().min(1).max(80).optional(),