A log layer (src/editor/console/log.ts) keeps a 500-line ring buffer and
wraps console.log/info/debug/warn/error, still calling the original. Notices
and behaviour runtime errors go straight to the layer instead of through
console, so editor tests stay quiet and errors can render red. world.log is
untouched: it is per-run structured state that runtime tests and e2e read
directly, not a text stream.
The panel sits under the visible tab, resizable with Splitter('y'), with a
CONSOLE header and a clear button. Each line carries its wall-clock time in
its own dimmed column, so locate() still finds the message text.
The taller centre area drops the graph fit zoom to 0.60, which broke six e2e
checks that assumed one screen pixel is one graph unit and that a fixed
offset from the canvas centre is empty. They now ask __gm.emptySpot(),
__gm.graph.emptySpot() and __gm.graph.zoom() instead of guessing.
locate/locateAll added the unscaled Pixi text width to the global position,
so click points in the scaled graph canvas landed too far right; they use
getBounds() now.
check.sh: 831 tests. Full e2e: ALL PASSED - 671 checks. Smokes 24/19/20.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
100 lines
3.0 KiB
JavaScript
100 lines
3.0 KiB
JavaScript
/**
|
||
* The throwaway rig steps 17–20 share in the fixture scene: a behavior asset cloned off `mover` with the
|
||
* step's graph, a crate to hold it, and a logic child that runs it.
|
||
*/
|
||
|
||
/**
|
||
* Where each step parks its box. The fixture scene is shared, so two steps at the same y would
|
||
* overlap and each would fail for reasons belonging to the other.
|
||
*/
|
||
export const LANES = {
|
||
editorRun: 400,
|
||
editorRunContainer: 800,
|
||
editorRunEdits: 1200,
|
||
pause: 300,
|
||
nested: 700,
|
||
keyRepeat: -300,
|
||
stateMachine: -700,
|
||
console: -1100,
|
||
};
|
||
|
||
/**
|
||
* Source for a `make(id, assetId, parentId, y, name)` factory, to paste inside a `mutate()`
|
||
* string. It clones `scene.objects[0]`, so a new object carries whatever shape the fixture has.
|
||
*/
|
||
export const MAKE_OBJECT = `
|
||
const make = (id, assetId, parentId, y, name = '') => {
|
||
const o = structuredClone(scene.objects[0]);
|
||
Object.assign(o, {
|
||
id,
|
||
assetId,
|
||
name,
|
||
position: { x: 0, y, z: 0 },
|
||
parentId,
|
||
overrides: {},
|
||
instanceEdits: {},
|
||
});
|
||
return o;
|
||
};`;
|
||
|
||
/**
|
||
* Returns the created ids plus `remove()`, so a step's cleanup matches what it made. `front` puts the
|
||
* asset at the head of the list: a row appended at the end scrolls out of `dragAsset`'s reach.
|
||
*/
|
||
export async function scaffoldBehavior(t, options) {
|
||
const {
|
||
assetId,
|
||
name,
|
||
graph,
|
||
boxId,
|
||
boxName = 'Box',
|
||
logicId,
|
||
y,
|
||
front = false,
|
||
props = { enabled: true, runInEditor: false },
|
||
} = options;
|
||
|
||
await t.mutate(`
|
||
const p = __gm.state.project;
|
||
const scene = __gm.state.activeScene;
|
||
const asset = structuredClone(p.assets.find((a) => a.id === 'mover'));
|
||
Object.assign(asset, {
|
||
id: ${JSON.stringify(assetId)},
|
||
name: ${JSON.stringify(name)},
|
||
props: ${JSON.stringify(props)},
|
||
variables: [],
|
||
graph: ${JSON.stringify(graph)},
|
||
});
|
||
p.assets.${front ? 'unshift' : 'push'}(asset);
|
||
${MAKE_OBJECT}
|
||
scene.objects.push(
|
||
make(${JSON.stringify(boxId)}, 'crate', '', ${y}, ${JSON.stringify(boxName)}),
|
||
make(${JSON.stringify(logicId)}, ${JSON.stringify(assetId)}, ${JSON.stringify(boxId)}, 0, ${JSON.stringify(name)}),
|
||
);
|
||
`);
|
||
await t.sleep(400);
|
||
|
||
return {
|
||
assetId,
|
||
boxId,
|
||
logicId,
|
||
/** Removes everything this rig added and reports what survived (should be all zeroes). */
|
||
async remove() {
|
||
await t.evaluate('__gm.state.select(null)');
|
||
await t.mutate(`
|
||
const p = __gm.state.project;
|
||
const scene = __gm.state.activeScene;
|
||
const objects = ${JSON.stringify([boxId, logicId])};
|
||
const assets = ${JSON.stringify([assetId])};
|
||
scene.objects = scene.objects.filter((o) => !objects.includes(o.id));
|
||
p.assets = p.assets.filter((a) => !assets.includes(a.id));
|
||
`);
|
||
await t.sleep(300);
|
||
return t.json(`{
|
||
objects: __gm.sceneObjects().filter((o) => ${JSON.stringify([boxId, logicId])}.includes(o.id)).length,
|
||
assets: __gm.state.project.assets.filter((a) => a.id === ${JSON.stringify(assetId)}).length,
|
||
}`);
|
||
},
|
||
};
|
||
}
|