Appearance
Prototype
User Argument:
The request is the text after this skill block (or the argument above). Read it once and pick exactly one path:
- Unavailable here. Before writing anything, check two facts: your tool list has both
begin_conversionandpromote_conversion, and your system prompt or a platform notice says durable workspace checkpoints (workspace backups) are enabled. If either is missing, say in one line that the prototype workflow is unavailable in this session, do not write.poe-prototype, and followtile-creatorfor the request as it stands. - A real activity already exists. A
.poe-tile.jsonin the workspace with no.poe-prototypebeside it is a real activity. Say so in one line and continue withtile-creator. Do not callbegin_conversion; there is nothing to convert. - Convert. The request asks to convert the prototype ("make it real", "turn this into the full activity"), or asks outright for the real thing with no prototype step ("skip the prototype", "build the multiplayer version now"). Go to Convert, even in a session whose workflow setting is direct.
- Prototype. Anything else: a new idea when the workspace has no manifest, or a change while
.poe-prototypeexists. Go to Prototype.
Say which path you chose in one line, then work.
While a prototype exists, tile-creator and synced-store are not part of your job: do not read them, and do not scaffold, install, or add a store. The bash, write, and edit tools refuse those actions in this phase, and each refusal is counted. Convert is where they come back.
Prototype
Goal: a preview the creator can open at the end of this turn, written so that the later split into project files is mechanical.
Files
Write exactly three files under /workspace/<name>/. <name> is a kebab-case base from the request plus a short random suffix (cat-flap-k4x7); it becomes the handle, and nobody has to see it.
.poe-tile.json:json{ "$schema": "https://poe-tiles-docs.pages.dev/schemas/poe-tile/v10.json", "handle": "cat-flap-k4x7", "displayName": "New Arcade Activity", "shortDescription": "One sentence about what this does, under 140 characters.", "runtimeBundle": { "dir": "." } }displayNamefollows the placeholder rule: "New" plus the kind of activity (New Word Activity, or plainNew Activitywhen the request names no theme). A name the creator supplied wins over the placeholder. NosourceBundle..poe-prototype: empty. Its presence is the phase; every boot re-derives the phase from disk, not from memory.index.html: the whole prototype.
No package.json, no bun install, no build, no tests, no poe-tiles-sdk import, no poe-tiles doctor. There is no project to install or check.
index.html
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">and<meta name="color-scheme" content="light dark">.- A static shell in the body before any script: title, HUD (score, status), the play surface, and the line
Prototype: state resets on reload.The host lifts its loading overlay on the first contentful paint, so this markup, not the script, is what makes the preview appear at once. - One
<style>block and one<script type="module">, each divided by header comments into these six labelled modules, in this order:state,rules,render,input,loop,styles. Thestylesmodule is the<style>block; label its sections with the same names. Convert ports one module to one file, so keep each module's cross-module reads narrow:rulesis pure functions overstate;renderreads state and draws;inputtranslates events intorulescalls;loopowns time. - Memory only. State lives in module variables. No
localStorage, no network, no external scripts, fonts, or images: the bundle runs from ablob:URL and cannot fetch files beside it, so draw with canvas, CSS, and inline SVG. - Single device. A second player is hot-seat or simulated; rooms, turns, and invitations arrive with Convert.
How a prototype should feel
The prototype exists to answer "does this feel right?", so these are requirements, not polish:
- Mobile first: a 390×760 phone viewport is the design target. Pointer events,
touch-action: noneon the play surface, tap targets at least 44 px, no hover-only affordances. - Light and dark from
prefers-color-scheme, with CSS variables for the palette. Never hardcode one scheme. - Pad against device chrome with
var(--poe-safe-area-inset-top, env(safe-area-inset-top))and the other three sides. Never use rawenv(safe-area-inset-*)alone. - For anything with motion: drive simulation from a fixed or clamped timestep so identical inputs give an identical run at 30 Hz and at 120 Hz. Never scale movement by a raw frame delta.
- For a skill or scored game: generate the challenge per run from a seed kept in state; bound the step between consecutive elements by what the movement constants allow; ramp from a forgiving opening to a floor; score once per obstacle and only after it is fully cleared; match every collision shape to the shape drawn. Unscoreable and unmissable are both failures, so tune both ends.
- Where the player embodies a character, draw one: an assembled silhouette that reacts to input and to what happens to it, not a rectangle or a lone emoji.
- Show failures in the UI, not only in the console.
Finishing a prototype turn
Write .poe-tile-change-summary beside .poe-tile.json: one plain sentence a person would want to read in Version History. Then end the turn. The platform publishes the directory as an unlisted preview and posts the link; you run no publish command and paste no link. Close with one sentence inviting changes, or converting once it feels right.
On a later turn while .poe-prototype exists: make the change, write a fresh change summary, end the turn. Keep the module labels intact.
Convert
Goal: the same handle and the same live directory, now a full activity, with the prototype kept intact until the converted activity has published once.
- Call
begin_conversionfirst, with no arguments, before any other command. It checkpoints the workspace durably, posts the notice the creator sees, lifts the phase guard, and returnsconversionId,source(prototypeorempty),handle,livePath, andstagingPath. Use those values; do not invent paths or a handle. If it returns an error, report it in one line and end the turn: the workspace stays closed to changes, and the creator's next message retries the checkpoint through the platform. After a restart, call it again; it resumes the same conversion. - Read
tile-creatorandsynced-storenow. They govern the staging work from here. - Scaffold into staging:
cd /workspace/.staging && poe-tiles tiles init <handle> --template <react|preact|solidjs|vanilla-js|phaserjs>(store on), thenbun installin the new directory. Confirm it isstagingPath..stagingis outside the publish scan, so nothing publishes from it, and the scaffold's handle equals the prototype's, which is what the promotion needs. - Port. For
source: prototype, one module to one place:statesplits into synced-store schema (everything durable or shared: progress, scores, seats, turns) and local UI state;rulesbecomes pure functions plus a mutator for every shared transition;render,input, andstylesgo to the UI;loopstays client-side and drives simulation from store state. A hot-seat "second player" becomes a real member andsetTurn. CopydisplayNameandshortDescriptionfrom the prototype manifest into the staged.poe-tile.jsonand keep itshandle. Forsource: empty, there is no prototype to read or port: build from the creator's request. - Verify in staging: run
bun run verifyin the staging directory until it passes, then the browser checkstile-creatorrequires. Fix and rerun. Never promote a red build. - Call
promote_conversion({ conversionId }). The bridge verifies the candidate again, checkpoints it, and performs the renames itself: for a prototype, live to.staging/<name>.prevand then staging to live; for an empty source, staging to live. Nevermvorrmthose directories and never edit.staging/conversion.json. An error names what to repair in staging; fix it and call the tool again with the sameconversionId. - End the turn with a change summary in the live directory and no further workspace commands. The publish under the recorded handle, the
.prevcleanup, and the record cleanup belong to the bridge. From the next turn on, thetile-creatordefinition of done applies.
Two manifests with the same handle must never be visible to the publish scan at a turn boundary. The bridge's renames are the only way the converted tree becomes live.