Polly Studio
Log inSign up

How Polly games are built

These are the practices Ember was built with. Every Polly game follows them.

1. One readable file

2. Runtime lives in vendor/

3. Zero network, zero asset files

4. Tuning in one place

5. A clear loop and clear phases

6. Desktop first, phone friendly

7. Juice

8. Animations and explainer videos

9. Community rules

10. Multiplayer when asked

The game-kit API

Games are written against this API. Frankie reads the same list through GET /api/v1/guide, so it never invents functions.

Kit.clamp(v,a,b) Kit.lerp(a,b,t) Kit.rand(a,b) Kit.randInt(a,b) Kit.pick(arr) Kit.dist(ax,ay,bx,by) Kit.dist2(...) Kit.hit(a,b)  // a,b: {x,y,r}
Kit.canvas({width=1280,height=720,id='c',webgl=false,onResize}) -> view {canvas, ctx, width, height, scale, toGame(clientX,clientY)}  // letterboxed, DPR aware; ctx already scaled to logical units
Kit.loop(update(dt), render(dt)) -> loop {time, paused, pause(), resume(), toggle(), stop()}  // dt clamped to 1/20s, pauses when tab hidden
Kit.input.axis(player=0) -> {x,y} (length<=1; P1 = WASD + arrows + touch drag; with setPlayers(2): P1 = WASD+touch, P2 = arrows)
Kit.input.action(player=0) -> bool this frame (P1 Space/E, P2 Enter/RightShift)
Kit.input.key(code) Kit.input.pressed(code)  // KeyboardEvent.code, pressed = this frame only
Kit.input.tap() -> {x,y} in game units or null  // a tap without dragging, this frame
Kit.input.mouse() -> {x,y,down,clicked}  // game units; down = left button held, clicked = this frame. First call makes the mouse aim instead of drag-to-move
Kit.input.setPlayers(1|2)
Kit.audio.sfx.blip() .pickup() .hit() .jump() .shoot() .boom() .win() .lose()
Kit.audio.tone({freq,to,dur,type:'square'|'sine'|'triangle'|'sawtooth',vol,delay}) Kit.audio.noise({dur,cutoff,vol,delay})
Kit.audio.toggle() -> muted  Kit.audio.muted  Kit.audio.setMuted(bool)
Kit.fx.burst(x,y,n,color,speed,life,size) Kit.fx.text(x,y,str,color) Kit.fx.shake(mag,dur) Kit.fx.clear()
Kit.fx.applyShake(ctx) (call inside ctx.save()/restore() before drawing the world) Kit.fx.draw(ctx) (draws particles + floating text, 2D only)
Kit.store.get(key, fallback) Kit.store.set(key, value)  // JSON, never throws
Kit.ui.el(tag, attrs, html) Kit.ui.overlay({title, body(html), button, onClick}) -> element (removes itself on button click)
Kit.film(scenes, {loop, fade, captions:'pop'|'bar', onEnd}) -> film {time, duration, playing, play(), pause(), seek(t), restart()}
  // scenes: [{dur, caption, enter(film), draw(ctx, t, p)}]; t = s into scene, p = 0..1. Kit.loop(film.update, film.render)
Kit.record({seconds, onStart(), onDone(blob, name)}) -> {stop()}  // canvas + Kit sounds to MP4/WebM
Kit.net.connect({room?, game?}) Kit.net.on('open'|'join'|'leave'|'close'|'error'|<type>, fn(data, fromId))
Kit.net.send(type, data, toId?) Kit.net.isHost() Kit.net.hasInvite() Kit.net.id Kit.net.peers Kit.net.room Kit.net.inviteLink() Kit.net.disconnect()
  // 'open' data: {id, peers, room}. Room comes from ?room= in the URL if present.

Building games through the Polly API

1. The open game's id is gameId in your page context. getGame returns its outline: sections and functions with line numbers.

2. Find code with searchGameCode, read it with readGameCode (numbered lines, a range at a time). Read before you change.

3. Change it with editGame: exact find/replace edits, baseVersion = the version you read. Copy find text exactly from readGameCode,

without the "12| " line numbers, with enough lines to be unique. Several edits in one call apply together or not at all.

4. Only for a whole new game or a total rewrite, send the complete file as html to updateGame.

5. Every save is checked: a JavaScript syntax error is refused with its line (nothing is lost), and unknown Kit names come back as warnings.

Fix warnings with another edit. Each save is a new version; restoreGameVersion undoes one.

6. No game open: listStarters, then createGame with the closest starterId and a title, then edit it.

7. Share: updateGame with visibility: "public". Give the person playUrl or downloadUrl; never paste game code into the chat.