Files
feuerwerk/tools/seed-e2e.mjs
T
rootandClaude Opus 5 76aa441dce Enabled switch, text assets, mouse input and the jump-and-run example game
Engine additions that the example game needed:
- `enabled` as a shared property of every placeable asset (default on, an
  override on the scene object). A switched-off object is not drawn, does not
  collide and its behavior does not run - and neither does anything parented
  below it. Disabled objects are now hidden in the scene view as well, so the
  editor shows what the game shows; they stay in the hierarchy panel, which is
  where they are selected and switched back on.
- Asset type `text` (text, fontSize, color, align), rendered in scene and game.
- Pointer input in the game view: `On click`, `Mouse position`, `Mouse button`,
  `Is hovered`, hit-tested against colliders, else sprite/text footprints.
- New nodes: Set/Is enabled, Set text, Is asset, Name of, Equals, To text,
  Join text, Floor/Ceil/Round, Modulo, Random, Time, Restart game.

Tooling: shared CDP harness (tools/harness.mjs), central project registry with
a guard that keeps destructive scripts off the playground (tools/projects.mjs),
plus smoke suites for the demo scene and the jump-and-run game.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 20:52:10 +02:00

94 lines
4.8 KiB
JavaScript

#!/usr/bin/env node
/**
* Writes the fixture scene tools/e2e.mjs asserts against: two top-level crates with a collider
* child each, a camera, the Mover behavior owning the red crate and a Spawner. Kept separate
* from tools/seed-demo.mjs so the shipped demo can change without rewriting 100+ checks; the
* fixture's objects are uniquely named and positioned exactly where the checks expect them.
*
* Usage: PATH=/opt/node22/bin:$PATH node tools/seed-e2e.mjs [projectId] [baseUrl]
* Defaults to the dedicated fixture project; the demo project is refused.
*/
import { PROJECTS, cliArgs, writeProject } from './projects.mjs';
const { projectId, baseUrl } = cliArgs('seed-e2e.mjs', PROJECTS.fixture);
/**
* "Mover" behavior in five nodes: every frame, move the owner along the normalized arrow-key
* axis, scaled by the `speed` variable per second (On update's dt feeds the dt input).
* Diagonals are not faster than straight moves because Axis input normalizes the vector, and
* Move's "Collide with solids" stops the owner's solid colliders at other solid colliders.
*/
const moverGraph = {
nodes: [
{ id: 'n-update', type: 'OnUpdate', x: 0, y: 0, params: {} },
{ id: 'n-owner', type: 'Owner', x: 0, y: 120, params: {} },
{ id: 'n-axis', type: 'AxisInput', x: 0, y: 200, params: {} },
{ id: 'n-speed', type: 'GetVariable', x: 0, y: 400, params: { variable: 'speed' } },
{ id: 'n-move', type: 'Move', x: 340, y: 0, params: { collide: true } },
],
edges: [
{ id: 'e1', from: { node: 'n-update', port: 'exec' }, to: { node: 'n-move', port: 'exec' } },
{ id: 'e6', from: { node: 'n-update', port: 'dt' }, to: { node: 'n-move', port: 'dt' } },
{ id: 'e2', from: { node: 'n-owner', port: 'object' }, to: { node: 'n-move', port: 'object' } },
{ id: 'e3', from: { node: 'n-axis', port: 'x' }, to: { node: 'n-move', port: 'dx' } },
{ id: 'e4', from: { node: 'n-axis', port: 'y' }, to: { node: 'n-move', port: 'dy' } },
{ id: 'e5', from: { node: 'n-speed', port: 'value' }, to: { node: 'n-move', port: 'scale' } },
],
};
/** "Spawner": pressing Space places a new Crate at world (0, 150) and logs its name. */
const spawnerGraph = {
nodes: [
{ id: 's-key', type: 'OnKeyDown', x: 0, y: 0, params: { key: ' ' } },
{ id: 's-spawn', type: 'Spawn', x: 260, y: 0, params: { asset: 'crate', y: 150 } },
{ id: 's-log', type: 'Log', x: 520, y: 0, params: {} },
],
edges: [
{ id: 's-e1', from: { node: 's-key', port: 'exec' }, to: { node: 's-spawn', port: 'exec' } },
{ id: 's-e2', from: { node: 's-spawn', port: 'exec' }, to: { node: 's-log', port: 'exec' } },
{ id: 's-e3', from: { node: 's-spawn', port: 'object' }, to: { node: 's-log', port: 'message' } },
],
};
const project = {
formatVersion: 1,
id: projectId,
name: 'Demo project',
activeSceneId: 'scene1',
assets: [
{
id: 'scene1',
type: 'scene',
name: 'Level 1',
props: { backgroundColor: 0x22252c },
objects: [
{ id: 'o1', assetId: 'crate', name: '', position: { x: -150, y: 0, z: 0 }, overrides: {}, parentId: '' },
{ id: 'o2', assetId: 'crate', name: 'Blue crate', position: { x: 120, y: -80, z: 0 }, overrides: { width: 140, color: 0x4488ff }, parentId: '' },
{ id: 'o3', assetId: 'cam', name: '', position: { x: 0, y: 0, z: 0 }, overrides: {}, parentId: '' },
// The mover is parented to the red crate (its owner); its position is local to it.
{ id: 'o4', assetId: 'mover', name: '', position: { x: 0, y: 110, z: 0 }, overrides: {}, parentId: 'o1' },
// Both crates carry the shared "Crate collider" as a centred child; the blue crate's
// instance is widened to match its 140-wide sprite override.
{ id: 'oc1', assetId: 'collider', name: '', position: { x: 0, y: 0, z: 0 }, overrides: {}, parentId: 'o1' },
{ id: 'oc2', assetId: 'collider', name: '', position: { x: 0, y: 0, z: 0 }, overrides: { width: 140 }, parentId: 'o2' },
// Top-level spawner (no owner) left of the red crate: Space spawns a crate.
{ id: 'o5', assetId: 'spawner', name: '', position: { x: -300, y: 0, z: 0 }, overrides: {}, parentId: '' },
],
},
{ id: 'crate', type: 'sprite', name: 'Crate', props: { width: 100, height: 60, color: 0xe0553f } },
{ id: 'cam', type: 'camera', name: 'Main camera', props: { width: 640, height: 360, zoom: 1 } },
{ id: 'collider', type: 'collider', name: 'Crate collider', props: { shape: 'box', width: 100, height: 60, radius: 50, isTrigger: false } },
{
id: 'mover',
type: 'behavior',
name: 'Mover',
props: { speed: 200 },
variables: [{ key: 'speed', label: 'speed', kind: 'number', default: 0 }],
graph: moverGraph,
},
{ id: 'spawner', type: 'behavior', name: 'Spawner', props: {}, variables: [], graph: spawnerGraph },
],
};
await writeProject(projectId, baseUrl, project);