Files
feuerwerk/tools/e2e/08-colliders.mjs
T
rootandClaude Fable 5.1 1eb28314cf Refactoring: one file per node, consistent comments, shared helpers, split large modules
- Graph: nodes.ts split into nodes/<category>/<Type>.ts (62 files) with
  shared.ts factories and index.ts registry; NodeContext/values/NodeRegistry
  separated, writeTyped() replaces duplicated typed writes
- Comments: one style (JSDoc on non-obvious exports, // for decisions and
  traps), wrong/stale comments fixed or removed, ids.ts now real 10 base-36 chars
- Core: chainOf, shared worldPosition/editorPosition core, commands/require.ts,
  commands/naming.ts, util/list.ts, setBagValue/toPosition/propDef; dead code
  removed (isBehaviorAsset, SCENE_OBJECT_SCHEMA, CommandStack.subscribe,
  Input.axis, setMode, titleText, World.worldPosition, unused ctor params)
- Editor: Inspector split into inspector/ sections, App.ts into Editor.ts +
  debugHook.ts + shortcuts.ts, Viewport into items.ts + symbols.ts, shared
  "Add collider" action, unified type labels/swatches
- UI toolkit: Widget.trackHover/onPointerRelease, drawControlBox, math helpers,
  Popup.openBelow, Menu/Dialog/ColorPicker/ListRow in own files, named Theme sizes
- Tools: seed-demo/smoke-demo and demo-check project removed, tools/lib
  (harness, projects, builders), e2e.mjs as runner over tools/e2e/*.mjs sections
- Tests: tests/helpers.ts replaces copied fixtures and NodeContext doubles

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 08:37:22 +02:00

78 lines
5.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Colliders: "Add collider" from the context menu, collision events reaching the owner's
* behavior, and Move with "Collide with solids" – blocking, triggers, no tunnelling.
*/
export async function colliders(t) {
const { check, sleep, evaluate, json, shot, click, clickText, locate, press, holdKey, undo, mutate, select, state, object, objects, asset, world, entity, stop } = t;
// 15. Right-click the red crate → "Add collider": a collider asset sized like the crate
// (100×60) is placed as its child at the centre, selected and drawn in the overlay. The
// seed already gives both crates a "Crate collider" child (oc1/oc2), so the added one is
// undone again to keep exactly one collider per body for the checks below.
const objectsBefore = (await objects()).length;
const redRow = await locate('Red crate');
await click(redRow.x, redRow.y, 'right');
await clickText('Add collider');
await sleep(100);
const s = await state();
const colObj = s.selection?.kind === 'object' ? await object(s.selection.objectId) : null;
const colAsset = colObj ? await asset(colObj.assetId) : null;
check('Add collider creates a collider child sized like the sprite', colObj?.parentId === 'o1' && colAsset?.type === 'collider' && colAsset.props.width === 100 && colAsset.props.height === 60, JSON.stringify({ colObj, colAsset }));
check('collider is drawn in the overlay layer', colObj && (await evaluate(`__gm.layerOf('${colObj.id}')`)) === 'overlay');
check('collider inspector shows the shape menu', Boolean(await evaluate(`__gm.locate('Box ▾')`)));
await shot('21-collider');
await undo();
const colliderAssetGone = colAsset ? !(await asset(colAsset.id)) : false;
check('undo removes the added collider object and its asset', (await objects()).length === objectsBefore && colliderAssetGone);
// 15a. Wire "On collision enter → Log(other)" into the Mover (owner = red crate) and play with
// the crates overlapping: the log names the blue crate exactly once (enter, not stay),
// and colliders are not rendered in play mode.
await mutate(`
const mover = __gm.state.project.assets.find(a => a.id === 'mover');
mover.graph.nodes.push({ id: 'n-hit', type: 'OnCollisionEnter', x: 0, y: 600, params: {} }, { id: 'n-hitlog', type: 'Log', x: 340, y: 600, params: {} });
mover.graph.edges.push(
{ id: 'e-hit1', from: { node: 'n-hit', port: 'exec' }, to: { node: 'n-hitlog', port: 'exec' } },
{ id: 'e-hit2', from: { node: 'n-hit', port: 'other' }, to: { node: 'n-hitlog', port: 'message' } });
__gm.state.activeScene.objects.find(o => o.id === 'o2').position = { x: -100, y: 0, z: 0 }; // 50 from the red crate → 100-wide boxes overlap
__gm.state.select(null)`);
await press('Enter', { ctrl: true });
await sleep(500);
const collisionWorld = await world();
check('On collision enter fired once with the other body', collisionWorld.log.filter((l) => l === 'Blue crate').length === 1, JSON.stringify(collisionWorld.log));
const spriteCount = await evaluate(`__gm.state.activeScene.objects.filter(o => __gm.state.project.assets.find(a => a.id === o.assetId)?.type === 'sprite').length`);
const info = await json('__gm.gameInfo()');
check('colliders are not rendered in the game view', info.visibleItems === spriteCount, `${JSON.stringify(info)} sprites=${spriteCount}`);
await stop();
// 15b. Solid vs trigger. Put the blue crate 150 to the right of the red one (red 100 wide, the
// blue collider 140 wide → 30 of free travel) and hold ArrowRight: the Mover's Move
// (Collide with solids) parks the red crate flush against the blue one at world x = −120.
// Making the blue collider a trigger lets it pass through instead (drawn amber). A frame
// step far larger than the collider must not jump across it either (sub-steps).
const playAgainstBlue = async (trigger, speed = 0) => {
await mutate(`
const scene = __gm.state.activeScene;
scene.objects.find(o => o.id === 'o2').position = { x: 0, y: 0, z: 0 };
scene.objects.find(o => o.id === 'oc2').overrides = ${trigger ? '{ width: 140, isTrigger: true }' : '{ width: 140 }'};
${speed ? `__gm.state.project.assets.find(a => a.id === 'mover').props.speed = ${speed};` : ''}
__gm.state.select(null)`);
await press('Enter', { ctrl: true });
await sleep(300);
await holdKey('ArrowRight', 700);
const red = await entity('o1');
await stop();
return red;
};
const blocked = await playAgainstBlue(false);
check("Move with \"Collide with solids\" stops the red crate flush against the blue crate's solid collider", blocked && Math.abs(blocked.x - -120) < 0.01, `world x=${blocked?.x}`);
const passed = await playAgainstBlue(true);
check('a trigger collider does not block (the crate passes through)', passed && passed.x > -120 + 20, `world x=${passed?.x}`);
const fast = await playAgainstBlue(false, 40000);
check('a very fast mover cannot tunnel through a solid collider', fast && Math.abs(fast.x - -120) < 0.5, `world x=${fast?.x}`);
await mutate(`__gm.state.project.assets.find(a => a.id === 'mover').props.speed = 200`); // later steps expect the seeded speed
await select({ kind: 'object', objectId: 'oc2' });
await sleep(200);
await shot('22-trigger-collider');
}