# SEAS Workshop

The complete illustrated HTML user guide is
[dist/docs/workshop-guide.html](../dist/docs/workshop-guide.html). Open it
directly in a browser or visit `/docs/workshop-guide.html` on the SEAS server.
It includes a walkthrough from a blank project, every node, and troubleshooting.

Workshop makes single-scene Canvas2D games using rectangles, text and visual
execution graphs. Launch **SEAS Workshop** from Start or run `workshop` in
Terminal. **Example** opens the editable Coin Collector. The factory project
is `/projects/coin-collector/project.json`; its published game is
`/apps/coin-collector/app.json`, also available from Start or
`game-coin-collector` in Terminal.

## Editing and retaining projects

Create rectangles and text with the object-list buttons. Select objects in
the list or scene, drag to move, and drag the white lower-right handle to
resize. Grid snaps transforms to 16 pixels. The inspector also accepts exact
positions, sizes, colours, text, comma-separated tags and collision modes.
Objects are drawn in list order. Duplicate copies an object's graph; Delete
may leave references in other graphs, which Play/Publish will diagnose.

Use the preview's **− / +**, **Fit** and **100%** controls to zoom the grid.
The mouse wheel zooms around the pointer. Pan with the middle mouse button,
or focus the canvas and hold Space while dragging. With the canvas focused,
**+ / −** zoom and **0** restores Fit. Fit follows the available preview size;
manual zoom stays fixed when panes or the window resize.

Drag the vertical dividers beside the left project/object sidebar and the
right node inspector to resize those panes. Double-click a divider to reset
its width. Focused dividers support Left/Right arrows (10 pixels, or 40 with
Shift), Home/End for the width limits, and Enter to reset. Escape cancels an
active divider or scene-object drag. Drag the horizontal divider below the
preview up/down to change its height. Its keyboard controls use Up/Down;
double-click or Enter restores 43% of the current editor height. Limits keep
space for the graph.
Zoom, pan and pane sizes belong to each editor window; they do not mark the
project modified, enter undo history, or appear in saved/exported JSON.

**Add recipe** above the object list opens a category-filtered picker:

| Object recipe | Included objects and behaviours |
| --- | --- |
| Top-down player | Solid player tagged `player`; four movement chains with arrows or WASD and configurable speed/size. |
| Solid wall | Solid rectangle with configurable width/height. |
| Room boundary | Four walls sized to the current scene, with thickness and inset. |
| Collectible | Coin trigger: overlap with `player` adds points and destroys Self. |
| Score display | Text updated every step from a number variable. |
| Goal zone | Trigger: overlap with `player` ends the game with a message. |
| Hazard | Trigger: overlap with `player` restarts the scene or ends the game. |

Select an object and click **Add behaviour** in the Behaviours toolbar to
append a wired graph: **Move while key held**, **Collect on contact**,
**Display variable**, **Win at threshold**, **React to contact**, or
**Initialise variable**. The picker lets you choose keys, movement speeds,
contact tags, messages, targets and comparisons as appropriate. Contact
behaviours require both objects to use solid or trigger collision; appending
a graph leaves existing object properties unchanged. Display variable needs
a text target; Initialise variable supports number and Boolean values.

Variable dropdowns offer existing compatible variables or a new variable
with a name, type and initial value. Score recipes prefer an existing number
variable named Score or with ID `score`; otherwise they offer to create one.
Reusing a variable preserves its initial value. Recipes generate fresh IDs,
wire references and place appended nodes below the existing graph. Each
insertion (including all four boundary walls and any new variables) is one
undo step. Existing nodes/connections remain intact, and incomplete graphs
can still receive recipes. Invalid settings or exceeded project limits leave
the draft unchanged.

Recipes expand to ordinary editable objects, nodes and variables using the
existing project schema. Save/Export/Import and Publish work normally. To
try them, start New, insert Top-down player, Collectible and Score display,
then append Win at threshold to the display using the same Score variable
and a threshold of 1. Play and move the player through the coin.

Undo and Redo retain up to 60 edits. With the scene focused, arrow keys nudge
the selected object, Delete removes it, and Ctrl+D duplicates it. Ctrl+S saves;
Ctrl+Z and Ctrl+Shift+Z undo and redo outside text inputs. Expand Variables to
add number/Boolean variables, set their initial values, and change labels.
Variable IDs are stable and shown above their properties.

**Save** writes the current project to `/projects/<project-id>/project.json`.
**Save copy** uses the current project ID for a new location. Existing target
directories require an explicit Replace choice. **Open** lists immediate
project folders. **New**, **Example** and **Import** ask before discarding an
unsaved draft. **Play** clones the unsaved draft into an independent native
window without saving it. The editor document never receives gameplay changes.

Save and Publish are session-only. Browser reload and simulated restart restore
factory files. **Export** downloads a self-contained `.seas-project.json`
file to the host; **Import** selects and validates JSON before opening an
unsaved draft. Existing `.aster-project.json` exports remain importable with
the same project schema. Export a project before reloading. Unmounting `/projects`
retains editor drafts and allows Export, but saving to an unavailable path
reports an error. After remounting, save to the original path or save a copy.

## Visual behaviours

Select an object and add nodes from the Behaviours palette. Click an output
handle followed by an action's input handle to create a connection. Each
output has one successor; rewiring an output replaces its old connection.
Drag node headers to arrange the graph. Selecting a node opens its parameters,
connection-disconnection buttons and Delete node. Condition outputs are
explicitly labelled `true` and `false`.

| Node | Parameters and behaviour |
| --- | --- |
| On Start | Once after starting or restarting. |
| On Update | Once per simulation step. |
| On Key Pressed | Selected key; once per press, ignoring held-key repeat. |
| On Overlap Enter | Optional other-object tag; once when contact begins. |
| Key Held | Selected key; `true`/`false` branches. |
| Compare Number | Number variable, operator, constant value; branches. |
| Move | Target, X/Y pixels per second. |
| Set Variable | Variable and matching number/Boolean value. |
| Change Variable | Number variable and amount. |
| Set Text | Text target and string; `{variable-id}` inserts its current value. |
| Destroy Object | Target; destruction takes effect in the current step. |
| Restart Scene | Recreate the original snapshot and clear input/contacts. |
| End Game | Stop gameplay and display a message until Restart. |

Targets are Self, Other, or a stable object ID. Other is available on overlap
event paths. Number comparisons accept `==`, `!=`, `<`, `<=`, `>` and `>=`.
Supported keys are arrow keys, WASD, Space and Enter, using browser `code`
names (`ArrowRight`, `KeyW`, etc.). Text substitutions refer to variable IDs,
not labels. Movement can be authored as On Update → Key Held → true → Move.
Coin collection can be On Overlap Enter (tag `coin`) → Change Variable
(score +1) → Destroy Object (Other).

Incomplete graphs remain editable and saveable. Play and Publish require
valid node types, ports, parameters, references, types and event context.
Cycles are rejected, including disconnected cycles. Error buttons select the
affected object/node. Event roots and connections execute in stored order.

## Game runtime

Games simulate at 60 Hz, with at most six catch-up steps and 0.1 seconds of
elapsed time accumulated per frame. Transform units are logical scene pixels;
Move multiplies speed by the simulation step. Rendering does not mutate state.
Solid movement sweeps and resolves X then Y against other solids. Trigger
objects produce contacts without blocking; `none` objects do not participate.
The runtime does not resolve initially intersecting solids automatically.

Objects execute Start, then key/update events in document order. Contacts are
processed after movement in stable object-pair order. Both sides can receive
overlap events. Objects queued for destruction cannot start another event that
step; the current action chain completes, so Destroy followed by Change
Variable still works. End Game retains the final drawing; Restart
resets variables, transforms, contacts, pending actions and held input.

Only the active game window receives gameplay keys. Switching windows,
minimising, hiding the document or losing browser focus clears held input and
pauses simulation; resuming uses a fresh time accumulator. Modified browser
shortcuts and typing in form fields remain available. Every runner has a
normal PID. Closing it, ending its task, or shutting down SEAS uses one
disposal path that cancels animation and removes input/lifecycle listeners.

## Publishing and launching

Publish validates the draft and stages `app.json` and `game.json` entirely in
memory, then swaps the directory into `/apps` with one VFS notification.
Validation or replacement failure leaves the previous package intact.
Publishing over an existing directory offers Replace or Cancel. Existing
games keep their loaded snapshots; new launches use the replacement.

Installed games appear in Start and Workshop's **Installed** list. Launch with
`game-<package-id>` in Terminal, open `/apps/<package-id>/app.json`, or
double-click that manifest in Explorer. Double-clicking `project.json` opens
Workshop. Package discovery scans immediate `/apps` children, keeps built-in
apps separate, and supplies namespaced `/bin/game-<id>` launchers. Invalid
packages have no launcher and appear as diagnostics in Installed. Unmounts,
file changes, removals and filesystem resets refresh discovery. Loaded games
continue from their snapshots while `/apps` is unmounted.

## Version-1 documents

Project JSON has `schemaVersion: 1`, `id`, `name`, `scene`, `variables` and
`objects`. IDs start with a lowercase letter and contain lowercase letters,
digits and hyphens, up to 64 characters. Object IDs `self` and `other` are
reserved for event context. Scenes have integer width/height from
64 to 4096 and a `#RRGGBB` background. A variable is `{id,name,type,initial}`,
where type is `number` or `boolean`. An object has:

```text
id, name, kind (rectangle | text), x, y, width, height,
colour (#RRGGBB), text, tags (string array),
collision (none | solid | trigger),
graph: { roots: [node ID], nodes: [...], edges: [...] }
```

Nodes are `{id,type,params,editor:{x,y}}`. Connections are `{from,port,to}`.
`roots` lists every event node in execution order. Runtime snapshots remove
node `editor` positions; transforms and initial values are retained. Graph
layout belongs to the document; runtime state is an independent clone.
The bundled factory project is a full readable schema example. The shared
node definitions in `dist/js/workshop/node-types.js` specify parameter names
and defaults.

A package manifest has its own `schemaVersion: 1`, matching `id`, `title`,
`kind: "seas-game"`, `runtimeVersion: 1`, and `entry: "game.json"`. Entry
paths are fixed and cannot traverse out of the package. The loader also accepts
legacy `kind: "aster-game"` manifests at schema/runtime version 1. Unsupported versions
report errors. First-release documents are shapes/text only and contain no
external code or assets. Future asset and multi-scene formats require explicit
versioning; there is no automatic migration.

Documents are limited to 2 MiB, 256 objects, 128 variables, and 256 nodes/512
connections per object. Numeric authoring values must be finite and within
±1,000,000; sizes are positive. Graph interpretation has a shared 10,000
instruction budget per step and reports the object/node if exceeded.

## Validation

Focused pure tests and the direct VFS/shell dependencies:

```sh
node --test test/workshop.test.js test/mounts.test.js test/rootfs-image.test.js test/shell-session.test.js
```

Use Node 20 or later as required by SEAS. Node 18 also needs
`--experimental-global-webcrypto` for the existing random-device test.
With a loopback SEAS server running, use the shared Playwright installation:

```sh
cd ../browser_validation
SEAS_URL=http://127.0.0.1:4173/ node tests/seas-workshop.js
SEAS_URL=http://127.0.0.1:4173/ node tests/seas-workshop-lifecycle.js
SEAS_URL=http://127.0.0.1:4173/ node tests/seas-workshop-view.js
SEAS_URL=http://127.0.0.1:4173/ node tests/seas-workshop-recipes.js
```

The Chromium check authors a coin game through the UI, exercises graph wiring,
scene drag/undo, gameplay and input isolation, save/export/reload/import/open,
publication/replacement, all three launch routes, mounts and task cleanup.
The complementary lifecycle check covers resize, keyboard duplicate/delete,
node drag/disconnect/delete, minimise/resume and shutdown, and tracks pending
animation frames and document input listeners to prove cleanup.
The view check covers zoom controls and pointer anchoring, pan, zoomed object
move/resize/undo/cancel, pane resizing and keyboard controls, minimum window
size, node wiring/dragging, unchanged exports and independent-window cleanup.
The recipe check covers all seven object recipes and six behaviour graphs,
settings, number/Boolean bindings, fresh IDs, atomic undo/redo, graph append
preservation, invalid settings/capacity, save/export/import, deterministic
runtime behaviour and a playable game built through the picker.
Final editing feel, graph readability and rendering belong in your normal
browser: open Example, edit a coin and graph, Play, collect the coins, Save and
Export, Publish, launch from Start, end the game in Task Manager, reload and
Import the downloaded file.
