A source wears an icon without becoming decoration (#89)
game-maker / suite (push) Successful in 9m49s
game-maker / suite (push) Successful in 9m49s
Behavior and Container draw an icon because isSymbolType() is true for them, but that predicate does not mean "draws an icon" — it means "is editor decoration". Four places read it that way: collectWorldItems, GameViewport. collectFrame and syncDrawOrder all drop symbols out of the running world and the game picture, and placementOf() gives them an upright unscaled box. Listing 'particles' there would have handed the source an icon and taken away its particles, in the scene view as well as in the game. So the two questions are two functions now. hasSceneIcon() says a type draws an icon; isSymbolType() keeps meaning decoration. The icon itself is a SceneViewport decoration, which is editor-only for free because GameViewport never syncs decorations. A ring (r 10, #c26a1f) with four sparks, upright and unscaled like the other symbols, its middle left open — unlike Behavior and Container a source draws something of its own, and the field is densest exactly there. The name sits beside it and a dashed line runs to the parent, same colour. Found while writing the tests: the source's grab box scaled with its transform while the icon did not, so the two came apart. placementOf() returns only the translation for 'particles', making the box the upright 24 x 24 that covers its icon. The source's own transform still feeds every particle untouched. 824 checks (821 + 3). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8ae20d044f
commit
78baf0c719
9 files changed
+177
-13
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { colourDistance } from '../lib/harness.mjs';
|
||||
import { LANES } from './lib/scaffold.mjs';
|
||||
|
||||
/**
|
||||
@@ -517,7 +518,58 @@ export async function particles(t) {
|
||||
);
|
||||
await shot('31-particles-scene');
|
||||
|
||||
// 31m. The graph tab draws no Pixi complaint of its own.
|
||||
// 31m. (#89) A source draws only its particles, so an empty one would be invisible: it wears an
|
||||
// icon of its own, in the overlay above the objects, with its name beside it.
|
||||
const icon = await json(`__gm.sceneIcon(${JSON.stringify(SOURCE)})`);
|
||||
check(
|
||||
'the source wears an icon in the overlay, named beside it',
|
||||
icon?.layer === 'overlay' && icon.visible === true && icon.label === 'Fountain',
|
||||
JSON.stringify(icon),
|
||||
);
|
||||
|
||||
// 31n. (#89) The ring is hollow on purpose – the mark may not hide the effect it marks. Scanned
|
||||
// outward from the source's own point, because a 2-unit stroke at an exact radius would be a
|
||||
// test of rounding rather than of drawing.
|
||||
const ICON_COLOUR = '#c26a1f';
|
||||
const unit = (await json(`__gm.objectRect(${JSON.stringify(SOURCE)})`)).w / 24;
|
||||
const row = [];
|
||||
for (let dx = 0; dx <= Math.ceil(19 * unit); dx++)
|
||||
row.push(await json(`__gm.pixelAt(${Math.round(spotOn.x + dx)}, ${Math.round(spotOn.y)})`));
|
||||
const ringAt = row.findIndex((c, dx) => dx > 4 * unit && colourDistance(c, ICON_COLOUR) < 12);
|
||||
check(
|
||||
'the icon rings the source and leaves its middle to the particles',
|
||||
ringAt > 0 && colourDistance(row[0], ICON_COLOUR) > 60 && row[0] !== '#22252c',
|
||||
JSON.stringify({ centre: row[0], unit, ringAt, ring: row[ringAt] }),
|
||||
);
|
||||
|
||||
// 31o. (#89) Parented, it hangs off its owner by a dashed line like a behavior does – otherwise a
|
||||
// source that moves with something else is a mark in mid-air.
|
||||
await mutate(`
|
||||
const scene = __gm.state.activeScene;
|
||||
const anchor = structuredClone(scene.objects[0]);
|
||||
Object.assign(anchor, { id: 'pt-anchor', name: 'Anchor', parentId: '',
|
||||
position: { x: 0, y: ${LANE}, z: 0 }, overrides: {}, instanceEdits: {} });
|
||||
scene.objects.push(anchor);
|
||||
const source = scene.objects.find((o) => o.id === ${JSON.stringify(SOURCE)});
|
||||
source.parentId = 'pt-anchor';
|
||||
source.position = { x: 200, y: 0, z: 0 };`);
|
||||
await sleep(300);
|
||||
const linked = await json(`__gm.sceneIcon(${JSON.stringify(SOURCE)})`);
|
||||
await mutate(`
|
||||
const scene = __gm.state.activeScene;
|
||||
const source = scene.objects.find((o) => o.id === ${JSON.stringify(SOURCE)});
|
||||
source.parentId = '';
|
||||
source.position = { x: 0, y: ${LANE}, z: 0 };
|
||||
scene.objects = scene.objects.filter((o) => o.id !== 'pt-anchor');`);
|
||||
await sleep(300);
|
||||
const unlinked = await json(`__gm.sceneIcon(${JSON.stringify(SOURCE)})`);
|
||||
check(
|
||||
'a parented source hangs off its owner by a line, a free one by none',
|
||||
linked?.link === true && unlinked?.link === false,
|
||||
JSON.stringify({ linked, unlinked }),
|
||||
);
|
||||
|
||||
// 31p. The graph tab draws no Pixi complaint of its own.
|
||||
const logged = await json('__gm.console.lines()');
|
||||
check(
|
||||
'nothing about the particle tab draws a complaint from Pixi',
|
||||
@@ -525,7 +577,7 @@ export async function particles(t) {
|
||||
JSON.stringify(logged.slice(0, 3)),
|
||||
);
|
||||
|
||||
// 31n. The step takes back everything it made, so the steps after it see the fixture again.
|
||||
// 31q. The step takes back everything it made, so the steps after it see the fixture again.
|
||||
await evaluate('__gm.particleTime(null)');
|
||||
await evaluate('__gm.state.select(null)');
|
||||
await mutate(`
|
||||
|
||||
Reference in new issue
Block a user