capybara-mcp-shared 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,367 +14,262 @@ export const assetTypeSchema = z.enum([
14
14
  "map_vfx",
15
15
  "music_bg",
16
16
  ]);
17
- const referenceIdSchema = z
18
- .union([z.string(), z.array(z.string()).min(1)])
19
- .describe("Dependency handle(s) pointing at completed asset(s) the pipeline must wait for. Use a single string for one dependency; use a string array when multiple assets must exist first.");
20
17
  const outputFramesSchema = z
21
- .preprocess((value) => {
22
- if (value == null)
23
- return undefined;
24
- if (typeof value === "number")
25
- return String(value);
26
- return value;
27
- }, z.enum(["8", "12", "16", "24"]).optional())
28
- .transform((value) => value == null ? undefined : Number(value));
29
- export const mapConnectionDirectionSchema = z
30
- .enum(["right", "left", "down"])
31
- .describe("Direction FROM the reference map TO the new map. 'right' = new map is east of reference; 'left' = west; 'down' = south. Never 'up' — North is always the wall side.");
32
- export const mapConnectionSchema = z.object({
18
+ .preprocess((value) => value == null
19
+ ? undefined
20
+ : typeof value === "number"
21
+ ? String(value)
22
+ : value, z.enum(["8", "12", "16", "24"]).optional())
23
+ .transform((value) => value == null ? undefined : Number(value))
24
+ .describe("Target frame count for looping sheets. Use 8 frames for subtle idle sways, 12 frames for walks and standard tasks, and 16 frames for complex, multi-stage actions.");
25
+ const mapConnectionSchema = z.object({
33
26
  referenceId: z
34
27
  .string()
35
- .describe("Id of the base_map or extend_map this new room connects to."),
36
- direction: mapConnectionDirectionSchema,
28
+ .describe("ID of the existing map this connection connects to."),
29
+ direction: z
30
+ .enum(["right", "left", "down"])
31
+ .describe("Direction of travel from the reference map to this new map. East/West/South edges are walkable. North (up) is reserved for structural walls."),
37
32
  });
38
33
  const mapOverlayStateSchema = z.object({
39
34
  label: z
40
35
  .string()
41
- .describe("Short stable state id for this overlay, e.g. 'closed', 'open', 'looted'."),
36
+ .describe("Stable state ID for this overlay state (e.g., 'closed', 'open', 'looted', 'repaired')."),
42
37
  prompt: z
43
38
  .string()
44
- .describe("Visual prompt for this specific overlay state on the anchored map structure."),
39
+ .describe("Visual-only prompt of the structure in this state. Exclude characters or floating UI labels."),
45
40
  });
46
- const baseAssetFields = {
41
+ const coreFields = {
47
42
  id: z
48
43
  .string()
49
- .describe("Unique asset id used as storage key and dependency handle. Ensure consistency across iterations so the coding agent can track references."),
50
- prompt: z.string().describe("Primary visual or audio prompt for this asset."),
44
+ .describe("Unique asset ID used as the storage key and dependency handle."),
45
+ prompt: z
46
+ .string()
47
+ .describe("Primary visual or audio prompt. Exclude camera terms like 'top-down', '2.5D', or 'isometric' for world assets. Focus on layout, materials, and flat ambient lighting."),
51
48
  gamePlay: z
52
49
  .string()
53
50
  .min(1)
54
- .describe("Required concise gameplay logic and wiring brief for the codeAgent. Include placement, interaction, runtime UI state, or playback timing as appropriate for this asset."),
51
+ .describe("Detailed wiring instructions for the coding agent. Describe spawn areas, collision behaviors, boundary limits, and interaction triggers."),
55
52
  };
56
- const characterBodyFormatSchema = z
57
- .enum(["humanoid", "quadruped_animal"])
58
- .describe("Body format for generation and walk animation. humanoid (default): upright biped with human-like proportions — humans, fantasy races, and anthropomorphic animals (fox shopkeeper, cat villager walking on two legs). quadruped_animal: natural four-legged anatomy — farm livestock, wild wolves, pets on all fours. Default to humanoid unless the game explicitly needs quadruped farm/wildlife; even animal-themed games usually use humanoid NPCs.");
59
- const legacyAssetSchema = z.object({
60
- ...baseAssetFields,
61
- type: assetTypeSchema.describe("Asset mode layer type."),
62
- propsMultipleBreakdown: z
63
- .string()
64
- .optional()
65
- .describe("ONLY FOR props_multiple: Optional secondary prompt mapping numbered bounding boxes to individual prop descriptions. This is NOT a substitute for the required prompt field."),
66
- multipleHudsBreakdown: z
67
- .string()
68
- .optional()
69
- .describe("ONLY FOR multiple_huds: Optional secondary prompt mapping numbered HUD widgets to individual descriptions, ids/names, and intended runtime behavior. This is NOT a substitute for the required prompt field."),
70
- anchorDescription: z
71
- .string()
72
- .optional()
73
- .describe("ONLY FOR props_map_overlay: Description of the exact existing map-baked structure/prop being overlaid, including its location on the map."),
74
- overlayStateBreakdown: z
75
- .array(mapOverlayStateSchema)
76
- .min(1)
77
- .optional()
78
- .describe("ONLY FOR props_map_overlay: Required array of overlay states ({ label, prompt }[]). Each entry is one visual state on the same map anchor."),
79
- defaultAnimation: z
53
+ const assetArray = (schema, description) => z.array(schema).default([]).describe(description);
54
+ const baseMapSchema = z.object({
55
+ ...coreFields,
56
+ id: coreFields.id.describe("Unique map ID starting with 'map_' (e.g., 'map_town_hall'). Depicts the unaltered, 'before' state of the environment."),
57
+ art_reference_url: z
80
58
  .string()
81
59
  .optional()
82
- .describe("Built-in ENGINE idle preset name (e.g., 'Breathing Idle'). Set on base_character for player and simple-NPC tiers. Set on base_character_variant for job-NPC tiers (idle while holding the signature prop). Omit on job-NPC empty-handed parent base_characters and pure static references."),
83
- characterBodyFormat: characterBodyFormatSchema.optional(),
84
- referenceId: referenceIdSchema.optional(),
85
- outputFrames: outputFramesSchema
86
- .optional()
87
- .describe("ONLY FOR character_animation: Spritesheet frame count for the Pixel Engine animator. REQUIRED on every character_animation."),
88
- connections: z
89
- .array(mapConnectionSchema)
90
- .min(1)
91
- .max(3)
92
- .optional()
93
- .describe("ONLY FOR extend_map: One entry per existing map this room connects to. Index 0 is the anchor used for outpainting. Additional entries declare other open transition sides."),
94
- });
95
- const baseMapSchema = z.object({
96
- ...baseAssetFields,
97
- id: baseAssetFields.id.describe("Unique id for a base map. Start with 'map_' followed by a descriptive name (e.g., 'map_wooden_desk_room')."),
98
- prompt: baseAssetFields.prompt.describe("For base_map: Describe the environment theme, North boundary, 5-8 small/compact/modest major floor props, and 5-8 small/compact/modest decor floor props. Keep furniture rectilinear. Keep dynamic gameplay items, pickups, NPCs, changing clues, and stateful objects out of the prompt; put them in gamePlay and separate prop/character/overlay assets. Omit camera perspective keywords (like top-down or 2.5D) as the pipeline enforces them automatically."),
99
- gamePlay: baseAssetFields.gamePlay.describe("For base_map: Tell the codeAgent the rough player spawn area and rough placement for any characters/NPCs/enemies, plus WHICH props/characters belong on WHICH obstacles; ensure furniture mentioned here is in the map prompt."),
60
+ .describe("Optional art style reference for this map: an http(s) image URL or a local file path. The tool fetches/reads it and sends base64 to the API."),
100
61
  });
101
62
  const extendMapSchema = z.object({
102
- ...baseAssetFields,
103
- id: baseAssetFields.id.describe("Unique id for an extended map. Start with 'map_extend_'."),
104
- prompt: baseAssetFields.prompt.describe("For extend_map: Describe the new outpainted wing with 5-8 small/compact/modest floor props per map tile/wing, keeping every shared seam completely open with no props, furniture, blockers, or decor. MUST match the reference map's shell type (interior stays interior, exterior stays exterior), copy the reference floor material phrase verbatim, and match north-wall material for interiors. Keep dynamic gameplay items out of the prompt and put them in gamePlay/prop assets. New room purpose is fine; changing environment type (indoor↔outdoor, biome, or terrain) is invalid — use base_map instead."),
105
- gamePlay: baseAssetFields.gamePlay.describe("For extend_map: State shell continuity with the reference map (e.g. 'same interior metallic lab wing'), transition direction, rough spawn/entry area, important placements, and gameplay interactions — not just 'seamless walk'."),
63
+ ...coreFields,
64
+ id: coreFields.id.describe("Unique ID starting with 'map_extend_' (e.g., 'map_extend_kitchen'). Represents an adjacent wing of the same biome and flooring material."),
106
65
  connections: z
107
66
  .array(mapConnectionSchema)
108
67
  .min(1)
109
68
  .max(3)
110
- .describe("One entry per existing map this room connects to. Index 0 is the visual anchor: its map image and direction drive outpainting; the extension must continue that map's floor material at the shared edge. Additional entries declare other open transition sides for prompts, validation, and codeAgent wiring."),
69
+ .describe("At least one adjacent map boundary. Transition seams must be left completely open."),
111
70
  });
112
71
  const editMapSchema = z.object({
113
- ...baseAssetFields,
114
- id: baseAssetFields.id.describe("Unique id for a map edit. Start with 'map_edit_'."),
115
- prompt: baseAssetFields.prompt.describe("For edit_map: Describe exactly one static floor-prop/obstacle/collider edit on an existing base_map or extend_map — adding, removing, or replacing one static floor prop per edit request. Be explicit about what to add and what to remove. Do NOT use this for dynamic gameplay items, ambient animations, or sprite-sheet VFX."),
116
- gamePlay: baseAssetFields.gamePlay.describe("For edit_map: Explain what changes and why."),
117
- referenceId: referenceIdSchema.describe("One id of a completed base_map or extend_map."),
72
+ ...coreFields,
73
+ id: coreFields.id.describe("Unique ID starting with 'map_edit_'. Use ONLY for static, post-build changes to map obstacles (e.g., removing a fallen tree blocking a path)."),
74
+ referenceId: z.string().describe("ID of the existing map being modified."),
118
75
  });
119
76
  const mapVfxSchema = z.object({
120
- ...baseAssetFields,
121
- id: baseAssetFields.id.describe("Unique id for map VFX. Start with 'map_vfx_'."),
122
- prompt: baseAssetFields.prompt.describe("For map_vfx: Describe animated VFX on an existing base_map or extend_map — which elements should get sprite-sheet animation, motion/effect, and any VFX to remove or stop. Do NOT use this for adding/removing static obstacles."),
123
- gamePlay: baseAssetFields.gamePlay.describe("For map_vfx: Explain what changes and why."),
124
- referenceId: referenceIdSchema.describe("One id of a completed base_map or extend_map."),
77
+ ...coreFields,
78
+ id: coreFields.id.describe("Unique ID starting with 'map_vfx_'. Use for looping environmental animations (e.g., flickering fire, swaying lanterns, river ripples)."),
79
+ referenceId: z
80
+ .string()
81
+ .describe("ID of the existing map hosting this visual effect overlay."),
125
82
  });
126
83
  const baseCharacterSchema = z.object({
127
- ...baseAssetFields,
128
- id: baseAssetFields.id.describe("Unique id for a base character. Start with 'char_'."),
129
- characterBodyFormat: characterBodyFormatSchema.default("humanoid"),
130
- prompt: baseAssetFields.prompt.describe("For base_character: Describe clothing and physical traits. State body format explicitly (upright humanoid biped OR natural quadruped on four legs) — must match characterBodyFormat. Draw the character facing the camera, angled slightly toward screen-right (¾ turn); the engine mirrors this single sprite (flipX) for the opposite direction. Full body centered with generous empty padding on all sides on solid neon green (#00FF00). Art style is inferred globally. Omit camera-perspective keywords. Player and simple NPCs: empty-handed. Job NPCs: also empty-handed here — the prop-in-pose lives on the base_character_variant."),
131
- gamePlay: baseAssetFields.gamePlay.describe("For base_character: Role, placement, player interaction, animation tier (player / simple NPC / job NPC parent base), and characterBodyFormat (humanoid vs quadruped_animal) when not obvious from the role."),
84
+ ...coreFields,
85
+ id: coreFields.id.describe("Unique ID starting with 'char_' (e.g., 'char_merchant'). Represents the empty-handed, baseline sprite facing forward and turned 3/4 right."),
86
+ characterBodyFormat: z
87
+ .enum(["humanoid", "quadruped_animal"])
88
+ .default("humanoid")
89
+ .describe("Body format. Use humanoid for player and bipedal NPCs. Use quadruped_animal for livestock or wildlife."),
132
90
  defaultAnimation: z
133
91
  .string()
134
92
  .optional()
135
- .describe("Built-in ENGINE idle preset (e.g., 'Breathing Idle') for player and simple-NPC tiers only. Omit for job-NPC parent base_characters and pure static references."),
93
+ .describe("Engine idle preset name (e.g., 'Breathing Idle'). Omit on empty-handed parents of Job NPCs."),
136
94
  });
137
95
  const baseCharacterVariantSchema = z.object({
138
- ...baseAssetFields,
139
- id: baseAssetFields.id.describe("Unique id for a base character variant. Start with the base_character id, followed by the variant name."),
140
- prompt: baseAssetFields.prompt.describe("For base_character_variant: Describe a static KEYFRAME pose — the job NPC's default visual with the signature prop already held/worn. Must match the FIRST frame of follow-up character_animation prompts. Same camera-facing, right-angled orientation as the parent base_character. Full body centered with generous empty padding on solid neon green (#00FF00). Props are blended via referenceId; do not invent new objects not in the references."),
141
- gamePlay: baseAssetFields.gamePlay.describe("For base_character_variant: Default visual and idle anchor for a job NPC; set defaultAnimation here for looping idle while the signature prop is visible. Anchor for all movement/task character_animations on this character. State when idle vs walk vs task clips play."),
96
+ ...coreFields,
97
+ id: coreFields.id.describe("Unique ID starting with 'char_' (e.g., 'char_blacksmith_working'). Used for Job NPCs. Depicts the character holding their signature prop in their default pose."),
98
+ referenceId: z
99
+ .union([z.string(), z.array(z.string()).min(1)])
100
+ .describe("Parent character ID, or an array of the parent character ID and held tool prop ID."),
142
101
  defaultAnimation: z
143
102
  .string()
144
103
  .optional()
145
- .describe("REQUIRED for job-NPC variants: built-in ENGINE idle preset (e.g., 'Breathing Idle'). Auto-generates a looping idle clip from this prop-holding variant sprite."),
146
- referenceId: referenceIdSchema.describe("Parent base_character id, OR [base_character_id, prop_id] when blending a props_single or props_multiple asset into the pose. Job NPCs: one variant per character with the single signature prop — all task/walk clips reference this variant, not the empty-handed base_character."),
104
+ .describe("Engine idle preset name (e.g., 'Breathing Idle') to auto-generate a looping idle sheet while holding a prop."),
147
105
  });
148
106
  const characterAnimationSchema = z.object({
149
- ...baseAssetFields,
150
- id: baseAssetFields.id.describe("Unique id for a character animation. Start with the base_character id, followed by the animation name."),
151
- prompt: baseAssetFields.prompt.describe("For character_animation: Write a temporally dense motion script for the image-to-video animator — NOT a short label. Animate ONLY the default right-angled camera-facing sprite; the engine mirrors it (flipX) for the opposite direction. Structure: starting pose (must match the reference sprite) → transitions using 'begins by…', 'then…', 'as they…' → loop or recovery back to start. Describe ONLY body motion; props/tools must already be visible on the referenceId sprite. Player/simple NPC walk clips reference base_character. Job NPC clips (idle-with-prop, walk-with-prop, task loop) reference base_character_variant — never the empty-handed base_character."),
152
- gamePlay: baseAssetFields.gamePlay.describe("For character_animation: When this clip should play. Job NPCs: cap at 2-3 cohesive clips per character, all serving the same role/prop."),
153
- referenceId: referenceIdSchema.describe("One id base_character (player/simple NPC walk) OR base_character_variant (all job-NPC clips). Job NPC animations must reference the prop-in-pose variant, not the empty-handed base_character."),
154
- outputFrames: outputFramesSchema.describe("Spritesheet frame count for the Pixel Engine animator. REQUIRED on every character_animation — pick by action complexity: 8 subtle idle sways, 12 standard walk/run/task loops, 16 elaborate, 24 rare long sequences."),
107
+ ...coreFields,
108
+ id: coreFields.id.describe("Unique ID starting with 'char_' (e.g., 'char_player_walk'). Represents a looping sprite sheet."),
109
+ referenceId: z
110
+ .union([z.string(), z.array(z.string()).min(1)])
111
+ .describe("ID of the base character this animation sheet is for."),
112
+ outputFrames: outputFramesSchema,
155
113
  });
156
114
  const propsSingleSchema = z.object({
157
- ...baseAssetFields,
158
- id: baseAssetFields.id.describe("Unique id for a single prop. Start with 'prop_'."),
159
- prompt: baseAssetFields.prompt.describe("For props_single: Describe one isolated, small pick-up item as a purely physical shape. Keep surfaces clear of text or complex symbols so it remains legible as a tiny map icon."),
160
- gamePlay: baseAssetFields.gamePlay.describe("For props_single: Pickup, placement, inventory, cursor use, or crafting role."),
161
- referenceId: referenceIdSchema
162
- .describe("Optional placement/style reference. Use a base_map or extend_map id for world props; use a hud_element id for UI-layer props. Omit to default to the batch map.")
163
- .optional(),
115
+ ...coreFields,
116
+ id: coreFields.id.describe("Unique ID starting with 'prop_' (e.g., 'prop_iron_key'). A single portable item, pickup, or weapon. Describe as a physical shape without text."),
117
+ referenceId: z
118
+ .union([z.string(), z.array(z.string()).min(1)])
119
+ .describe("Map ID when world-anchored, or parent HUD ID when UI-anchored."),
120
+ });
121
+ const propsMultipleSchema = z.object({
122
+ ...coreFields,
123
+ id: coreFields.id.describe("Unique ID starting with 'prop_' (e.g., 'prop_farming_tools'). A sequential sheet of related items or lifecycle states (e.g., tilled soil stages)."),
124
+ referenceId: z
125
+ .union([z.string(), z.array(z.string()).min(1)])
126
+ .describe("Map ID when world-anchored, or parent HUD ID when UI-anchored."),
127
+ propsMultipleBreakdown: z
128
+ .string()
129
+ .min(1)
130
+ .describe("Description mapping each item index left-to-right on the sheet."),
164
131
  });
165
132
  const propsMapOverlaySchema = z.object({
166
- id: baseAssetFields.id.describe("Unique id for a map-anchored overlay group. Start with 'prop_' followed by a descriptive name (e.g., 'prop_kitchen_cupboard_states')."),
133
+ id: coreFields.id.describe("Unique ID starting with 'prop_' (e.g., 'prop_dungeon_door'). A stateful texture overlay aligned with an existing map structure."),
167
134
  anchorDescription: z
168
135
  .string()
169
- .describe("For props_map_overlay: Describe the exact existing map-baked structure/prop this overlay attaches to, including a stable location phrase from the map prompt/gameplay (e.g., 'north-wall kitchen cupboard', 'closed chest beside the bed', 'locked iron gate at the east path'). This is used to find the anchor and align every overlay state."),
170
- gamePlay: baseAssetFields.gamePlay.describe("For props_map_overlay: When each state shows/hides, interaction trigger, and whether the base map raw state stays visible underneath. Do not rely on this to identify the anchor; put the target structure in anchorDescription. Persisted on the parent map JSON under mapOverlays."),
171
- referenceId: referenceIdSchema.describe("Required: base_map or extend_map id — overlay states are written into that map's JSON file under mapOverlays."),
136
+ .describe("Exact description of the map-baked structure this overlay aligns with (e.g., 'iron cell gate on north wall')."),
137
+ gamePlay: coreFields.gamePlay,
138
+ referenceId: z
139
+ .string()
140
+ .describe("ID of the base or extend map hosting the anchor structure."),
172
141
  overlayStateBreakdown: z
173
142
  .array(mapOverlayStateSchema)
174
143
  .min(1)
175
- .describe("Required visual states for this map anchor. One entry per composited state. Single-state overlays use a one-item array. Example: [{ label: 'closed', prompt: 'Kitchen cupboard with shut doors' }, { label: 'open', prompt: 'Same cupboard with doors open revealing shelves' }]."),
176
- });
177
- const propsMultipleSchema = z.object({
178
- ...baseAssetFields,
179
- id: baseAssetFields.id.describe("Unique id for a multi-prop sheet. Start with 'prop_'."),
180
- prompt: baseAssetFields.prompt.describe("For props_multiple: Describe isolated, small pick-up items as purely physical shapes. This primary prompt is REQUIRED; use it for the overall composition, and use propsMultipleBreakdown only as extra numbered split guidance."),
181
- gamePlay: baseAssetFields.gamePlay.describe("For props_multiple: Pickup, placement, inventory, cursor use, state progression, or crafting role."),
182
- propsMultipleBreakdown: z
183
- .string()
184
- .optional()
185
- .describe("Optional secondary prompt mapping numbered bounding boxes to individual prop descriptions (e.g., 'Prop 1: bare dark tilled soil; Prop 2: tiny green sprout'). This is NOT a substitute for the required prompt field."),
186
- referenceId: referenceIdSchema
187
- .describe("Optional placement/style reference. Use a base_map or extend_map id for world props; use a hud_element id for UI-layer props. Omit to default to the batch map.")
188
- .optional(),
144
+ .describe("Visual state variants for the anchor. Exclude a top-level prompt field on this asset."),
189
145
  });
190
146
  const hudElementSchema = z.object({
191
- ...baseAssetFields,
192
- id: baseAssetFields.id.describe("Unique id for a HUD element. Start with 'hud_'."),
193
- prompt: baseAssetFields.prompt.describe("For hud_element: Describe the full UI layout using the HUD art-vs-code strategy: generated art owns atmosphere and stable chrome; code owns state, data, accessibility, and interactivity. Immersive/gameplay-facing HUDs (title/start screens, result screens, health/resource frames, hotbars, inventory grids, dialogue, shop, inspect/case-file views, compact status widgets, stylized quest/ledger shells) should bake stable frames, slots, panel borders, short stable labels, neutral button chrome, and title/menu artwork into the image. Title menus (hud_title_menu / hud_start_screen): cinematic full-screen composition with baked game title and baked neutral menu button artwork for stable labels such as Start Game, Load Game, Continue, Settings, or Credits; arrange buttons with clear rectangular hit regions for transparent code overlays. Hybrid information screens need a generated thematic backplate/border/header but a plain high-contrast blank surface for runtime text. Clean system UI such as settings, keybindings, save/load lists, and credits should usually be code-rendered rather than custom HUD art. Use neutral/default chrome only; put runtime state in gamePlay. Floating UI panels need solid flat neon green chroma-key background (#00FF00); full-screen title menus should use high-fidelity cinematic backgrounds instead."),
194
- gamePlay: baseAssetFields.gamePlay.describe("For hud_element: Explain UI wiring and runtime-only visual state that must NOT appear in the prompt: transparent hit regions, active tab highlights, hover/focus effects, disabled dimming/masks, dynamic text/numbers, slot contents, meter fill levels, scroll/list contents, and click behavior."),
147
+ ...coreFields,
148
+ id: coreFields.id.describe("Unique ID starting with 'hud_' (e.g., 'hud_start_screen'). Full-screen overlays, dialogue borders, menu boards, or shop panels."),
195
149
  });
196
150
  const multipleHudsSchema = z.object({
197
- ...baseAssetFields,
198
- id: baseAssetFields.id.describe("Unique id for a multi-HUD sheet. Start with 'hud_' followed by a group name, e.g. 'hud_common_widgets'."),
199
- prompt: baseAssetFields.prompt.describe("For multiple_huds: Describe several small isolated HUD widgets generated together in one sheet for cost-effective cropping/codegen. Use for compact reusable UI chrome like dialogue panels, quest widgets, status widgets, tooltip frames, command bubbles, notification plaques, portrait frames, and small inventory subpanels. The sheet must have a solid neon green #00FF00 background, generous spacing between widgets, neutral/default chrome only, and blank high-contrast runtime text surfaces. Do NOT use for full-screen title menus, result screens, large shop screens, or highly complex unique layouts; use hud_element for those."),
200
- gamePlay: baseAssetFields.gamePlay.describe("For multiple_huds: Explain the runtime role of each cropped widget, including dynamic text/data, transparent hit regions, hover/focus effects, open/close behavior, and where each small HUD should appear."),
151
+ ...coreFields,
152
+ id: coreFields.id.describe("Unique ID starting with 'hud_' (e.g., 'hud_small_panels'). A single green chroma-key sheet containing small reusable HUD widgets."),
201
153
  multipleHudsBreakdown: z
202
154
  .string()
203
- .describe("Required secondary prompt mapping numbered cropped HUD widgets to individual descriptions, ids/names, and runtime behavior. Example: 'HUD 1: dialogue panel with portrait frame and blank text parchment; HUD 2: compact top-left quest tracker with two blank objective rows.'"),
155
+ .describe("Cropping coordinates and descriptions for each small widget on the sheet."),
204
156
  });
205
157
  const musicBgSchema = z.object({
206
- ...baseAssetFields,
207
- id: baseAssetFields.id.describe("Unique id for background music. Start with 'music_'."),
208
- prompt: baseAssetFields.prompt.describe("For music_bg: Provide a cohesive, comma-separated list of tags. ALWAYS start with 'Instrumental' unless vocals are explicitly requested; include rhythmic glue, balanced instrument roles, exact game mood/setting, and end with utility tags like 'loopable, steady relaxed tempo'."),
209
- gamePlay: baseAssetFields.gamePlay.describe("For music_bg: Explain when the track should play (e.g., 'Loops during daytime gameplay')."),
158
+ id: coreFields.id.describe("Unique ID starting with 'music_' (e.g., 'music_pastoral_theme'). Background tracks. Always start with the word 'Instrumental' and include rhythmic anchors. No SFX."),
159
+ prompt: coreFields.prompt,
160
+ gamePlay: z
161
+ .string()
162
+ .optional()
163
+ .describe("Optional in-game usage context for the track."),
210
164
  });
211
- const assetArray = (schema, description) => z.array(schema).default([]).describe(description);
212
- export const assetToolSchema = z
213
- .object({
165
+ const assetBatchFields = {
214
166
  artStyle: z
215
167
  .string()
216
- .describe("Shared style direction applied across all visual assets. This should stay consistent across the entire game."),
217
- isFirstBuild: z
218
- .boolean()
219
- .describe("Indicate if this is the first time running the assets tool and there has been no previous successful asset generation in this workspace (including after cloning another workspace). " +
220
- "When true, default to one base_map and avoid extend_map, edit_map, or map_vfx unless the user explicitly asks for more."),
221
- baseMaps: assetArray(baseMapSchema, "Base map assets to generate. CRITICAL: You must ensure absolute spatial consistency between the visual prompt and the gameplay field. If gameplay requires an NPC or player to stand 'behind' a counter, bar, table, or desk, the visual prompt must explicitly describe a walkable aisle/gap between that furniture and the North wall. Do not place furniture flush against walls if characters need to occupy the space behind them."),
222
- extendMaps: assetArray(extendMapSchema, "Map extension assets to generate. Only when the new wing shares the reference map's shell and floor — BAD: interior lab extend_map to exterior dock; GOOD: interior lab extend_map to interior corridor with same floor phrase. Must share the reference map's shell and floor material. Maintain the same spatial rules: ensure any counter or barrier where NPCs stand has a clear, described walkable aisle behind it, completely free of wall collisions."),
223
- editMaps: assetArray(editMapSchema, "Surgical static map edit assets to generate."),
224
- mapVfx: assetArray(mapVfxSchema, "Animated map VFX assets to generate."),
225
- baseCharacters: assetArray(baseCharacterSchema, "Base character sprites. Set characterBodyFormat on every entry (default humanoid). Player/simple NPC: empty-handed with defaultAnimation. Job NPC: empty-handed parent base with NO defaultAnimation — pair with a base_character_variant for the visible prop-in-pose."),
226
- baseCharacterVariants: assetArray(baseCharacterVariantSchema, "Job-NPC prop-in-pose keyframes. One variant per job NPC with the signature prop; set defaultAnimation for looping idle and use as anchor for movement/task character_animations. Do not create variants for player or simple NPCs."),
227
- characterAnimations: assetArray(characterAnimationSchema, "Character animation spritesheets. Player/simple NPC: one walk loop on base_character (idle via base_character defaultAnimation). Job NPC: up to 2 movement/task clips on the variant (idle via variant defaultAnimation). Never exceed 3 generated clips per job NPC."),
228
- propsSingles: assetArray(propsSingleSchema, "Single prop assets to generate."),
229
- propsMultiples: assetArray(propsMultipleSchema, "Multiple-prop sheet assets to generate."),
230
- propsMapOverlays: assetArray(propsMapOverlaySchema, "Map-anchored structural overlay props. Use when a state change must align with a specific map-baked structure (open cupboard, repaired building, opened chest). Returns image URL + box_2d per state. Not for portable pickups or generic zone tiles."),
231
- hudElements: assetArray(hudElementSchema, "HUD/UI image assets to generate."),
232
- multipleHuds: assetArray(multipleHudsSchema, "Multi-HUD sheets to generate. Each sheet is cropped into multiple small HUD widgets and each crop receives generated widget code."),
233
- backgroundMusic: assetArray(musicBgSchema, "Background music assets to generate."),
234
- })
235
- .describe("Expensive asset generation request. Before filling this schema, decide the complete asset batch needed for the user's requested outcome. Preserve the user's visual wording, style constraints, placement, and exclusions in the asset prompts/gamePlay; do not simplify them into a different asset idea. Bundle all related asset needs for the user's current turn into one call. Do not call assetsAgent again to verify, double-check, or add items that should have been included in the same batch unless the first call failed/incomplete, a required dependency blocks describing the second batch up front, or the user explicitly approves another separate batch.")
236
- .refine((input) => input.baseMaps.length +
237
- input.extendMaps.length +
238
- input.editMaps.length +
239
- input.mapVfx.length +
240
- input.baseCharacters.length +
241
- input.baseCharacterVariants.length +
242
- input.characterAnimations.length +
243
- input.propsSingles.length +
244
- input.propsMultiples.length +
245
- input.propsMapOverlays.length +
246
- input.hudElements.length +
247
- input.multipleHuds.length +
248
- input.backgroundMusic.length >
249
- 0, "At least one asset array must contain an asset.");
250
- const withType = (assets, type) => (assets ?? []).map((asset) => ({ ...asset, type }));
251
- export const mapAssetToolInputToLegacy = (input) => ({
252
- artStyle: input.artStyle,
253
- isFirstBuild: input.isFirstBuild,
254
- assets: [
255
- ...withType(input.baseMaps, "base_map"),
256
- ...withType(input.extendMaps, "extend_map"),
257
- ...withType(input.editMaps, "edit_map"),
258
- ...withType(input.mapVfx, "map_vfx"),
259
- ...withType(input.hudElements, "hud_element"),
260
- ...withType(input.multipleHuds, "multiple_huds"),
261
- ...withType(input.baseCharacters, "base_character"),
262
- ...withType(input.propsSingles, "props_single"),
263
- ...withType(input.propsMultiples, "props_multiple"),
264
- ...input.propsMapOverlays.map((asset) => ({
265
- ...asset,
266
- type: "props_map_overlay",
267
- prompt: asset.overlayStateBreakdown
268
- .map((state) => `${state.label}: ${state.prompt}`)
269
- .join(" | "),
270
- })),
271
- ...withType(input.baseCharacterVariants, "base_character_variant"),
272
- ...withType(input.characterAnimations, "character_animation"),
273
- ...withType(input.backgroundMusic, "music_bg"),
274
- ],
275
- });
276
- export const codingAgentTaskTypeSchema = z.enum([
277
- "new_game",
278
- "bug_fix",
279
- "revisit_previous_bug_fix",
280
- "feature_implementation",
281
- "explore",
282
- "general",
283
- ]);
284
- const codingAgentTaskFieldDescription = "What the coding agent should do this turn. Scale the detail to 'type' — do NOT force a full game brief on every call. The coding agent automatically reads the asset manifest, so always omit file paths and pixel dimensions and focus on functional game logic and asset behavior. " +
285
- "Preserve the user's actual wording and context. Include a short current-vs-desired framing when relevant: what the user says currently happens, what they want instead, when it should happen, what UI/game state it applies to, and any negations or 'not X, but Y' distinctions. Put implementation hints after that preserved context, not instead of it. " +
286
- "This is an expensive coding tool: before setting task/type, decide the user's requested outcome, pick the workflow type, and draft one bundled task brief that covers all related code work. Default to one codeAgent call per user turn, bundle all related code changes into one task, and never call it again just to verify, double-check, or polish the same successful task. A second same-turn codeAgent call is only for a failed/incomplete prior call, a clear user-reported unresolved prior bug via revisit_previous_bug_fix, or genuinely independent work that cannot be safely bundled. " +
287
- "For 'new_game': on the first build the server injects the approved plan automatically — pass a short placeholder (for example \"Use approved plan\"). " +
288
- "For 'bug_fix': use when something is broken, incorrect, crashing, or not working as expected for the first fix attempt on that bug. Pass one or more related bug reports in a single task (e.g. 'Player falls through floor; inventory count is wrong'). Do not use 'explore' first as a preflight. " +
289
- "For 'revisit_previous_bug_fix': use only after a prior successful codeAgent bug_fix when the user says the bug still isn't fixed and their updated description is clear enough to act on. Loads the last bug-fix session and retries without re-analyzing the compiled bundle. Pass the user's updated bug description in task. If the user's report is vague or may reflect misunderstood intent, ask a clarifying follow-up question in chat instead of calling this type. " +
290
- "For 'feature_implementation': use when the user wants new behavior, mechanics, UI, asset wiring, SDK integrations, balance changes, or polish — not fixing broken existing behavior. Pass one or more related feature requests in a single task. Do not use 'explore' first as a preflight. " +
291
- "If a single user turn mixes bug fixes and feature work, prefer one bundled codeAgent call using the dominant type when the items affect the same feature, subsystem, UI flow, or player-facing outcome. Split only for genuinely independent tasks that require different workflows and cannot be safely bundled. " +
292
- "For 'explore': a plain read-only question about the codebase, asset manifest, wired gameplay, or game state (e.g., 'List all asset IDs and types in src/data' or 'Summarize the current map layout and wired mechanics'). Do not request any changes. Never use for broken behavior or implementation requests unless the user explicitly asks for read-only/no-change analysis. " +
293
- "For 'general': fallback only when the task clearly needs code changes but does not fit new_game, bug_fix, revisit_previous_bug_fix, feature_implementation, or explore. Pass a concise task brief.";
294
- const codingAgentTypeFieldDescription = "Select the coding workflow. " +
295
- "This is an expensive coding tool: before choosing the type, decide the bundled task brief and choose the one workflow that best fits the user's requested outcome. Do not use a second codeAgent call for verification after a successful complete result. " +
296
- "'new_game': the first playable implementation after assets are generated on a brand-new game (no prior successful codeAgent run in this workspace); starts a fresh coding session. " +
297
- "'bug_fix': first attempt to fix broken behavior after the first build. Builds and analyzes the compiled bundle for root cause, then implements a minimal fix. Use for errors, crashes, logic bugs, and initial 'not working' reports. A single call may include multiple related bug fixes in the task string. " +
298
- "'revisit_previous_bug_fix': retry a bug fix after a prior successful bug_fix when the user says it still isn't fixed and you have a clear updated description. Resumes the last bug-fix Pi session without re-analyzing the compiled bundle. If the user's report is vague or you may have misunderstood the original bug, ask a clarifying follow-up in chat instead. " +
299
- "'feature_implementation': add or change game behavior after the first build when nothing is fundamentally broken. Use for new mechanics, UI behavior, asset wiring, SDK integrations, balance changes, and polish. A single call may include multiple related feature requests in the task string. " +
300
- "When one user turn contains both bug fixes and feature requests, prefer one bundled call with the dominant type when the items are related; split only for genuinely independent tasks that require different workflows. Do not use 'explore' first as a preflight for either workflow. " +
301
- "'explore': read-only inspection of the codebase, asset manifest, wired gameplay, or game state with NO changes; pass a plain exploration question as the task. Never use for broken behavior, vague 'not working' reports, or implementation requests unless the user explicitly asks for read-only diagnosis. " +
302
- "'general': last resort when code changes are needed but no other workflow applies. Runs the coding agent directly on the task without specialized pre-analysis.";
303
- export const parseCodingAgentTaskType = (type) => {
304
- const parsed = codingAgentTaskTypeSchema.safeParse(type);
305
- if (parsed.success) {
306
- return { ok: true, value: parsed.data };
307
- }
308
- const allowed = codingAgentTaskTypeSchema.options
309
- .map((option) => `"${option}"`)
310
- .join(" | ");
311
- return {
312
- ok: false,
313
- summary: `Invalid codeAgent type "${type}". Expected one of: ${allowed}.`,
314
- };
168
+ .describe("Shared visual style constraints for all assets in this batch to maintain stylistic consistency."),
169
+ baseMaps: assetArray(baseMapSchema, "New base maps."),
170
+ extendMaps: assetArray(extendMapSchema, "Map extensions connected to existing maps."),
171
+ editMaps: assetArray(editMapSchema, "Static edits to an existing map."),
172
+ mapVfx: assetArray(mapVfxSchema, "Animated VFX on an existing map."),
173
+ baseCharacters: assetArray(baseCharacterSchema, "Character sprites."),
174
+ baseCharacterVariants: assetArray(baseCharacterVariantSchema, "Job-NPC prop-in-pose variants."),
175
+ characterAnimations: assetArray(characterAnimationSchema, "Character animation sheets."),
176
+ propsSingles: assetArray(propsSingleSchema, "Single pickup props."),
177
+ propsMultiples: assetArray(propsMultipleSchema, "Multi-prop sprite sheets."),
178
+ propsMapOverlays: assetArray(propsMapOverlaySchema, "Map-anchored overlay states (doors, chests, etc.)."),
179
+ hudElements: assetArray(hudElementSchema, "HUD/UI images."),
180
+ multipleHuds: assetArray(multipleHudsSchema, "Multi-widget HUD sheets."),
181
+ backgroundMusic: assetArray(musicBgSchema, "Background music tracks."),
315
182
  };
316
- /** DurableAgent tool boundary — accepts any string type; validate in execute. */
317
- export const codingAgentToolLooseSchema = z.object({
318
- task: z.string().describe(codingAgentTaskFieldDescription),
319
- type: z.string().describe(codingAgentTypeFieldDescription),
320
- });
321
- export const codingAgentToolSchema = z.object({
322
- task: z.string().describe(codingAgentTaskFieldDescription),
323
- type: codingAgentTaskTypeSchema.describe(codingAgentTypeFieldDescription),
324
- });
325
- export const newGameCodeAgentToolSchema = z.object({});
326
- export const exploreCodeAgentToolSchema = z.object({
327
- query: z
328
- .string()
329
- .min(1)
330
- .describe("One high-level natural-language, read-only codebase question for the coding agent to answer. Ask for current behavior, ownership, risks, and the safest implementation path. Do NOT include shell commands, grep terms, guessed file paths, or a file-by-file checklist unless those exact details came from prior tool output."),
331
- isFollowup: z
332
- .boolean()
333
- .describe("Spell this field exactly as isFollowup. Use true only when continuing the most recent successful exploreCodeAgent session in this chat. Use false for a fresh, unrelated user goal. When true, the query should summarize the previous user question, the prior exploreCodeAgent query, the exploreCodeAgent output, and what needs clarification now."),
334
- });
335
- export const implementCodeAgentToolSchema = z.object({
336
- task: z
337
- .string()
338
- .min(1)
339
- .describe("One high-level natural-language implementation task for the coding agent. Write in first-person as a playtester reporting visible symptoms — do not guess file paths, subsystems, or technical root causes. NEVER embed or append raw file contents (assets.md, SCENES.md, package.json, scene files, or source code); Codex reads those autonomously. Do not write this as a chat transcript. For normal bugs/features, lead with the task and desired outcome, then include the user's symptom language as player report/context. For browser/runtime errors, keep the useful exception signature but drop hosted workspace URLs, repeated stack frames, minified line/column offsets, and bundled main.js/dist/main.js locations; do not infer feature/domain ownership from a property name alone. Ask the coding agent to ground the fix in actual code/assets and validate appropriately — do not invent root causes, subsystems, file names, inspected paths, or exact validation results. When isFollowup is true, summarize the prior same-tool chain in this task: what the user originally said, what you previously asked implementCodeAgent to do, what implementCodeAgent output, what changed or remained incomplete, and what the user is asking now. When attemptType is stuck_bug_loop, use isFollowup false and put the full stuck-bug context in this brief: original user report(s) verbatim, what you asked implementCodeAgent in each prior attempt, each prior implementCodeAgent output, previous changes it reported, latest user feedback verbatim, baseline commitId from the first checkpoint in that bug chain when available, and ask the coding agent to re-investigate from first principles, identify the real cause, and fix it. Do not split normal bug fixes or feature changes into locate/fix/verify task lists."),
340
- isFollowup: z
341
- .boolean()
342
- .default(false)
343
- .describe("Spell this field exactly as isFollowup. Use true only when continuing the most recent successful implementCodeAgent session in this chat. Use false for a fresh user goal, for an unrelated goal, or when the prior same-tool session was exploreCodeAgent rather than implementCodeAgent. For attemptType stuck_bug_loop, always use false — context goes in the task brief, not session history."),
344
- attemptType: z
345
- .enum(["normal", "stuck_bug_loop"])
346
- .default("normal")
347
- .describe("Use normal for fresh implementation and ordinary same-session follow-ups. Use stuck_bug_loop only when the user reports the same bug is still unresolved after one or more prior implementCodeAgent attempts; pair with isFollowup false and put original user report(s), what you asked implementCodeAgent in each prior attempt, each prior implementCodeAgent output, previous changes it reported, latest user feedback, and baseline commitId (if available) in the task brief. This starts a fresh coding-agent session with the full prior chain in the prompt."),
348
- });
349
- export const ASSET_TOOL_DESCRIPTION = "Generate layered game assets for a Stardew Valley–style 2.5D top-down (¾ bird's-eye) browser game. " +
350
- "Preserve the user's visual wording, style constraints, placement, and exclusions in the asset brief; do not simplify them into a different asset idea. " +
351
- "This is an expensive generation tool: before calling it, decide the complete asset batch needed for the user's requested outcome. Bundle all related asset needs into one call, and do not call it again to verify or add items that belonged in the same batch unless the first call failed/incomplete or the user explicitly approves another separate batch.";
352
- export const mcpGenerateAssetInputSchema = assetToolSchema.and(z.object({
183
+ const mcpClientExtraFields = {
353
184
  output_dir: z
354
185
  .string()
355
186
  .optional()
356
- .describe("Directory to write generated assets into. Defaults to the workspace root."),
187
+ .describe("Write directory. Defaults to workspace root."),
357
188
  workspace_root: z
358
189
  .string()
359
190
  .optional()
360
- .describe("Root directory to read workspace context from (assets.md, src/data/*.json). Defaults to the current working directory."),
361
- plan_used_to_build: z
362
- .string()
363
- .optional()
364
- .describe("Optional plan text for first-time asset generation when assets.md is empty (replaces in-app plan chat lookup)."),
365
- art_reference_url: z
366
- .string()
367
- .optional()
368
- .describe("Optional art style reference image URL for base_map generation."),
369
- }));
370
- export const mcpGenerateAssetRequestSchema = mcpGenerateAssetInputSchema.and(z.object({
191
+ .describe("Root to read assets.md and src/data/*.json from."),
192
+ };
193
+ const mcpServerExtraFields = {
371
194
  workspace_files: z
372
195
  .record(z.string(), z.string())
373
196
  .optional()
374
- .describe("Client-provided workspace files keyed by repo-relative path."),
375
- session_id: z
197
+ .describe("Repo-relative path file content."),
198
+ session_id: z.string().optional().describe("Stable generation session id."),
199
+ gameId: z
376
200
  .string()
377
201
  .optional()
378
- .describe("Stable session id for this generation run (usually the job id)."),
379
- }));
202
+ .describe("Existing game id from index.html; set automatically by the client."),
203
+ };
204
+ const countAssets = (input) => (input.baseMaps?.length ?? 0) +
205
+ (input.extendMaps?.length ?? 0) +
206
+ (input.editMaps?.length ?? 0) +
207
+ (input.mapVfx?.length ?? 0) +
208
+ (input.baseCharacters?.length ?? 0) +
209
+ (input.baseCharacterVariants?.length ?? 0) +
210
+ (input.characterAnimations?.length ?? 0) +
211
+ (input.propsSingles?.length ?? 0) +
212
+ (input.propsMultiples?.length ?? 0) +
213
+ (input.propsMapOverlays?.length ?? 0) +
214
+ (input.hudElements?.length ?? 0) +
215
+ (input.multipleHuds?.length ?? 0) +
216
+ (input.backgroundMusic?.length ?? 0);
217
+ /** MCP tool input — assets grouped by type. */
218
+ export const assetToolSchema = z.object(assetBatchFields);
219
+ const withType = (assets, type) => (assets ?? []).map((asset) => ({ ...asset, type }));
220
+ /** Flatten grouped MCP input into the API payload agent-server expects. */
221
+ export const flattenAssetToolInput = (input) => {
222
+ if (countAssets(input) === 0) {
223
+ throw new Error("At least one asset array must contain an asset.");
224
+ }
225
+ return {
226
+ artStyle: input.artStyle,
227
+ assets: [
228
+ ...withType(input.baseMaps, "base_map"),
229
+ ...withType(input.extendMaps, "extend_map"),
230
+ ...withType(input.editMaps, "edit_map"),
231
+ ...withType(input.mapVfx, "map_vfx"),
232
+ ...withType(input.baseCharacters, "base_character"),
233
+ ...withType(input.baseCharacterVariants, "base_character_variant"),
234
+ ...withType(input.characterAnimations, "character_animation"),
235
+ ...withType(input.propsSingles, "props_single"),
236
+ ...withType(input.propsMultiples, "props_multiple"),
237
+ ...withType(input.hudElements, "hud_element"),
238
+ ...withType(input.multipleHuds, "multiple_huds"),
239
+ ...withType(input.backgroundMusic, "music_bg"),
240
+ ...(input.propsMapOverlays ?? []).map((asset) => ({
241
+ ...asset,
242
+ type: "props_map_overlay",
243
+ prompt: asset.overlayStateBreakdown
244
+ .map((state) => `${state.label}: ${state.prompt}`)
245
+ .join(" | "),
246
+ })),
247
+ ],
248
+ };
249
+ };
250
+ /** Accept grouped MCP input or an already-flattened REST payload. */
251
+ export const normalizeAssetToolInput = (input) => {
252
+ if (Array.isArray(input.assets)) {
253
+ const flat = input;
254
+ if (flat.assets.length === 0) {
255
+ throw new Error("At least one asset must be provided.");
256
+ }
257
+ return flat;
258
+ }
259
+ return flattenAssetToolInput(input);
260
+ };
261
+ export const ASSET_TOOL_DESCRIPTION = "Generate visual and audio game assets for a 2.5D top-down (3/4 oblique) browser game. " +
262
+ "Bundle all related assets (maps, characters, walk loops, HUD layout, and looping background music) " +
263
+ "for the current design phase into a single tool invocation to maintain stylistic consistency and establish proper dependency linking.";
264
+ // Plain z.object — MCP SDK only advertises tool parameters for object schemas
265
+ // (not z.and / z.refine), otherwise clients see { properties: {} }.
266
+ export const mcpGenerateAssetInputSchema = z.object({
267
+ ...assetBatchFields,
268
+ ...mcpClientExtraFields,
269
+ });
270
+ export const mcpGenerateAssetRequestSchema = z.object({
271
+ ...assetBatchFields,
272
+ ...mcpClientExtraFields,
273
+ ...mcpServerExtraFields,
274
+ });
380
275
  //# sourceMappingURL=asset-tool-schema.js.map