# Pockit Engine extension guide

Pockit is a small 2D game editor and public game library. The editor and runtime use plain HTML, CSS, and JavaScript; accounts, private drafts, and published snapshots use Supabase. The game runtime itself remains self-contained.

For a visual walkthrough of the editor, open the [Maker’s Guide](guide.html). This document covers extensions and runtime details.

## What works today

### Precision platforming

Player Inspector → Precision movement & forgiveness exposes `coyoteTime` and `jumpBuffer` (0–250 milliseconds), `cornerCorrection` (0–6 world pixels), `instantMovement`, `dashEnabled`, `dashSpeed` (100–600 px/s), `dashDuration` (50–350 ms), `wallJump`, `wallClimb`, `climbStamina` (1–10 seconds), and `respawnGrace` (0–2000 ms). Defaults keep dash and wall movement off. New and legacy projects receive 100 ms of ledge grace, a 120 ms jump buffer and three pixels of upward corner correction. Set these to zero for strict movement.

Dash uses Shift or X plus a direction; X shooting is disabled for a platformer with native dash enabled. One charge refills on landing, wall jump, or a `refill` trait. C grips a nearby wall, Up/Down climbs, and jumping pushes away. The `refill` trait restores dash and stamina and reappears after its configurable delay. These mechanics are available in published games. Game-setting blocks expose the precision switches (0/1) and numeric movement values. The “forgiving ledge jump” and “early jump” recipes configure the native input handling instead of competing with the player update.

`onLevel` runs once per level initialization, including restart. Parent translation is applied before rider movement, so solid children can carry the player. These changes are covered by the precision and scene regression tests.

- Platform, top-down, and shooter movement; editable acceleration, gravity, jump speed, variable jump height, extra jumps, auto-run, and independent hitboxes.
- Levels from 32×18 up to 160×90 tiles with a smooth following camera. A painted background layer behind the world.
- **Traits**: 23 composable behaviors (below) that any object can stack, each with sliders/switches. Built-in objects can be re-traited per project; custom objects start from any object.
- A 30-object mechanics library (platforms, lifts, springs, crumbling/blinking blocks, crates, spikes that fall or move, ghosts, bats, turrets, nests, keys/doors, switches/gates, portals, signs, ladders, water, gems, hearts, stars, scenery) with art for all five original packs.
- Sprite animation: default loops and named clips on every object, imported frame sheets, plus automatic walk/jump states for the player. Trigger named clips from the Animation blocks or JavaScript.
- Backdrops: twelve styles, parallax, clouds, color cycling, darkness, reflective water, uploaded images.
- Hand-authored level sequences, seeded rooms/mazes/platform courses of any size, endless rooms, looping levels, story chapters, and enemy waves.
- Scoring, health, time limits, five win conditions, seven power-up effects, checkpoints, keys and doors (four channels), switches and gates.
- True 8×8 sprite editing, five original themed art packs with 86 named tiles and sprites each.
- 25 post-processing controls, synthesized sound effects, background music, keyboard and touch controls.
- Account-scoped cloud saves, JSON project backups, and playable publication snapshots. Preview and published games run the same self-contained HTML in sandboxed frames; the website offers publication rather than an HTML download.

## Current boundaries

All 50 templates are playable. Chasing enemies move toward the player directly and do not perform pathfinding. Scenes support gameplay, editable menus, branching conversations, and short cutscene sequences. Object-to-object collisions (other than bullets, stomps, and pushing) are not modeled. Multiplayer, simultaneous collaborative editing, paid sales are outside this version. Accounts use usernames and passwords without email. Each draft has a single owner.

Each cell is 16 world units; source sprites are 8×8 pixels. Up to 4,000 authored sprite parts and 4,096 live objects per level (including runtime spawns), 2,000 hierarchy groups, no fixed custom-object count cap, 20 authored levels, 8 traits per object, 12 animation frames per clip.

## Traits

Every object carries `traits: [{type, ...settings}]`. The registry in `traits.js` describes each setting (`number`, `bool`, `choice`, `object`, `text`), and the editor builds its dials from that description. Built-in traits:

| Trait | Does | Settings |
|---|---|---|
| `solid` | blocks movement | `oneWay` |
| `hurts` | damages the player on touch | `damage` |
| `collect` | picked up for points | `points`, `respawn` |
| `goal` | completes the level | — |
| `powerup` | temporary power | `effect` (shield, health, speed, jump, magnet, tiny, ghost), `duration` |
| `checkpoint` | sets the respawn point | — |
| `key` / `door` | doors need a key (or switch) with the same letter | `channel`, `opens`, `stayOpen` |
| `switch` | toggles a letter | `channel`, `mode` (toggle, hold, once) |
| `moves` | movement pattern | `pattern`, `speed`, `distance`, `startDir`, `onlyWhen` (a switch) |
| `falls` | drops with gravity | `when` (below, touch, always), `delay`, `respawn` |
| `crumbles` | breaks after being stood on | `after`, `respawn` |
| `blinks` | appears and disappears | `on`, `off`, `offset` |
| `bouncy` | launches the player | `power` |
| `pushable` | can be shoved | — |
| `enemy` | has health, can be defeated | `hp`, `stompable`, `shootable`, `points`, `drops` |
| `breakable` | breaks after hits | `hits`, `by`, `points`, `drops` |
| `shoots` | fires on a timer | `every`, `aim`, `bulletSpeed`, `range`, `friendly` |
| `spawns` | creates other objects | `object`, `every`, `max`, `range` |
| `zone` | changes movement inside | `effect`, `amount` |
| `teleport` | jumps to the twin with the same letter | `channel` |
| `sign` | shows a message | `text` |
| `code` | runs your JavaScript for this object | `source` |

Solid-only objects and plain scenery are static tiles; everything else becomes a live entity `{kind, x, y, vx, vy, alive, active, hp, tr, home, dir, timer, …}` with its own copy of the trait settings (`me.tr.moves.speed`, for example).

### Custom code per object

The `code` trait's source receives `me` (the entity) and `api`, and returns hooks:

```js
return {
  update(dt) { me.y = me.home.y + Math.sin(me.timer * 4) * 12; },
  touch()    { api.message('You found ' + me.def.name); },
  hit()      { api.particles(me.x, me.y, '#ff9a88'); }
};
```

### Registering a new trait from project code

In Game logic → JavaScript (or an imported mod), register a trait before the level loads. Its settings appear on every object that lists it, and exported games carry it.

```js
api.registerTrait({
  id: 'hop', label: 'Hop', group: 'motion', icon: '⤒',
  description: 'Hops upward on a timer.',
  params: { every: {type:'number', label:'Every', default:2, min:.2, max:10, step:.1, unit:'sec'},
            power: {type:'number', label:'Strength', default:150, min:20, max:400} },
  update(me, dt, api, settings) { me.hopIn = (me.hopIn ?? settings.every) - dt; if (me.hopIn <= 0) { me.vy = -settings.power; me.hopIn = settings.every; } },
  touch(me, api, settings) {}
});
return {};
```

Objects then use `{type:'hop', every:1.5, power:200}` in their trait list (add it through the ⚙ dialog's custom-trait fields or a project file).

## Levels, camera, and backdrops

Each level stores `width`, `height`, `tiles`, `backTiles`, `player`, `text`, and `music`. `project.background` holds `{style, color, color2, cycle, clouds, cloudSpeed, parallax, water, reflection, dim, image, imageFit, imageAlpha}`. `project.animations[id] = {fps, frames:[…]}` adds looping frames after `project.sprites[id]`; `player:walk` and `player:jump` clips replace the idle art while moving or airborne. `project.objectOverrides[id] = {name, traits, box}` re-traits a built-in or library object.

## Camera and screen UI

`project.camera` supports `mode` (`follow` or `fixed`), `followX`, `followY`, `zoom` (0.5–4), `smoothing` (0–20, zero snaps), `offsetX/Y`, `lookAhead`, `deadzoneX/Y` (screen pixels), `bounds`, and fixed focus `x/y` (world pixels). The editor map pan remains independent. `screen.js` is a self-contained factory embedded in playable snapshots.

`project.ui = {showDefaultHud: true, elements: []}` holds up to 64 screen elements. Types are `text`, `bar`, `hearts`, `panel`, and `button`. Every element has a stable `ui_` ID, name, anchor, x/y offsets, width/height, fontSize, color, background, opacity, visible, text, value/max, binding, and optional variable name. Anchors support top/bottom left/center/right and center. Right/bottom offsets are measured inward. Bindings include score, lives, time, remainingTime, level, wave, and variables. Text supports `{score}`, `{lives}`, `{time}`, `{level}`, `{wave}`, `{keys}`, and `{var:name}`.

Screen UI is rendered after effects, independent of world camera movement. Buttons have native keyboard-accessible overlays. Authored camera/UI settings reset when replaying; runtime block changes reset when scenes change.

New Camera and Screen UI block categories include follow, pan, zoom, response, axes, bounds, camera reads, UI click events, text, meter values, visibility, positioning, style, reads, and bindings. JavaScript hooks can use `api.cameraSet(property,value)`, `api.cameraPan(x,y,seconds)`, `api.cameraFollow()`, `api.cameraZoom(zoom,seconds)`, `api.ui(id,property,value)`, `api.getUI(id,property)`, and `onUI(id)`.

## Sprite and object format

A sprite is an array of 64 values in row-major order. Each value is a six-digit hex color or an empty string for transparency. All template and imported artwork uses this format. Legacy 16×16 projects are converted on import.

Custom objects use `{ id, name, tile, traits, box }`. `id` begins with `custom_`; `tile` is a unique integer from 8 to 899 (900+ is the built-in library). `box` is `{x,y,w,h}` in source pixels, entirely inside the 8×8 tile. Older projects with a single `behavior` word are expanded into traits on open.

## Visual game logic

Game logic is a main editor workspace beside World builder. It uses a local copy of Blockly 12.4.1, with Pockit blocks plus standard logic, loops, math, text, variables, and functions. Object-aware blocks cover touching any object, switches, breaking, spawning any object, per-object properties and patterns, counts, nearest objects, and the touched object. Event stacks are the entry points; disconnected action blocks do not run. The 88 searchable behavior starters are appended without replacing existing user logic.

`level.logic` stores `{version: 1, workspace, code}`; `project.logic` mirrors the active scene for the editor. `workspace` is Blockly JSON and is the source of truth. Imports validate its structure and rebuild the cached `code`; imported cached JavaScript is not trusted. Each scene supports up to 800 blocks. Each synchronous event has a fresh budget of 300,000 weighted operations. The 250 ms time watchdog applies only after 4,096 guard checks, so a few ordinary actions are not stopped because audio decoding, scene setup, garbage collection, or a browser pause took longer. Nested functions and broadcasts share both counters, with at most 128 nested calls. There is no lifetime operation limit. A runaway sequence stops with a visible, specific error instead of freezing the browser; use timers to distribute expensive repeated work across frames. Timers advance with game time and reset on replay.

Blocks cover game start, key presses, intervals, collection, damage, defeated enemies, levels, power-ups, win/lose, and broadcast messages. Actions control score, lives, player movement, settings, tiles, enemy spawns, powers, sound effects, particles, and post-processing. Functions support inputs and return values. Pockit game variables share the existing `api.variables`; standard Blockly variables are private to the visual program.

`blocks.js` defines the visual language and compiles it without executing it in the editor. `logic-runtime.js` runs the resulting program inside the isolated game preview/export. `block-editor.js` connects workspace editing to project save/undo. `pockit-renderer.js` gives blocks their custom file-tab headers, square connectors, and inset fields; it does not change their saved program format. The compiler library is only needed by the editor: exported games embed the small runtime and compiled program, with no Blockly dependency. Visual handlers and handwritten hooks both run; one does not overwrite the other. Implementation references: [Blockly JSON serialization](https://docs.blockly.com/guides/configure/serialization/) and [custom renderer connection shapes](https://docs.blockly.com/guides/create-custom-blocks/renderers/create-custom-renderers/connection-shapes/).

## Background music

`project.music` is `{src: '', name: '', volume: 0.35, loop: true}`. `src` is an embedded audio data URL. Each authored level has `music: {mode: 'inherit', src: '', name: '', volume: 0.35, loop: true}`; `mode` can also be `custom` or `silent`.

The game track continues across levels that inherit it. A custom track starts fresh when its level begins. A silent level pauses music. Playback starts after the player presses Play; win/loss and story transitions pause it. Replay starts the track again. With loop off, finishing a track leaves silence until a new track or replay. Generated rooms use level one's music override.

`music.js` validates embedded data and supplies the standalone `PockitMusic` player. `music-editor.js` handles upload, manual preview, volume, looping, and scope selection. Validation accepts the supported audio MIME types, limits each track to 8 MiB and all music and custom sound effects together to 20 MiB, and rejects network URLs. Project file imports allow up to 100 MiB. IndexedDB holds device drafts, subject to browser and disk quota; account media is stored separately from the project document. Downloadable JSON backups remain portable.

## Writing custom game logic

In Game logic → JavaScript, return an object of hooks. The code factory receives `api` and runs in the isolated game preview and exported game. It does not run inside the editor. Syntax errors are shown when applying; runtime errors appear below the game. Network access is disabled by the game page's Content Security Policy.

```js
let timer = 0;
return {
  onStart() { api.message('Find the little lights.'); },
  onUpdate(dt) {
    timer += dt;
    if (timer >= 5) {
      api.addScore(10);
      timer = 0;
    }
  },
  onCollect(kind) { api.message('Found: ' + kind); },
  onDraw(ctx) {
    ctx.fillStyle = '#fff3bc';
    ctx.fillRect(12, 12, 3, 3);
  }
};
```

Touch hooks fire once when contact begins, and can fire again after the player leaves and returns. For continuous behavior, use `onUpdate(dt)`. Native collision and contact traits still update each frame.

Hooks: `onStart()`, `onUpdate(dt)`, `onCollect(kind)`, `onTouch(kind, entity)`, `onHit()`, `onLevel(index)`, `onChapter(index)`, `onPowerup(kind)`, `onEnemyDefeated(kind)`, `onBreak(kind)`, `onSwitch(channel, on)`, `onWin()`, `onLose()`, `onDraw(ctx)`.

- `api.state`: live player, score, lives, tiles, `entities`, projectiles, `switches`, `keys`, `cam`, round, wave, and timers. `api.level` is `{width, height}` in tiles; `api.camera` and `api.touched` are live.
- `api.project`: current game configuration.
- `api.variables`: custom state preserved across levels/loops, reset on a new run.
- `api.keys`: current lower-case keyboard state (`arrowleft`, `w`, `' '`, `x`, `shift`).
- `api.addScore(number)`, `api.win()`, `api.lose()`, `api.message(text)`.
- `api.sound(event)` with jump/coin/hit/shoot/win.
- `api.particles(x,y,color)`, `api.spawn(kind,x,y)`, `api.objects(kind)`, `api.remove(entity)`, `api.setProperty(kind, prop, value)`, `api.registerTrait(def)`.
- `api.setTile(x,y,tile)` uses grid cell coordinates.
- `api.runtime`: direct advanced access to the engine. For example `shoot()`, `move(body,dx,dy)`, or `loadLevel(index)`.

Positions passed to spawn/move/draw use world units. `onDraw` receives the scene context already translated by the camera, so draw in world coordinates; the visible window is 512×288. This hook can draw a completely custom board or interface. Code can override the engine's methods through `api.runtime`; use the MIT source to introduce larger systems.

## Main files

- `editor.html`, `style.css`, `scene-editor.js`, `scene-editor.css`: editor shell, hierarchy and contextual Inspector.
- `app.js`, `studio.js`: editing workflows, project validation, catalogue, extension UI, and HTML export.
- `traits.js`: the trait registry and settings schema.
- `engine.js`: movement, collision, entities and trait behavior, camera, scoring, levels, and game lifecycle.
- `effects.js`: GPU post-process shader plus simplified Canvas fallback.
- `effects-ui.js`: effect control definitions and looks.
- `data.js`: project schema, seeded generation, rendering, migration.
- `scene.js`: placed-instance metadata, grouping, movement inheritance and validation.
- `sprites.js`: native eight-pixel art packs.
- `tilesets.js`: parsed user-supplied tilesheet data.
- `recipes.js`: the 50 templates, their text maps, and the map legend.

## Research sources

These sources informed the workflow and customization model. The catalogue is curated; it is not an empirically ranked top 50. Trait design follows GDevelop's behaviors model.

- Steam tags: https://partner.steamgames.com/doc/store/tags
- itch.io browsing categories: https://itch.io/games
- GB Studio scene types: https://www.gbstudio.dev/docs/project-editor/scenes/types/
- GDevelop platform movement: https://wiki.gdevelop.io/gdevelop5/behaviors/platformer/
- GDevelop events: https://wiki.gdevelop.io/gdevelop5/events/
- GDevelop dialogue: https://wiki.gdevelop.io/gdevelop5/all-features/dialogue-tree/
- MakeCode tilemaps: https://arcade.makecode.com/reference/tiles/tilemap
- Construct behavior reference: https://www.construct.net/en/make-games/manuals/construct-3/behavior-reference
- Godot 2D movement: https://docs.godotengine.org/en/stable/tutorials/2d/2d_movement.html
- Unity post-processing overview: https://docs.unity3d.com/560/Documentation/Manual/PostProcessingOverview.html

## License and artwork

Original engine code and original native sprite packs in this project are MIT licensed; see LICENSE. User-supplied tilesheets retain their original authorship and licensing. Minimal Future is credited to s4m_ur4i. The second woodland sheet was supplied by the user without license metadata. The engine license does not relicense those supplied images. The watermarked bird reference is retained only in the local references folder; it is not used in the engine or included in source.zip. DM Sans and Space Mono are bundled locally under their SIL Open Font Licenses in assets/fonts/.

The visual editor vendors Blockly under Apache-2.0; its license and provenance are in vendor/blockly/. The original Pockit code remains MIT licensed.

## Scene hierarchy and per-copy properties

`scene.js` augments each authored level with `scene: {entries, groups}`. Entries reference a tile cell/layer, retain a stable identity when moved, and optionally override its name, traits and 8×8 collision box. Groups have a name, world-pixel position and optional parent ID. The hierarchy includes every placed copy, camera, backdrop, player, UI and effects. Parent movement is translation only; rotation and scale inheritance are not implemented. Sprites and sprite groups move freely in world pixels, including fractional positions. Moving a legacy sprite detaches it from the tile map while preserving its identity, traits, and parent. Ground and tileset brushes remain on the 16-world-pixel tile grid and refuse occupied destination cells. Mixed selections containing terrain also snap to that grid. Children keep their world positions on reparenting. Runtime children retain their own behavior while inheriting the parent's displacement, including nested groups. UI remains in screen space and is not parented to world sprites. Procedurally generated levels use their generated maps rather than an authored hierarchy.

Per-copy traits are promoted to live entities, subject to the 4,096 live-entity limit. Sprite images/animations remain shared by object type. An emitter's optional `sceneParent` follows one exact authored sprite or group; this differs from `attach: 'object'`, which follows every instance of a sprite type. Parent-specific effects are scoped to the current authored level. Erasing a sprite removes its instance metadata; children are detached rather than reassigned to an unrelated future tile.

## Lights and adaptive shadows

Select Camera for global `effects.shadows` (0–100), `shadowLength` (0–48 world pixels), `shadowAngle` (−180–180 degrees), `shadowSoftness` (0–100), and `ambientDarkness` (0–90). Angle 0 points right and 90 points down. Shadows are opt-in to preserve existing game looks. Platformers use projected terrain and floor-contact shadows; top-down/shooter scenes use grounded silhouettes. The master post-processing switch disables both lighting and shadows.

The Light particle shape provides `lightRadius` (8–256), `lightIntensity` (0–2), `lightSoftness` (0–100), `lightCore` (0–50), `lightFlicker` (0–100), `lightPulse` (0–100), and boolean `lightShadows`. Steady lights use the first palette color without accumulating particles. Burst lights fade over their lifetime. Solid collision rectangles and closed doors occlude point lights; decorative backgrounds do not. Up to 24 visible lights and 8,192 nearby occluder rectangles are processed per frame. These are pixel-resolution 2D masks, not normal-map or physically based lighting. Screen UI is composited afterward to stay readable.

Particle blocks can change those light properties during play, while Effects blocks control the ambient darkness and adaptive shadows. For example: `api.particleSet('fx_lantern', 'lightRadius', 120)`.

Research: [Celeste lighting implementation by Noel Berry](https://noelfb.com/posts/celeste_lighting/index.html), [Godot 2D lights and shadows](https://docs.godotengine.org/en/stable/tutorials/2d/2d_lights_and_shadows.html), [Unity parenting](https://docs.unity.com/en-us/engine/6000.7/manual/unity-editor/editor-windows-views-reference/hierarchy-window/hierarchy-parent-child), and [GDevelop's advanced jump example](https://gdevelop.io/game-example/free/coyote-time).

## Particle controls

Particles are visual effects; they do not cause damage or collide with the level. Combine them with object traits or blocks for gameplay effects such as a damaging fire or an explosion that breaks crates.

`project.particleEmitters` has no fixed limit on the number of named effects (the project storage limit still applies). Start with light, fire, smoke, rain, snow, explosion, sparks, dust, footsteps, magic, trail, bubbles, or confetti. Each can appear in all levels (`level: -1`) or one zero-based level index. Attach to a world position, the player, up to 32 live/static instances of an object type, or a weather region following the camera. Coordinates are world pixels; player/object coordinates act as offsets. Weather source width and height are screen pixels, adjusted for camera zoom. Moving and grounded triggers emit only while the attached object moves; grounded also requires contact with the ground in platformers, while top-down actors are treated as on the floor.

The visual controls cover continuous or one-shot emission, rate and burst amount, particle lifetime, direction and spread, speed, gravity, wind, randomness, size over life, a color palette, fade, additive glow, emission area, local coordinates, and square/circle/spark/custom sprite shapes. A custom particle sprite uses an existing project sprite. Each effect permits up to 256 live particles; the runtime caps the total at 1,024. Emission stops at that budget instead of growing without bounds.

Blocks and advanced code can use the effect's ID (for example `fx_fire`):

```js
api.particleStart('fx_fire');
api.particleStop('fx_fire');       // existing particles finish naturally
api.particleStop('fx_fire', true); // also clear existing particles
api.particleEmit('fx_blast', 40, 160, 96);
api.particleSet('fx_fire', 'rate', 30);
api.particleGet('fx_fire', 'rate');
```

Runtime changes carry between levels but reset when replaying the game. `api.particles(x, y, color)` remains available for a small, simple legacy burst.

Design references: [Godot 2D particles](https://docs.godotengine.org/en/stable/tutorials/2d/particle_systems_2d.html) and [Construct particles](https://www.construct.net/en/make-games/manuals/construct-3/plugin-reference/particles). Pockit's implementation uses its own compact Canvas 2D runtime.


### Imported tilesets and event animations

Use **Tilesets: Import tileset** for PNG, WebP, or JPEG (16 MB maximum, up to 4,194,304 source pixels and 8,192 pixels per side). Any image dimensions work: the importer slices left to right, then down, without rescaling or cropping; partial edge tiles are padded transparently. Alpha below 128 becomes transparent; other pixels retain their RGB color. Empty squares are skipped in the picker. Up to 15 imported sheets / 65,536 source tiles fit in one project, with tiles becoming custom objects when first used (no fixed object-count cap). A sheet overview and 256-tile pages keep large sheets navigable. The existing project/media size budgets also apply. Source pixel data lives in `project.importedTilesets` and travels with project backups and cloud saves.

Each tile can be scenery, solid, one-way, hazardous, or collectible. Drag a rectangle on the preview, type its 8×8 pixel bounds, fit visible pixels, or apply the current box/behavior to the sheet. One-way collision applies to downward platformer movement. Painting background tiles always makes them decorative. Imported tiles become ordinary custom objects, so Object Studio and the per-copy Inspector can add more traits. Editing sheet behavior updates that tile’s shared traits; it leaves per-copy overrides independent. Renaming a sheet preserves traits added later.

Pixel Studio has a named animation selector on every sprite, not only the player. Add up to 16 clips per sprite (600 saved clips across the project), 64 frames per clip and 1–24 fps. Choose **Import sprite sheet**, or drop an image into Pixel Studio. The importer suggests a square frame grid and groups each nonempty row into an animation. Preview the 8×8 conversion, edit clip names and speeds, and choose which clips to use. Suggested names are editable, not semantic recognition of actions. Adjust frame dimensions, spacing, margins, row/column grouping, and transparent color as needed. Select “All frames in current clip” to import a strip into one animation. Empty cells are skipped by default; disable that option to retain them. Frames are converted to 8×8 with nearest-neighbor scaling and consistent padding. PNG/WebP/JPEG imports allow 8 MB and 4,194,304 source pixels, with up to 4,096 source cells per grid. Applying a sheet changes the current draft; **Save sprite** commits it. Matching names replace only those clips. `idle` loops automatically. Player `walk` and `jump` select themselves during movement. Other names start on demand.

In **Game logic: Animation & fades**, play a clip on the player, every copy of an object type, or the touched live object. Choose loop, once then default, or once and hold. A second block restores normal animation. A missing clip makes no change. Trigger these from an event, not every frame, because repeated play calls restart the animation.

```js
return {
  onHit() { api.playAnimation('player', 'hurt', 'once'); },
  onSwitch() { api.playAnimation('door', 'open', 'hold'); },
  onTouch(kind, object) {
    if (kind === 'enemy' && !object.touching)
      api.playAnimation(object, 'surprised', 'once');
  }
};
// api.stopAnimation('player');  // Restore automatic idle/walk/jump.
```

`api.playAnimation(target, clip, mode)` accepts `'player'`, an object kind ID, or a live entity reference. It returns whether the clip was found and applied. Kind-wide playback works on static terrain and decorative background tiles too; entity references animate only that copy. Playback resets between levels/replays, uses game time, and never changes collision bounds. This is frame animation, not skeletal animation or an animation state graph.


### Custom sound effects

Open **Sounds & music · Sound library**. Import multiple MP3, WAV, OGG, M4A/MP4, AAC or WebM files. Rename, search, preview, tune volume and pitch, or loop each recording. There is no sound-count cap: the shared decoded audio budget is 20 MiB per project, with 8 MiB per file. Sounds are embedded in backups and published games. Browser codec support still applies.

`project.soundEffects` is an optional array of `{id, name, src, volume, rate, loop}`. IDs begin `sfx_` and stay stable after renaming; volume is 0–1, rate .25–4, and src is a base64 audio data URL. Standard `project.sounds` event slots can reference these IDs. Removed event assignments become Silent; blocks retain a visible missing sound ID.

```js
api.playSound('sfx_door', {volume: .7, rate: 1.1, loop: false});
api.stopSound('sfx_door');
api.stopSound(); // stop every imported sound
api.sound('jump'); // existing event assignment, including a custom recording
```

Call volume multiplies asset volume and the game effects volume. Call pitch multiplies asset pitch; the result is clamped .25–4 and changes pitch and duration together. Omitting looping uses the asset default; the play-recording block explicitly chooses once or loop. A looping sound has one voice per ID. Recordings use cached Web Audio buffers, short click-preventing fades, and a shared compression bus. At most 24 voices play concurrently, with up to four overlapping copies of one recording; excess calls are skipped without cutting off playing tails. Repeated calls to one sound within 40 ms are ignored. Overlapping copies are attenuated for mix headroom; pending first-decode requests never build up a delayed burst. Older browsers fall back to media elements. Library size is unaffected. Ended or failed voices release their sources. Blur, level changes, reset and stop release imported sounds; an ending chime may follow completion cleanup.

The **Sound & messages** blocks offer play recording (volume, pitch, once/loop) and stop recording (one/all).

### Additional art and feedback hooks

`onBackground(ctx)` draws at 512 × 288 screen coordinates after the built-in backdrop and before world tiles, lighting and post-processing. Use `api.camera.x` for parallax. Canvas save/restore surrounds the hook. `onDraw(ctx)` remains a world-space foreground hook. These hooks run only when custom code is enabled.

`onCheckpoint(entity)`, `onRefill(entity)`, and `onBounce(entity)` run after the corresponding trait activates. They are useful for custom chimes, animation and particles. `api.state.player.climbing` indicates active wall gripping.

### Mouse aiming and shooting

For a top-down or shooter game, open **Game rules · Combat & power-ups · Mouse controls**. Enable **Aim toward the mouse** and **Left-click to shoot**; **Player can shoot** must also be on for automatic fire. Hold the left button to repeat at the configured shots-per-second rate. Keyboard movement and Space/X shooting remain available. Existing games keep both mouse options off until enabled.

`config.mouseAim` and `config.mouseShoot` are independent booleans. Mouse aiming affects built-in and block-fired player shots in top-down/shooter games; movement stays on the keyboard. The pointer uses a crosshair while mouse aiming is active. Touch movement/action buttons retain their existing behavior.

`api.mouse` returns a snapshot `{x, y, screenX, screenY, inside, down}`. World coordinates account for camera position and zoom, including a camera moving beneath a stationary pointer. Screen coordinates use the logical 512 × 288 viewport, with fullscreen letterboxing excluded. Check `inside` before using a pointer position. `down` tracks the primary button and clears on release, cancellation, leaving the canvas, blur, level changes, restart and stop.

```js
return {
  onMouseDown(mouse) {
    // For custom firing, leave automatic Left-click to shoot off.
    api.shootAt(mouse.x, mouse.y);
  },
  onUpdate() {
    const mouse = api.mouse;
    if (mouse.inside) api.variables.cursorX = mouse.x;
  }
};
```

`api.shootAt(x,y)` fires once toward a world position using the current projectile settings. Like the existing Shoot block, it is independent of the automatic firing toggle and cooldown; use a timer/cooldown for a custom fire rate. The **Mouse** block category includes a left-click event, screen/world position values, held/inside checks, and Shoot toward x/y. Screen button clicks, touch events, non-primary clicks and clicks in letterbox margins do not trigger gameplay mouse clicks or automatic firing.

### Connected world passages

The `passage` sprite trait travels to an authored scene with `level` (1–20, one-based), `x` (arrival column) and `y` (arrival row). The World block **visit level at column / row** and `api.travelTo(level, column, row)` do the same. Every entry rebuilds that scene from its saved design; live sprites, variables, keys, score, power-ups, modified settings, and UI do not travel with the player. Arrival becomes the new checkpoint; a one-second cooldown prevents immediate portal bouncing. Random/endless generated modes do not accept authored passages.

Screen UI text automatically wraps at word boundaries within its box; explicit line breaks are retained. Content remains clipped to its widget rectangle.


## Publishing and developer workspace

My games provides project search, draft/published/changed filters, duplicate projects, JSON backups, play-link copying, unpublishing and existing revision-checked deletion. Maker profile editing changes only display name and workspace bio; usernames and authentication are unchanged.

Publish opens Details, Appearance and Extras with a live responsive preview. `project.pageSettings` holds a private page draft. Saving it uses the same account revision protections as the game. Publishing sanitizes this presentation into inert JSON (`pockit-page-settings`) alongside the isolated game snapshot; no schema migration or arbitrary creator HTML/CSS is required. Existing publications without this metadata keep their original page.

Page controls include four colors, bundled type styles, two layouts, a tiled/cover background, banner, cover, three screenshots, six tags, status, version, description, controls, credits and three custom text sections. Raster uploads (PNG/JPEG/WebP) are resized and limited to 1.5 MB encoded per image. Creator text is always escaped, and images must be embedded raster data; page customization never runs author code in the host site.


## Conversations, scenes, and cutscenes

`story-system.js` contains the shared `createPockitStory()` factory. The identical runtime is embedded in new publications. `story-editor.js` supplies the Story section of Add to world and a conversation section in each placed sprite’s Inspector.

Conversations are stored in `project.conversations` (up to 100 conversations, 100 pages each, four choices per page). Pages have stable IDs, speaker, sprite portrait, expression caption, text, a one-shot sound, optional shared-value threshold, next page, and choices. A choice may set a shared value and jump to a page or end. No rich HTML is executed. Conditions use the same numeric scene values as Game values blocks; these reset between scene visits. For cross-scene conditions, use Game memory blocks to choose when to start the conversation. A missing qualifying page ends the conversation. Holding a key never advances more than one action. Conversations advance on E, Enter, Space, click, or tap; the first press completes unfinished typewriter text. Repetition is once per run or repeatable. By default the world pauses. Sprite conversations use the `conversation` trait (conversation ID and talk radius), assigned automatically by the friendly Inspector. Full eight-trait instances can use the dedicated conversation slot in `level.scene.entries[id].conversation`; a nearby prompt selects the nearest eligible sprite within 44 world pixels. Existing sign/message blocks retain their older behavior; they are not automatically converted into conversations.

Each level now has a stable `id` and `kind` (`gameplay` or `menu`). New menu presets: title, level-select, game-over, ending, credits, and pause. Menu elements remain ordinary Screen UI elements, scoped by their `scene` field; unscoped HUD elements appear in gameplay. Panel widgets may display an `artwork` sprite. Buttons can start a game, continue the current run, resume a pause, change scene by ID, open sound settings, or restart. A newly created title becomes the opening screen until changed in Manage; ending and game-over presets appear on win/loss. A pause is an overlay over the live world, not a reloaded level. Escape pauses/resumes. Continue preserves an in-memory run when returning to a menu; it is not a persistent player save slot and does not survive a page reload. Credits text can scroll; their soundtrack uses existing per-scene music controls and loop settings. Level-select presets initially link to up to four existing gameplay scenes; add more buttons with Screen UI if needed.

`project.cutscenes` holds up to 60 sequences with up to 100 steps each. Supported steps: wait, character movement, camera pan, conversation, animation, screen fade, scene change, and sound. Durations are 0–120 seconds; movement coordinates are world pixels (16 per tile). Set `level.cutscene` to play a sequence on entry, or use a Story block. Movement is authored cinematic positioning, not collision/pathfinding. Missing actors safely skip movement. Move steps accept optional `moveAnimation` (a clip name such as `walk`, or empty to keep current playback). The selected clip loops only while that step moves, then restores the previous playback or normal automatic animation. Finishing, skipping, and resetting all clean up the temporary playback. Cutscenes default to `pause: true` (also the behavior when omitted). With `pause: false`, ordinary simulation, player input, enemies, timers, and particles keep running. A scripted Move step owns only its selected actor until the step ends; the camera stays controlled by the sequence. A conversation with `pause: true` temporarily pauses even a non-pausing cutscene. A paused cutscene keeps gameplay frozen throughout its dialogue. Cutscenes preserve animation time, wait for dialogue input, restore camera/control on finish, and optionally allow skipping. Skip applies remaining movement destinations and the first scene transition; it does not invent dialogue choices, rewards, or gameplay variable changes.

JavaScript: `api.conversation(id)`, `api.cutscene(id)`, `api.scene(id)`. Events: `onConversationStarted(id)`, `onChoiceSelected(conversationId, choiceId)`, `onConversationFinished(id)`, `onCutsceneStarted(id)`, `onCutsceneFinished(id, skipped)`. The Game logic Story category exposes conversations, choices, cutscenes, and scene transitions. Project JSON and new public snapshots include all authored data. Existing public snapshots stay unchanged until republished.

The Idea Machine offers 40 game cards, 40 worlds, and 40 twists (64,000 combinations), in three simple cards, with lock/reroll and a browser-local idea drawer. Its suggestions are creative prompts, not generated or guaranteed built-in game mechanics.

### Conversation appearance, menu images, and scene order

A conversation’s optional `appearance` stores width (35–96% of the viewport), box height (0–90%, 0 fits content up to 45% of the viewport), position (`top`, `center`, `bottom`), font (`pixel`, `clean`, `serif`), fontSize (8–28), portraitSize (8–128), padding (0–32), opacity (0–100), radius (0–32), borderWidth (0–6), shadow, portraitTransparent, and avoidActors booleans, and six-digit hex colors for background, text, accent, border, button, buttonText, and portraitBackground. The same renderer drives the editor preview and published conversations. Height is a real box height, not a minimum. Long text scrolls in its own focusable area, with answers below; oversized answer lists can also scroll. Text, portrait, and spacing values scale from a 512-pixel-wide viewport. New conversations use 12px text, 24px portraits and 8px padding. Make compact sets 88% width, 30% height, 10px text, a 16px portrait, and 6px padding without changing colors or dialogue.

`project.startScene` is a stable scene ID. Existing projects migrate to their title scene, or first scene if there is no title. Manage offers an explicit opening scene and Earlier/Later ordering. Reordering preserves the active editor scene, stable-ID links, native Passage destinations, emitter level assignments, and literal numeric travel-block destinations. Calculated level numbers and custom JavaScript are not rewritten. Pause overlays are excluded from opening-scene selection.

Screen panels accept an embedded `image` (PNG, JPEG, WebP; up to 2 MB per uploaded file and 4096 pixels per side) and `imageFit` (`cover`, `contain`, `stretch`). Add to world, Image creates a scene-specific screen-space panel, adjustable with the existing Screen UI positioning and opacity controls. There are still 64 screen elements per project. The image data travels with the project and published snapshot; no external image URL is fetched. `menuBackground: true` identifies a menu backdrop. A pause scene’s `pauseBlur` (0–16, default 3 on new pause presets) blurs the paused world before sharp menu UI is rendered; the backdrop’s opacity controls its tint independently.

The cutscene maker can assign its saved sequence directly to a scene’s `cutscene` entry trigger. Other triggers use the existing Story Play cutscene action beneath an event. Scene-entry triggers run on each entry; one-shot interactions use a shared value guard.


### Cutscene camera focus and zoom

Camera steps accept optional `focus` (`position`, `target`, `current`; old steps default to `position`), `target` (`player` or a stable placed sprite ID), `offsetX` / `offsetY` (world pixels, −16000 to 16000), `bounds` (optional boolean; defaults to the original camera), and `zoom` (0 to retain current zoom, or 0.5–4). `x` / `y` remain world-center coordinates for `position`. `current` preserves framing while changing zoom. Camera movement and zoom use smooth interpolation over the step’s duration, including durations above 30 seconds. A target stays centered through subsequent movement/wait steps until another camera step replaces it. Missing targets retain the current framing safely. Level bounds constrain framing unless Keep camera inside level is turned off for that shot. Completion and skip restore the camera settings from before the cutscene.

The main creation entry is **Add to world**, grouped into Sprites, Screen UI, Effects, and Story. Story creates conversations (with a speaker picker when no placed sprite is selected), cutscenes, and gameplay/menu scenes. Saved conversations and cutscenes open the Story library. Inspector shortcuts edit the same saved objects; there is no separate Story tools toolbar. The picker closes with its Close button, Escape, an outside click, or an item selection.

Cutscenes accept optional `letterbox` (boolean, default false), `barSize` (2–25, percent of the game viewport for each black bar; default 12), and `barFade` (0–3 seconds for each fade; default 0.4). Cinematic bars overlay the game without resizing its canvas or changing zoom. They hold while dialogue waits for input and fade out on completion or skip; restart and scene changes clear them. Dialogue and skip controls remain above the bars. Older projects retain their existing framing.

Story editor saves commit a cutscene and its scene-entry trigger together, then await the account save before confirming success. Pending saves disable repeated submission. Failures preserve authored work, show an always-visible error beside Save, and offer retry. Validation errors identify missing step selections. Confirmation distinguishes device storage from account storage and reminds creators to republish arcade snapshots.

### Proximity and collision triggers
Events now include `pockit_near` (source sprite, target sprite, distance 0–2048 world pixels) and `pockit_collision` (source/target collision boxes touching or overlapping). Sprite kinds match any live copy; the player is available on either side, and a target may be Any object. Static world tiles are included, background art is excluded, and a sprite never matches itself. Distance uses collision-box centers; 16 world pixels equal one tile. Collision events detect contact; they do not add physical object-to-object collision response or swept projectile detection.

Both default to once per scene visit. The repeat option fires on entry again after all matching pairs separate; neither fires every frame. A new game resets once-only triggers. Each repeating event rearms on scene entry. Runs are paused during conversations/cutscenes. Up to 64 spatial events can be registered: `api.whenNear(source, target, distance, 'once'|'enter', callback)` and `api.whenTouching(source, target, 'once'|'enter', callback)`. These are visual-logic API methods. The target is available through Touched object actions while the callback runs.

Select any placed sprite, then Inspector, Story interaction to choose None, Conversation, or Cutscene. The per-copy `cutscene` trait stores the sequence ID, `trigger` (`interact`, `near`, or `touch`), `radius` (0–2048 world pixels), and `repeat` (default false). Interaction offers E/tap; automatic triggers require no blocks. Repeating automatic triggers require leaving and returning, while manual interaction can be requested again. Conversation and cutscene selections replace each other for that placed copy; other traits are preserved. The builder can create a cutscene directly from the selected sprite and attach it when saved.

### Cinematic sprite conversations

The Inspector supports Conversation, Cutscene, or Cutscene + conversation on each placed sprite. Both traits may coexist. A conversation trait can set `cinematic` (default false), `barSize` (2–25 percent, default 12), and `barFade` (0–3 seconds, default 0.4). These settings belong to that copy, independently of shared dialogue pages. Bars fade without resizing the viewport or changing zoom.

For a sprite with both traits, its cutscene runs first and automatically opens the attached conversation at the end, keeping the cutscene camera and gameplay pause through dialogue. An attached conversation already started in that sequence is not played again. Conversation requirements and repeat settings still apply. Finishing restores the normal camera; skipping or changing scene cancels pending dialogue. Once the intro is used, normal interaction can replay an eligible conversation. Existing proximity/collision blocks and their behavior are unchanged.


### World organization and streamlined creation

New game offers Blank plus ten curated starter recipes. The original 50-recipe bank remains in source for compatibility. Blank projects use `rules.win: "none"`, `rules.points: "none"`, `rules.lose: "none"`, and no built-in HUD; blocks control their objectives, scoring, and endings. Existing project rules remain unchanged. Rule and world-mode form controls are removed; Player Inspector exposes movement type directly.

The World tree supports Shift ranges, Command/Ctrl toggles, multi-object drag/reorder/parenting, atomic world movement, grouping, deletion, undo, and double-click/F to frame a selection in the editor. Optional `level.scene.order` stores unique row IDs in display order; it never changes rendering order or gameplay positions. Screen UI stays under Screen UI. Player, Camera, Backdrop, and Screen UI remain at the root. Sprite/group parenting rejects cycles. `PockitScene.moveMany(project, ids, dx, dy)` moves the union of selected world objects and descendants exactly once, or rejects the entire move if blocked.

Conversation presentation reserves the full page’s text height while typing. Cinematic dialogue stays inside the clear area between letterbox bars. `appearance.avoidActors` defaults to true: top/bottom placement can switch sides at a page or viewport change to avoid the player and interacting sprite; it never changes the game camera or zoom. Set it false to keep authored placement (center always stays centered). Small displays retain readable default text and larger continuation buttons; portrait layouts use unused space below the game when available. Existing arcade snapshots receive presentation-only fixes when opened, without changing their saved project or branching logic.

### Screen videos

Create a **Video scene** when a clip should replace a hand-authored cutscene. It starts with one scene-bound video at the exact 512 × 288 game-screen rectangle and hides ordinary gameplay chrome. Select the video, upload an MP4 (H.264 recommended) or WebM, and use **Make full screen** at any time to restore the top-left anchor, zero offsets, full dimensions, opaque black backdrop, and fill/crop fit. You can also use **Add to world · Screen UI · Video** inside an existing scene; videos added this way belong to that scene.

Clips are embedded in project saves and published games, up to 8 MB per file within the 50 MB project-storage limit (published downloads are compressed automatically). Choose **Play once** (holds the last frame) or **Loop**, fit/crop/stretch, optional sound, position, size and opacity. A still preview appears in the editor; Playtest runs the video. Existing Screen UI visibility blocks pause hidden clips and restart them when shown. Scene changes and restart reset playback; stopping play releases video resources. Hidden browser tabs pause videos. Sound is off by default for ordinary video elements and on by default in the Video scene preset; browsers may require a player interaction before audible playback.


### Scene-local settings and scripts

`sceneLogicVersion: 1` gives each level its own `logic` workspace and `code` hooks. New scenes start empty; duplicating a scene retains its logic. Legacy global logic is copied into existing scenes on upgrade. The editor mirrors the selected scene at `project.logic` and `project.code`; `storeCurrentLevel` commits these before switching, saving, or exporting. Runtime transitions disable old block timers/listeners and initialize the destination scene's code. Named shared values are shared only by scripts within the current scene visit, and reset on every scene change.

`sceneSettingsVersion: 1` stores `level.settings` with movement `type`, `theme`, `config`, `camera`, `background`, `effects`, `rules`, `sounds`, and `showDefaultHud`. These are independently cloned and restored on scene selection and runtime transitions. New scenes use fresh default settings for the selected movement type; duplicating explicitly copies the source design. Sprite images, object-type definitions, animations, story libraries, and music assets remain shared; music and Screen UI retain their explicit scene-scope controls.

### Durable saves and media storage

`device-store.js` writes atomic data/metadata records to IndexedDB. Legacy localStorage drafts are removed only after a successful migration. Device quota failures never cancel account saving. The UI distinguishes confirmed account saves from device backups and provides a JSON download while a save is failing.

`cloud-media.js` extracts embedded image/audio/video data over 4 KB into immutable, SHA-256-addressed private Storage objects. Only their references go into the database. Editable loads hydrate them back to portable data URIs; a missing asset stops loading with an error instead of overwriting the project. Limits: 50 MiB per project (stored JSON data with media replaced by null, plus each unique decoded media asset once), 12 MiB per cloud asset (individual upload tools may be stricter), 8 MiB compact game document, and 50 MiB per published download after compression. The subtle Storage meter in the account toolbar shows used and remaining space; open it for a breakdown. Oversize edits cannot overwrite account saves or be published, but remain in the editor and its device recovery backup so creators can remove content, undo, or download their work. Existing large games can still open for cleanup. The 100 MiB backup-file parsing ceiling is separate from the deduplicated project budget. Server enforcement is in `supabase/project-storage-limit.sql`; it verifies reference sizes against private Storage metadata rather than trusting client byte counts. Published exports deduplicate shared media across scenes and use gzip for large downloads. Saving prunes branches without media before walking the document, so tile and pixel arrays never become millions of temporary SQL rows. Published HTML lives in private Storage; an anonymous read policy permits only the release currently referenced by a public publication. Older inline publications continue to work.

`pockit_project_write_v2` locks each draft, checks ownership and revision, and uses a client operation ID to make retrying a timed-out save safe. It retains the five previous revisions from saves made through this new path. Save history downloads a revision without replacing the editor. Old open editors still use the compatible original RPC. Assets are immutable and retained for recovery; this release does not garbage-collect unused uploads or implement per-account billing quotas.

### Multi-tile sprite sheets

`project.spriteGroups` stores up to 128 reusable stamps: `{id, name, width, height, collision, parts: [{x,y,sprite}]}`. Import selection uses 8 × 8 cells, a maximum 32 × 32-cell extent and 128 visible cells per stamp. Internal parts are ordinary custom sprite definitions, hidden from the regular palette; there is no fixed custom sprite type count cap, and the eight imported sheets limit remains. Reusing a sheet cell reuses its definition.

Placement creates one named hierarchy group and its children atomically, preserving transparency and rejecting out-of-scene cells. New placements use independent scene entries (`loose: true`) and can overlap painted tiles without replacing them. Moving or deleting an overlaid sprite leaves the tile map intact. Repeated placement of the same stamp at the same origin is ignored. Group metadata includes `stamp`, `width`, and `height` for whole-object selection. The default is decoration; solid-base and whole-shape collision presets set per-copy traits. Expand a group for individual 8 × 8 collision boxes. Selection, translation, erasing, deletion, and runtime parenting preserve the complete object. Grouped parts count toward the scene's 4,000 authored-part budget; placement warns before exceeding the supported budget. Whole sprites can have multiple animation frames; animated 8 × 8 sprite imports are also available in Pixel Studio.


### Sprite overlays and scene playtesting

Sprites chosen from Add to world and imported whole-sprite sheets are independent scene entries, drawn above the selected painted tile layer. Ordinary Ground and Tilesets brushes continue to paint the tile map. A loose entry retains the existing `cell`, `layer`, `tile`, traits, collision box, and parent fields, so selection, grouping, save/load, scene resizing, and undo use the same hierarchy. Runtime loose sprites spawn independently of the terrain tile at that coordinate, with `cell: -1` so changing a terrain tile through blocks does not destroy them. Runtime collisions still use each sprite's traits and box. Repeated brush events in one stroke do not create stacked duplicates or erase through a removed group into its floor.

Playtest embeds the active scene index as a preview-only runtime option. Initial play and Restart begin in that scene, including menu scenes. Publishing continues to use the authored opening scene. Previewing does not modify the project's `startScene`.

Free sprite positioning: loose scene entries may store `x` and `y` in world pixels; older entries without these fields use their cell position. `cell` remains the containing tile index for compatibility. Player spawn coordinates may be fractional tile units. Dragging is continuous; arrow keys nudge one world pixel, Shift+arrows eight. Inspector X/Y accepts decimal positions. Save/load, runtime collision and scene parenting preserve those positions.

Whole-sprite animation import: select a frame rectangle, queue up to 64 same-sized frames manually or slice the row to its right. Reorder/remove queued frames and preview at 1–24 fps. Transparent margins remain aligned. Each occupied cell across all frames uses an independent custom sprite type with a synchronized idle clip, so existing rendering, collisions, saves and exports work unchanged. Animated parts are not shared with other templates. A sheet can produce many individually named groups without closing the import window. Group metadata includes optional `frameCount` and `fps`.

Group traits: `scene.groups[].traits` and optional 8×8 `box` apply to all descendant sprite entries at runtime. The child starts with its own/shared traits; ancestors override matching trait types, root first, nearest group last. Other child traits remain. Reparenting automatically changes inheritance; removing the group trait exposes the child’s own setting again. Back-layer objects stay decorative. Group boxes are per child, not a combined collision hull.

Uploaded asset deletion: sprite cards delete custom types and their placements/animation clips; whole-sprite cards delete the reusable group and placed copies while retaining shared parts used by other templates. Imported tileset deletion removes its tile-brush types and placements but retains whole sprites extracted from the sheet. Recording deletion clears built-in sound assignments in every scene. Operations are undoable; named references in custom blocks/scripts remain for the author to repair.


### Scene isolation

`sceneIsolationVersion: 1` scopes each screen element to `element.scene` and each emitter/light to its numeric `level`. Legacy unscoped elements are assigned to the first scene whose logic references their ID, falling back to the first gameplay scene; newly added elements belong to the active scene. There is no all-scenes option. A scene change restores authored UI visibility, camera, movement, backdrop, post-processing, sound settings, and emitter settings. Only destination emitters are instantiated. Dialogue, fades, cinematic bars, video, sounds, particles, animation state, button state, variables, pending block actions, and old hooks are cleared. Destination UI is initialized before its startup scripts run.

Advanced scripts' ordinary `setTimeout` / `setInterval` callbacks are owned by their scene and cancelled on leaving or stopping. Stale scene APIs cannot mutate a later scene. This lifecycle management is not a security sandbox for arbitrary JavaScript using browser globals. Asset libraries (sprite art, animation definitions, conversations, sequences and recordings) remain reusable; scene instances and running state do not carry over. Whole-game music remains an explicit authored music choice, with playback restarted at scene entry. Pause is an overlay and preserves the current scene; changing to a menu is an actual scene change, and Continue opens a fresh gameplay scene. Duplicating a scene copies its UI/effects and remaps block references.


Editor scene ownership: Particle Studio lists and edits only emitters belonging to the active scene. Removing its last effect leaves an empty scene panel; it never selects an effect from another scene. Mounted Inspector controls reject foreign-scene or stale selections. Hierarchy deletion and group-child movement explicitly check scene ownership for UI and emitters. Other scenes' attachments cannot influence runtime parent discovery. UI's explicit Move to scene action refreshes the Inspector immediately. Reusable asset-library deletion remains a separate project-level operation.


Imported sprite sheet removal: Sprites → Imported sprite sheets exposes Open and Delete sheet. The whole-sprite importer offers the same Delete sheet control for its selected saved source. This removes only the imported source record and detaches `importedSheet` / `importedIndex` metadata from created sprite types. Saved pixels, animation clips, collision/trait settings, reusable groups, tile-brush identity, and all scene placements remain intact. Cancel is non-mutating; Undo restores the source and metadata. The existing Tiles tab's Delete tileset action explicitly removes its painted tiles, and remains separate.

Whole sprites also appear under **Add to world → Sprites → Whole sprites**, with full thumbnails and name search. Selecting one uses the same atomic whole-object placement as the Sprites strip. Scene placement counts each interactive map cell once; a group’s 8 × 8 parts each count as one sprite. Offscreen loose artwork is culled in the editor and background artwork is culled in playtest. Simulation remains active offscreen. Runtime capacity failures produce a visible limit message instead of silently dropping spawns. Dense scenes with many active behaviors still need playtesting on target devices.

### Particle effects on the first frame

Every effect has **Start already flowing** (`prewarm`, boolean, default `false`) in the Inspector and Particle Studio. When enabled, scene entry seeds a spread of particle ages and positions from its rate, lifetime, velocity, gravity, wind, source area and attachment. Rain/snow enter in progress and smoke/fire have an established plume. Camera weather uses the initial camera position and zoom. This initialization does not advance game time, movement, blocks, audio or cutscenes. It repeats on restart and scene reentry, with no particles carried between scenes.

Disabled effects remain empty; movement/grounded triggers still require their source to move. One-shot effects start on the first rendered frame without being aged away or fired twice; steady lights already appear immediately. Prewarming runs only at scene load, not each time a block starts an effect. Existing projects keep the original start-empty behavior. The existing 256 particles per effect, 1,024 overall and 32 sources remain enforced; particle drawing and aging continue normally afterward.

### Sprite Z index

Select any placed sprite, imported whole-sprite group, ordinary group, tile or Player in World to edit **Z index · layer order**. Higher values draw in front; lower values draw behind. Values are integers from −10,000 to 10,000 (default 0). Each parent’s value adds to its descendants, so a whole tree moves together while its individual parts can retain relative ordering. Layers belong to individual scene copies; shared sprite art, collision behavior and other scenes are unchanged.

Stored as `level.scene.entries[id].zIndex`, `level.scene.groups[].zIndex`, or `level.scene.playerZIndex`. Missing values preserve the prior default draw order. Sorting is stable at equal values. Z order spans background/world sprites and Player; background artwork, screen UI, particle overlays and post-processing retain their dedicated passes. Editor picking considers Z order, while the World hierarchy remains a separate organizational order. Runtime preparation snapshots effective values for entities and static map cells without promoting purely visual tile edits to active entities. The same renderer is embedded in published games.

### Player footstep sounds

Select **Player → Footstep sounds** to choose None, Pixel step, Soft step, any built-in sound or an imported recording. Preview it, import a recording directly, set volume (0–100%) and time between steps (80–1,000 ms). No blocks are required. Settings use scene-local `config.footstepSound` (default `silent`), `footstepVolume` (60) and `footstepInterval` (300 ms), so interiors can sound different or stay silent.

Steps depend on actual player movement after collision resolution, with grounded movement required for platformers. Standing still, pushing into a wall, riding a platform without walking, jumping, climbing, dashing and swimming do not trigger steps. Top-down and shooter movement work in both axes. Custom recordings always play once and overlapping copies of the same recording are skipped, including pending decodes. Long recordings should be trimmed to one step; stopping movement stops new triggers but allows a playing step to finish naturally. Footstep volume multiplies the recording and master effects volume. Scene changes and restart clear playing recordings and the cadence. Deleting a recording clears footstep assignments in every scene and supports Undo.

### Video completion and menu timers

Game logic → Screen UI → **when video finishes** selects a video element and runs its nested actions once at the actual end of visible, non-looping playback. Use **Go to scene** inside it for a prologue transition; choose **Play once** in the video Inspector. Hidden videos, loops and failed/blocked playback do not emit a completion. Showing a finished video again restarts it and permits another completion. Scene changes clear old decoders and pending completion events. JavaScript can use `onVideoFinished(elementId)`; visual logic subscribes to `videoFinished`.

**when a level begins → after 30 seconds → action** also works in menu/video scenes. Their scene clock and update/timer blocks run without world physics or gameplay win/lose checks. Pausing, settings overlays and blocking story sequences suspend these timers. Videos pause in the pause/settings overlays. Disconnected action stacks are saved but do not execute; the block editor shows an event-connection warning.

### Named sprite triggers

Select any placed sprite (including a whole imported sprite group) → **Trigger → Enable named trigger**. Give it a name, choose Code blocks only / Player touches / Player is nearby / Press E nearby, and optionally enable repeat activations. Nearby distance is in world pixels from collision-box edges. Touch and proximity fire on entry, not every frame; repeated entry requires leaving first. Interact uses the existing E/tap prompt and does not repeat while held. A player can have a code-only named trigger.

**Events → when trigger … happens** runs actions for the selected placed copy. **activate trigger …** calls it from any other event. Both store stable scene-object IDs; changing the display name does not break blocks. Copies with the same name remain separate choices. Deleted or disabled targets show as missing/disabled. By default each trigger fires once per scene visit, including manual activation. Optional repeats allow subsequent calls. Everything resets on scene change/restart; no triggers cross scenes.

Sprite/group triggers use the `named_trigger` trait (`enabled`, `name`, `mode`, `radius`, `repeat`); the player's optional trait lives in `level.scene.playerTrigger`. Group triggers cover their live descendant bounds and fire once for the group, unlike normal group traits which apply to each child. Children may also have independent triggers. JavaScript can call `api.trigger(sceneObjectId)` and return `onTrigger(sceneObjectId, name)`. During the callback, `api.touched` refers to that sprite (or group). Recursive activations of a trigger already being handled are ignored.

### Game memory across scenes

`project.gameValues` configures up to 256 typed, game-wide playthrough values. Each definition is `{id: "gv_...", name: "has_key", type: "boolean", initial: false}`. Types: `boolean`, `number`, `text`. Names are unique case-insensitively, 1–64 characters. IDs match `^gv_[a-z0-9_]{1,64}$` and remain stable across renaming. Finite numbers are limited to ±1 billion; text to 2,000 characters. Import and runtime reject invalid definitions or mismatched assignments. This array is deliberately outside scene settings. Older projects get an empty array.

Game logic's **Game memory** toolbar button and block category configure starting values. Blocks read, set, add/subtract numbers, or reset one value. JavaScript methods are `api.getGameValue(id)`, `api.setGameValue(id, value)`, `api.changeGameValue(id, amount)`, and `api.resetGameValue(id)`; all use the stable ID. Missing values report an explicit runtime error. Scene changes do not initialize or erase memory. Full `reset()` / new playtest resets to authored defaults; memory is isolated per runtime and is not persisted after closing the game. Old scene scripts still cannot call into the next scene. Ordinary `api.variables`, scene-value blocks, Blockly locals, and timers remain scene-local. UI/conversation shared-value bindings are also scene-local; use memory blocks to update UI or select dialogue when needed.

The **Remember a key between scenes** and **A door that remembers your key** behavior templates provision/reuse the same boolean `has_key` definition. One scene records collection and removes previously collected keys on reentry; the other removes doors when entering with that flag. Change their sprite selectors for custom keys and doors. Scene navigation remains authored through existing passage or scene-change tools.

### Video audio permission

The video player calls `play()` synchronously from start/retry gestures and delegates autoplay in editor and arcade iframes. If the browser rejects audible autoplay, a keyboard-accessible **Play video with sound** button appears over the canvas. It retries within the gesture, preserves authored audio, and disappears after playback succeeds. Hidden/paused/released videos cannot be revived by a late promise; scene changes remove notices and decoders. Decode/load errors show a format explanation instead of silently displaying black.


### Text styling, fades, and block search

Screen text, button labels, and bar labels support `textAlign` (`left`, `center`, `right`), `verticalAlign` (`auto`, `top`, `middle`, `bottom`), `fontFamily` (`pixel`, `clean`, `serif`), `bold`, `wrap`, `padding` (0–64), and `lineSpacing` (0–48). Defaults preserve left-aligned text, centered button/bar labels, 5-pixel padding, and 3-pixel line spacing. Text clips to its box. Inspector and UI designer share these controls. `api.ui(id, property, value)` / guarded block `api.setUI` expose them; blocks include text alignment, font/weight/wrapping, and numeric style changes.

`api.hideSprite(target)` / `api.showSprite(target)` disable or restore a sprite without deleting it. Find **Hide sprite** and **Show sprite** in **Sprites & world**. Targets support the player, one placed sprite, a group (including imported whole sprites), all copies of a kind, or the touched object. Hidden sprites keep their position, health and traits but do not render, collide, move, run sprite code, emit attached particles, or offer proximity/contact/conversation/named triggers. Show restores full opacity and normal behavior; existing blink traits still follow their schedule. Removed/collected sprites are not resurrected. Hiding does not count as collecting or defeating an object. These changes are local to the running scene and reset when it reloads. Explicit game logic can still change a hidden object's settings. Existing particles already emitted finish naturally.

`api.fadeSprite(target, 'in' | 'out', seconds)` and `api.fadeUI(id, 'in' | 'out', seconds)` animate opacity over 0–120 seconds. Sprite targets are `player`, `kind:<object-id>` (all current copies), `scene:<placed-id>` (including whole-sprite groups and their descendants), or a live entity reference in JavaScript. Blocks also offer the touched object. Static foreground/background tiles fade too. Fades are nonblocking, reverse from current opacity, and are replaced per target by a new fade. Fade in starts a fully opaque target at zero; it also shows hidden UI. Zero duration applies immediately. A direct UI opacity/visibility edit cancels that element's fade. Fade state is cleared on every scene change/restart. Fades continue during conversation/cutscene holds and menu screens, but stop under the pause overlay. Fades do not remove collisions or trigger objects.

The block toolbox has one home per visible block type. `pockit_start` and `pockit_touch` remain supported for existing workspaces and starter recipes; new work uses the scene-entry event and configurable collision event. Search matches block labels, tooltips, static options, category names, local variables, and functions. Clearing search restores the categories without mutating the workspace.

### Mirror placed sprites

`level.scene.entries[id].flipX` / `flipY` mirror a placed sprite and every animation frame. Player uses `level.scene.playerFlipX` / `playerFlipY`. These flags default to false and apply on top of automatic movement-facing. They are scene-local, do not change shared artwork, and do not change individual collision rectangles. `PockitScene.flip(project, id, 'x' | 'y')` toggles a sprite/Player, or mirrors an imported whole-sprite stamp by rearranging its loose parts within the group rectangle and flipping each part. Stamp groups retain their own flip flags for Inspector controls. Undo uses the ordinary editor checkpoint.

### Optional cinematic conversation zoom

The placed sprite's `conversation` trait adds `cameraZoom` (boolean, default false) and `zoom` (0.5–4, default 1.5). Zoom runs only when both `cinematic` and `cameraZoom` are true. The existing camera eases toward the requested zoom during dialogue, including when gameplay is paused, then returns to its previous zoom on finish. Duration follows the cinematic fade time with a 0.25-second minimum. Normal camera bounds remain active. Ending/skipping a surrounding cutscene restores its camera as before; scene changes discard dialogue and use the destination's camera. Default cinematic conversations keep their existing zoom.


### World copy, paste, and duplicate

The World toolbar offers Copy, Paste, Duplicate and Cmd/Ctrl+C/V/D. The editor-only `PockitWorldClipboard` captures selection descendants into an in-memory snapshot. `capture(project, nodes, ids)` snapshots instance data; `plan(project, snapshot, offset)` checks scene bounds and limits and allocates fresh IDs; `apply(project, plan)` installs a preflighted copy. The editor checkpoints once before applying, selects the new roots, and saves normally. Clipboard snapshots survive scene changes and Undo, and clear when another project is opened or the page reloads. They do not use the operating-system clipboard or duplicate the entire project.

Copies include groups, whole imported sprites, attached effects, UI, flips, Z order, collision boxes, named triggers, story traits, and explicit per-instance values. Copying a child without its parent materializes inherited traits/collision and cumulative Z. Sprite defaults are captured from the source scene so destination movement/config defaults do not change them. Sprites paste as loose instances over existing terrain without erasing occupied cells. Relative positions and hierarchy are preserved; a selection is translated as a unit to fit a smaller scene or rejected if it cannot fit. A same-scene paste/duplicate offsets by 16 pixels; cross-scene paste starts at the source coordinates.

Player, Camera, Backdrop, and Screen UI are singletons: copy/paste applies their settings, while Duplicate is disabled for selections containing them. Camera includes effects; Player includes movement/configuration and player scene metadata; menu backdrops carry their image panels. Pasted UI and particle emitters receive new IDs and explicit destination scene ownership. Existing limits remain 4,000 placed sprites per scene, 2,000 groups per scene, 64 UI elements per project; named effects have no fixed count limit; rejection leaves destination objects unchanged.

Asset library references remain shared. Scene logic is not copied. Cutscenes whose movement/animation/camera targets refer to copied objects get a cloned sequence with internal target IDs remapped; moved destinations shift with the selection. External targets remain references to the original objects, and creators must include or reassign them when moving across scenes. Sprite trait scripts retain their authored code verbatim. Clipboard data is not an external project import format.

Custom object IDs use positive safe-integer tile numbers starting at 8, skipping all built-in tile IDs. The old 899 ceiling and 256-definition count limit are removed in creation, tileset painting, whole-sprite imports and project validation. Existing IDs are preserved. Overall save/import size budgets and per-scene live sprite limits still apply.

Water zones (`zone.effect: "water"`) refract and tint only the submerged opaque player pixels, using each live zone’s collision box. Settings: `waterColor` (hex color, default `#559fbe`), `waterTint` (0–100%, default 35), `waterDistortion` (0–3 world pixels, default 1). These controls appear for Water in both Object Studio and the instance/group Inspector. Existing water traits inherit defaults. Adjacent tiles use the same world-space ripple phase; overlapping water uses the first matching zone without stacking tint. Hidden, removed and decorative background objects do not affect the player. Animation, flips, tiny power-ups and fades are preserved; the effect is independent of global post-processing. Zero tint and distortion give the original artwork. Water artwork also has `waterRipples` (0–3 world pixels, default 1) and `waterRippleSpeed` (0–200%, default 100). Set ripple strength to zero for still artwork. Optional `waterReflect` (default false) mirrors the visible world above the connected water surface, with `waterReflection` (0–100%, default 35) and a 96-world-pixel depth fade. Adjacent/stacked water sprites share a continuous surface. Reflections are clipped to opaque water pixels and retain world Z order; they exclude screen UI and the later particle/post-processing passes. Only visible reflective water allocates reusable snapshot canvases and requires a second world pass. These effects render during playtest and published gameplay; republish existing game snapshots to include new renderer features.

### Whole-screen scene transitions

**Story & scenes** has **Fade to black over … seconds** and **Fade from black over … seconds**, searchable by “fade” or “black”. These are simple action blocks with no nested actions or “when finished” slot. Following blocks run immediately; use a separate **after … seconds** block if a scene change should be delayed.

`api.fadeScreen('out' | 'in', seconds)` creates a fresh pure-black overlay over the game viewport, including screen UI and conversations. **Fade from black** always starts at 100% opacity and fades to 0%. **Fade to black** always starts at 0% and fades to 100%, then remains black. Each call replaces the previous overlay and restarts from its own endpoint. Durations accept decimals from 0–120 seconds; zero applies the final opacity immediately. Gameplay continues. Fades also progress in menu scenes and conversation/cutscene holds, but pause with the pause menu. Scene changes and restarts clear the overlay. Older saved “when finished” contents are preserved as a separate ordinary timer block, so existing actions are not lost.

### Standalone itch.io downloads
Game owners can use My games → ••• → Download playable HTML, the editor’s Download playable HTML button, or the publishing window. The download uses the current loaded project (including unsaved changes), compiles scene logic, embeds runtime and deduplicated media, and excludes page settings. No new publication or server-side stored copy is created. `html-export.js` adds responsive Pockit branding and a production arcade link when published, and hides the engine’s default HUD. Creator-authored Screen UI remains intact. The download is a snapshot, not a live link; JSON remains the editable backup format. Large single-file exports can temporarily use substantial browser memory.

### Public domain
Pockit’s canonical website is https://www.pockitengine.com (the apex redirects there). Share links, publication confirmations, and standalone exports always use this host, including when editing locally. Existing game and project IDs are unchanged. The legacy Vercel host permanently redirects paths and query parameters to the new host. Supabase remains the same project; internal `accounts.pockit.invalid` user identities and Storage URLs must not be renamed. Domain changes require updating account-function exact origins and Auth Site URL/redirect configuration. Browser login and device-only backups are origin-scoped; saved account projects carry over after signing in. Previously downloaded files remain snapshots, but their old outbound links follow the permanent redirect.


## Arcade community
Published games have shared view, like, and comment counts. Views are approximate page visits, counted once per randomly identified browser per UTC day; no IP address is stored. Clearing browser storage can count as a new visitor. Historical visits before this feature are not backfilled. Likes require a permanent account and are idempotent, one per account per publication. Saved-shelf bookmarks remain device-only and are separate from likes.

Comments are plain text, 1–2,000 characters, require a permanent account, and are limited to one submission every 15 seconds. They load 30 at a time. Authors may remove their own comments; game owners may moderate comments on their games. Game scripts and custom page HTML cannot access these account controls. Static example games do not have community metrics.

Arcade sorts: Newest (default), Popular, Most liked, Most viewed, Most discussed. Popular uses lifetime views + 10 × likes + 3 × current comments; it is not a trending-time-window algorithm. Ranking occurs in the database before pagination (100 games per page). Updating a published game preserves engagement; unpublishing or deleting it removes the publication and its engagement.

Arcade playback creates the sandboxed game iframe only after Play game is pressed. Restart is disabled until then; expansion and fullscreen preserve the same running iframe. Page settings `showSurface` defaults to true for both existing and new pages; false makes the shared preview/live content surface and custom-HTML base background transparent. Navigation and footer retain Pockit styling.
