Let the agent draw pixel art and vector art (#429)
Stage 5c of the MCP work. The agent could place sprites and edit graphs, but it could not draw: pixel art, palettes and vector art had no commands in its catalog. Two new areas bring 29 commands, so the catalog now holds 144. - pixelart (13): paint, layers (add, copy, remove, change, move, merge down), frames (add, also as a copy, remove, move), canvas size with an anchor, palette colors set and removed. - paint_pixels edits one layer on one frame. Its steps run in order: rows (a small picture as characters, a space keeps the pixel), pixels, line, rect, ellipse, fill, clear. An index the palette does not have, a pixel off the canvas and an unknown character are refused. - vectorart (16): shapes from rings or as rect and ellipse, outline, style (fill, opacity, gradient or image fill, stroke), move, scale, rotate and flip, copy, remove, combine, rename, order, move into groups, group, layers. - get_asset gives the palette of pixel art as colors. Up to 16384 pixels over all layers and frames each layer comes as rows, above that only the number of painted pixels. Palette and vector colors come as #rrggbb. - Merge down, canvas size and removing a color are now builders in pixelActions.ts. The panel and the agent share the same code. - Color and point helpers moved into catalog/shared.ts, and graphs.ts uses them too. Found while testing: - The ellipse tests only checked the four extreme points, which stay right even with wrong handle lengths. The test now checks that every point of the drawn curve lies on the ellipse. Tests: - agent-drawing (new, 9) and the other agent, pixel and relay tests: 133/133. - 7 mutations, all red in the matching test. - e2e 71 on melektaus-atx: 24/24. New check: the agent's pixel art opens in its tab, reads back as rows, and its cel is on the server after saving (status 200). - Live in a test project: an 8x8 sprite and a copied second frame read back as painted, a rotated square and a radial gradient disc with their corners where computed. - Gate on melektaus-atx: CHECK EXIT 0, 322 files, 4978 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d01e2b832e
commit
0684e76dc5
11 files changed
+1546
-36
No files matched your search
+47
-1
@@ -32,7 +32,7 @@ const TOOLS = [
|
||||
*/
|
||||
export async function mcp(t) {
|
||||
const { check, json, sleep, waitUntil, settle, press, locate, click, cdp, baseUrl, projectId } = t;
|
||||
const { clickText, menuTicked, exceptions, navigate, shot, holdKey, entity } = t;
|
||||
const { clickText, menuTicked, exceptions, navigate, shot, holdKey, entity, evaluate } = t;
|
||||
const site = process.env.FEUERWERK_MCP_BASE_URL ?? baseUrl;
|
||||
const endpoint = `${site}mcp`;
|
||||
|
||||
@@ -417,6 +417,52 @@ export async function mcp(t) {
|
||||
}),
|
||||
);
|
||||
|
||||
// 71g7. Drawing (#429 stage 5c): pixel art the agent paints comes up in its tab, reads back as rows,
|
||||
// and its cel reaches the server when the project is saved.
|
||||
const painted = await callTool('run_commands', {
|
||||
label: 'paint',
|
||||
steps: [
|
||||
{ command: 'create_asset', args: { type: 'pixelart', name: `${TOKEN_NAME} pixels` }, as: 'art' },
|
||||
{ command: 'resize_pixel_canvas', args: { asset: '$art', width: 4, height: 3 } },
|
||||
{
|
||||
command: 'paint_pixels',
|
||||
args: { asset: '$art', draws: [{ draw: 'rows', rows: ['8..8', '.88.', '8..8'] }] },
|
||||
},
|
||||
],
|
||||
});
|
||||
await settle();
|
||||
const artId = painted?.structuredContent?.made?.find((entry) => entry.step === 1)?.id;
|
||||
const artView = await viewNow();
|
||||
const artRead = artId ? (await callTool('get_asset', { asset: artId }))?.structuredContent : null;
|
||||
const artRows = artRead ? Object.values(artRead.frames?.[0]?.layers ?? {})[0] : null;
|
||||
await waitUntil('__gm.state.saveStatus === "saved"', { timeout: 10000, label: 'saved' }).catch(() => {});
|
||||
const celFiles = artId
|
||||
? await json(
|
||||
`Object.values(__gm.state.project.assets.find((a) => a.id === ${JSON.stringify(artId)}).frames[0].cels)`,
|
||||
)
|
||||
: [];
|
||||
const celStatus = celFiles[0]
|
||||
? Number(await evaluate(`fetch('/feuerwerk/data/pixels/${celFiles[0]}').then((r) => String(r.status))`))
|
||||
: null;
|
||||
await callTool('undo');
|
||||
await callTool('open', { asset: walkId });
|
||||
await settle();
|
||||
check(
|
||||
'pixel art the agent paints comes up in its tab, reads back as rows, and its cel is saved to the server',
|
||||
painted?.isError === false &&
|
||||
artView?.document?.asset?.id === artId &&
|
||||
JSON.stringify(artRows) === JSON.stringify(['8..8', '.88.', '8..8']) &&
|
||||
celFiles.length === 1 &&
|
||||
celStatus === 200,
|
||||
JSON.stringify({
|
||||
text: painted?.content?.[0]?.text?.slice(0, 300),
|
||||
document: artView?.document,
|
||||
artRows,
|
||||
celFiles,
|
||||
celStatus,
|
||||
}),
|
||||
);
|
||||
|
||||
// 71h. Played by the agent, the object walks left while the user holds the arrow.
|
||||
const started = await callTool('play', { action: 'play' });
|
||||
await waitUntil('__gm.state.mode === "play"', { timeout: 10000, label: 'the game runs' });
|
||||
|
||||
Reference in new issue
Block a user