{"ok":true,"site":{"host":"opendots.thach.app","url":"https://opendots.thach.app/","type":"live","desc":"A picture-book canvas. Draw the scene, place words and shapes, then present the story.","tools":[{"name":"get_storybook","kind":"answer","impl":"imperative","description":"Read the book: pages with named layers, activeLayerId and overlay placements, reusable named color profiles (palettes + activePaletteId), assets including animation frame count/timing, the active page, and webmcp.ready. After a refresh, re-fetch live tools, wait until webmcp.ready is true, then call this to recover asset ids before mutating. Call get_pixel_art_guide first; use get_asset_image and get_page_image to inspect pixels.","annotations":{"readOnlyHint":true},"inputSchema":{"type":"object","properties":{}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute","page":"/","pages":["/"]},{"name":"get_pixel_art_guide","kind":"answer","impl":"imperative","description":"Load the pixel-art playbook and attached top-down storybook-RPG quality reference — call this first each session before drawing. Pass topic storybook-rpg, workflow, shading, composition, or tools; omit for the full playbook.","annotations":{"readOnlyHint":true},"inputSchema":{"type":"object","properties":{"topic":{"type":"string","enum":["workflow","shading","composition","storybook-rpg","tools","full"],"description":"Section to return; omit for full playbook"}}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute","page":"/","pages":["/"]},{"name":"set_palette","kind":"act","impl":"imperative","description":"Create, update, or select a reusable named color profile. Make profiles for material or asset families (for example Milo, sheep wool, bedroom wood, moonlight); profiles are not bound to assets, so select or reuse whichever profile fits the next asset. Pass any number of #rrggbb swatches — there is no count cap, and Default is never overwritten. Prefer cohesive base, reflected-light, shadow, and highlight ramps over maximizing unique colors. Extra #rrggbb colors can also be used inline in draw ops without adding them to a profile.","annotations":{"readOnlyHint":false},"inputSchema":{"type":"object","properties":{"colors":{"type":"array","items":{"type":"string","description":"#rrggbb"},"minItems":1,"description":"Any number of #rrggbb swatches for a named theme profile. Omit to select an existing profile by name."},"name":{"type":"string","description":"Theme label (e.g. Bedtime). Creates or updates that profile. Omit to update the current non-Default theme or create Theme N. Pass name without colors to select."}}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute","page":"/","pages":["/"]},{"name":"add_page","kind":"act","impl":"imperative","description":"Append a new landscape page and select it. Optional width sets pixel density (48–256; height follows 16:9; default 128); use 160–224 for rich scenes. Compose complex art with add_asset + stamp_assets rather than painting the whole page. Optional story rasterizes one line of words — do not add a story form or caption box on the page.","annotations":{"readOnlyHint":false},"inputSchema":{"type":"object","properties":{"width":{"type":"integer","description":"Optional page density in pixels across, 48–256. Height follows 16:9. Default 128."},"story":{"type":"string","description":"Optional line of words rasterized onto the page. Do not add a form or caption box."}}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute","page":"/","pages":["/"]},{"name":"select_page","kind":"act","impl":"imperative","description":"Select which page is on the canvas by 0-based index. Use after add_page or when editing a different page. Call get_storybook if you need current indexes.","annotations":{"readOnlyHint":false},"inputSchema":{"type":"object","properties":{"index":{"type":"integer","description":"0-based page index from get_storybook"}},"required":["index"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute","page":"/","pages":["/"]},{"name":"place_text","kind":"act","impl":"imperative","description":"Rasterize words into a topmost reusable Story layer at x,y (0–1 fractions or pixel coords), so animated assets cannot cover the text. Uses Inter glyphs at size 1–8 (default 2).","annotations":{"readOnlyHint":false},"inputSchema":{"type":"object","properties":{"body":{"type":"string","description":"The words on the page"},"x":{"type":"number","description":"Horizontal position, 0–1 or pixels"},"y":{"type":"number","description":"Vertical position, 0–1 or pixels"},"color":{"type":"string","description":"#rrggbb"},"size":{"type":"integer","minimum":1,"maximum":8,"description":"Glyph scale 1–8 (default 2)"}},"required":["body"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute","page":"/","pages":["/"]},{"name":"get_asset_image","kind":"answer","impl":"imperative","description":"Rasterize one asset animation frame to PNG so you can compare it to a reference. Pass frameIndex to inspect a specific frame and scale 1–8 to enlarge it. Returns frame metadata, stats, the image, and optional comma-separated rows. Call after each draw pass and each animation frame before stamping.","annotations":{"readOnlyHint":true},"inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Asset id from add_asset or get_storybook.assets"},"frameIndex":{"type":"integer","minimum":0,"description":"Animation frame to inspect (default 0)"},"scale":{"type":"integer","minimum":1,"maximum":8,"description":"Nearest-neighbor upscale for inspection (default 1 = native 1:1)"},"includeRows":{"type":"boolean","description":"Include comma-separated rows in text summary (default true)"}},"required":["id"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute","page":"/","pages":["/"]},{"name":"review_asset","kind":"act","impl":"imperative","description":"Record an actual vision judgment for the latest inspected asset revision. Use revise when silhouettes, material ramps, clustered pixels, or lighting need work; only approved revisions may be stamped.","annotations":{"readOnlyHint":false},"inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Asset id from get_asset_image"},"revision":{"type":"integer","minimum":0,"description":"Revision returned by get_asset_image"},"verdict":{"type":"string","enum":["revise","approved"]},"observations":{"type":"string","description":"Concrete visual observations about silhouette, shading ramps, pixel clusters, lighting, and needed fixes"}},"required":["id","revision","verdict","observations"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute","page":"/","pages":["/"]},{"name":"paint_asset","kind":"act","impl":"imperative","description":"Paint one declared asset pass: outline, fill, shadow, highlight, or cleanup. Mix rects, lines, fills, and pixels; mirror/repeat drafts or translate one painted region on a copied frame for animation. Color \"\" erases. Each call returns the new revision PNG; inspect it and submit review_asset before stamping.","annotations":{"readOnlyHint":false},"inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Asset id to paint into"},"pass":{"type":"string","enum":["outline","fill","shadow","highlight","cleanup"],"description":"The visual construction pass being applied"},"frameIndex":{"type":"integer","minimum":0,"description":"Frame to paint (default 0). Use the current frameCount to append one copied frame."},"frameDuration":{"type":"integer","minimum":100,"maximum":2000,"description":"Milliseconds per frame, shared by the asset (default 400)"},"rects":{"type":"array","maxItems":512,"description":"Filled rectangles: each {x,y,width,height,color} paints a solid block. One rect can cover any region; color \"\" erases the block.","items":{"type":"object","properties":{"x":{"type":"integer"},"y":{"type":"integer"},"width":{"type":"integer"},"height":{"type":"integer"},"color":{"type":"string","description":"#rrggbb or \"\" to erase"}},"required":["x","y","width","height","color"]}},"lines":{"type":"array","maxItems":512,"description":"Straight lines: each {x0,y0,x1,y1,color} draws an edge or outline. Use for silhouettes, seams, and furniture edges. color \"\" erases.","items":{"type":"object","properties":{"x0":{"type":"integer"},"y0":{"type":"integer"},"x1":{"type":"integer"},"y1":{"type":"integer"},"color":{"type":"string","description":"#rrggbb or \"\" to erase"}},"required":["x0","y0","x1","y1","color"]}},"fills":{"type":"array","maxItems":512,"description":"Flood fills: each {x,y,color} bucket-fills the contiguous region touching x,y. Outline first (lines/rects), then flood the enclosed area. color \"\" erases.","items":{"type":"object","properties":{"x":{"type":"integer"},"y":{"type":"integer"},"color":{"type":"string","description":"#rrggbb or \"\" to erase"}},"required":["x","y","color"]}},"pixels":{"type":"array","maxItems":4096,"description":"Fine-detail pixels (applied last). For blocks/bands prefer rects.","items":{"type":"object","properties":{"x":{"type":"integer","description":"Column within asset (0 = left)"},"y":{"type":"integer","description":"Row within asset (0 = top)"},"color":{"type":"string","description":"#rrggbb or \"\" to erase"}},"required":["x","y","color"]}},"mirror":{"type":"string","enum":["left-right","top-bottom","both"],"description":"Duplicate supplied operations across the asset axes while keeping the originals"},"repeat":{"type":"object","description":"Repeat supplied operations as a grid, including the original at row 0, column 0","properties":{"columns":{"type":"integer","minimum":1,"maximum":16},"rows":{"type":"integer","minimum":1,"maximum":16},"stepX":{"type":"integer","description":"Horizontal pixels between copies"},"stepY":{"type":"integer","description":"Vertical pixels between copies"}},"required":["columns","rows","stepX","stepY"]},"translateRegion":{"type":"object","description":"Move painted pixels in one bounded region on this frame; useful after appending a copied frame for a 1–2px blink, breath, limb, or hair motion.","properties":{"x":{"type":"integer"},"y":{"type":"integer"},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1},"dx":{"type":"integer","minimum":-16,"maximum":16},"dy":{"type":"integer","minimum":-16,"maximum":16}},"required":["x","y","width","height","dx","dy"]},"offsetX":{"type":"integer","description":"Added to every x (default 0) — use to tile regions"},"offsetY":{"type":"integer","description":"Added to every y (default 0)"}},"required":["id","pass"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute","page":"/","pages":["/"]},{"name":"add_asset","kind":"act","impl":"imperative","description":"Save a reusable sprite to the library (each side 1–96px; library ≤100). Pass imageDataUrl for a generated PNG/JPEG/WebP; transparent bounds are cropped, resized, and quantized to the active palette. For an exact bitmap, pass bitmapPalette plus indexedRows. Otherwise start with template \"empty\" then paint, or pass pixels, fill, or a page rect. Imported or painted assets return an inline PNG — compare before the next pass.","annotations":{"readOnlyHint":false},"inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"Label shown in the Assets list"},"imageDataUrl":{"type":"string","description":"Base64 data URL for a PNG, JPEG, or WebP image (maximum 10 MB); crops transparency, fits within 96×96, and quantizes to the active palette"},"width":{"type":"integer","description":"Width in pixels, 1–96"},"height":{"type":"integer","description":"Height in pixels, 1–96"},"pixels":{"type":"array","items":{"type":"string"},"description":"Row-major #rrggbb colors; empty string is transparent"},"rows":{"type":"array","items":{"type":"string"},"description":"Comma-separated #rrggbb per row (+ width + height)"},"bitmapPalette":{"type":"array","items":{"type":"string","description":"#rrggbb"},"description":"Colors referenced by zero-based indexes in indexedRows"},"indexedRows":{"type":"array","maxItems":96,"items":{"type":"string"},"description":"Direct bitmap rows such as \".,0,1,0,.\"; indexes reference bitmapPalette and . is transparent. Width and height are inferred."},"template":{"type":"string","enum":["empty"],"description":"With width+height, create a transparent blank asset"},"fill":{"type":"string","description":"With width+height, fill the asset with one #rrggbb color"},"x":{"type":"integer","description":"Page rect left, if copying from a page"},"y":{"type":"integer","description":"Page rect top, if copying from a page"},"pageIndex":{"type":"integer","description":"Page to copy from; defaults to the active page"}},"required":["name"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute","page":"/","pages":["/"]},{"name":"stamp_assets","kind":"act","impl":"imperative","description":"Add or update movable overlay placements on the selected layer of the active page (max 100 per call). Each item needs asset id, x, y; pass placementId from get_storybook/get_page_image to reposition or resize an existing stamp after visual review. Optional scale or width/height stays proportional. Array order for new stamps is back-to-front.","annotations":{"readOnlyHint":false},"inputSchema":{"type":"object","properties":{"stamps":{"type":"array","maxItems":100,"items":{"type":"object","properties":{"id":{"type":"string","description":"Asset id from add_asset or get_storybook"},"placementId":{"type":"string","description":"Existing placement id to move/resize instead of adding a duplicate"},"x":{"type":"integer","description":"Page column of the stamp top-left (0 = left)"},"y":{"type":"integer","description":"Page row of the stamp top-left (0 = top)"},"scale":{"type":"number","description":"Uniform scale; default 1 is native size"},"width":{"type":"integer","description":"Optional stamp width in page pixels"},"height":{"type":"integer","description":"Optional stamp height in page pixels"}},"required":["id","x","y"]},"description":"New stamps apply in array order (back-to-front); placementId updates an existing stamp in place"}},"required":["stamps"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute","page":"/","pages":["/"]},{"name":"paint_page","kind":"act","impl":"imperative","description":"Paint into the selected layer of the active page for flat backgrounds and touch-ups. Mix rects, lines, fills, and pixels (≤4096 detail pixels/call); ops apply rects → lines → fills → pixels. color \"\" erases; a full-page rect with \"\" clears the selected layer. For characters and props, build assets then stamp_assets. Optional offsetX/offsetY tiles a motif across the page.","annotations":{"readOnlyHint":false},"inputSchema":{"type":"object","properties":{"rects":{"type":"array","maxItems":512,"description":"Filled rectangles: each {x,y,width,height,color} paints a solid block. One rect can cover any region; color \"\" erases the block.","items":{"type":"object","properties":{"x":{"type":"integer"},"y":{"type":"integer"},"width":{"type":"integer"},"height":{"type":"integer"},"color":{"type":"string","description":"#rrggbb or \"\" to erase"}},"required":["x","y","width","height","color"]}},"lines":{"type":"array","maxItems":512,"description":"Straight lines: each {x0,y0,x1,y1,color} draws an edge or outline. Use for silhouettes, seams, and furniture edges. color \"\" erases.","items":{"type":"object","properties":{"x0":{"type":"integer"},"y0":{"type":"integer"},"x1":{"type":"integer"},"y1":{"type":"integer"},"color":{"type":"string","description":"#rrggbb or \"\" to erase"}},"required":["x0","y0","x1","y1","color"]}},"fills":{"type":"array","maxItems":512,"description":"Flood fills: each {x,y,color} bucket-fills the contiguous region touching x,y. Outline first (lines/rects), then flood the enclosed area. color \"\" erases.","items":{"type":"object","properties":{"x":{"type":"integer"},"y":{"type":"integer"},"color":{"type":"string","description":"#rrggbb or \"\" to erase"}},"required":["x","y","color"]}},"pixels":{"type":"array","maxItems":4096,"description":"Fine-detail page pixels (applied last). For blocks prefer rects.","items":{"type":"object","properties":{"x":{"type":"integer","description":"Page column (0 = left)"},"y":{"type":"integer","description":"Page row (0 = top)"},"color":{"type":"string","description":"#rrggbb or \"\" to erase"}},"required":["x","y","color"]}},"offsetX":{"type":"integer","description":"Added to every x (default 0) — use to tile motifs"},"offsetY":{"type":"integer","description":"Added to every y (default 0)"}}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute","page":"/","pages":["/"]},{"name":"get_page_image","kind":"answer","impl":"imperative","description":"Rasterize a page to PNG so you can compare it to a reference (composites overlay stamps over page.pixels). Omit x, y, width, height for the full page; pass all four to crop a region. Returns coverage, colorCount, placementCount, and sceneHint for composition problems. Call after every few stamps, then inspect important character/object crops rather than trusting counts alone.","annotations":{"readOnlyHint":true},"inputSchema":{"type":"object","properties":{"pageIndex":{"type":"integer","description":"0-based page index; defaults to active page"},"x":{"type":"integer","description":"Optional region left edge in page pixels (requires y, width, height)"},"y":{"type":"integer","description":"Optional region top edge in page pixels"},"width":{"type":"integer","description":"Optional region width in page pixels"},"height":{"type":"integer","description":"Optional region height in page pixels"},"scale":{"type":"integer","minimum":1,"maximum":8,"description":"Nearest-neighbor upscale (default 1)"}}},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute","page":"/","pages":["/"]},{"name":"review_page","kind":"act","impl":"imperative","description":"Record a vision judgment for the latest inspected page revision. Check top-down perspective, story-text stacking, depth overlap, lighting, density, and focal hierarchy before approving.","annotations":{"readOnlyHint":false},"inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Page id from get_page_image"},"revision":{"type":"integer","minimum":0,"description":"Revision returned by get_page_image"},"verdict":{"type":"string","enum":["revise","approved"]},"observations":{"type":"string","description":"Concrete visual observations about perspective, stacking, overlap, lighting, density, and needed fixes"}},"required":["id","revision","verdict","observations"]},"hasHandler":true,"hasExecute":true,"__source":"spec","executable":true,"handlerField":"execute","page":"/","pages":["/"]}],"category":"Media & Personal","favicon":"/favicons/opendots.thach.app.png","apiSurface":"spec","toolCount":14}}