Pick the frame's outline when adding a collider to a sprite (#218)
game-maker / gate (push) Successful in 2m49s
game-maker / suite (push) Successful in 13m30s

"Add collider" on a sprite always made a box of the sprite's size,
even when the sprite's picture is a frame that has a collision outline.
The outline then had to be picked by hand as "Frame collision".

- newColliderProps in colliderCommands.ts is the rule as a pure
  function. A sprite whose picture is a frame with an outline gets a
  collider with shape "frame" and that frame. Anything else keeps the
  box of the sprite's size.
- The frame is the one the sprite shows when the collider is added,
  also when only this placement overrides it.
- Decided here, the issue says nothing about size: a frame collider is
  as big as its frame, so a sprite drawn bigger or smaller with the
  same proportions gets the collider's scale set to match.
- Decided here as well: a sprite stretched out of the frame's
  proportions keeps the box, because an outline cannot be stretched
  in one direction.

Tests:
- Gate: 198 files, 2914 tests (2911 + 3), exit 0
- e2e 8,42: 46 checks passed (42: +1 for 42e-ii)
- Three new unit tests in tests/core/collider-parts.test.ts: the
  frame with undo, the scale with an overridden frame, and the cases
  that keep the box
- 42e-ii goes through the context menu: "Frame collision" with scale
  2 on the sprite wearing the wedge frame, a box on a sprite without
  a picture
- Five unit counter-probes fail, 1 to 2 tests each. They ran on
  stein2, not in the container.
- The browser counter-probe fails 42e-ii with shape "box"
- The e2e sum of 1617 is calculated (1616 + 1). Neither the full
  suite nor the smokes ran for this state.
- Vector art has no test of its own, it takes the same branch as a
  whole texture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-01 15:03:05 +02:00
1 parent defc34bd09
commit d39bf83ea9
4 files changed
+150 -7

No files matched your search

+55 -3
View File
@@ -1,13 +1,14 @@
import { edge, node } from '../lib/builders.mjs';
import { LANES, MAKE_OBJECT } from './lib/scaffold.mjs';
import { inView, LANES, MAKE_OBJECT } from './lib/scaffold.mjs';
/**
* Capsules and polygons (#151), and the collision outline of a frame that a tile blocks with
* (#141). What only a running editor can show: the corners of a polygon collider are edited on
* its outline in the scene, the engine lets a capsule slide down a polygon ramp, the Texture tab
* draws a frame's outline with clicks, and a painted map blocks with that outline in the game – as
* does a collider that wears the frame's outline (#193), and "Generate collision…" reads that
* outline off the picture (#161).
* does a collider that wears the frame's outline (#193) – which "Add collider…" picks by itself on a
* sprite wearing such a frame (#218) –, and "Generate collision…" reads that outline off the picture
* (#161).
*/
/** A 32 × 32 picture, pasted like a file: the texture the outline is drawn on. */
@@ -502,6 +503,57 @@ export async function shapes(t) {
frameClimb.length > 100 && peak < F - 42 + 1 && peak > F - 42 - 3 && frameClimb.at(-1).x > 45,
JSON.stringify({ frames: frameClimb.length, peak, top: F - 42, last: frameClimb.at(-1) }),
);
// 42e-ii. "Add collider…" on a sprite wearing that frame (#218): the new collider wears the frame's
// outline at once, scaled to the sprite (64 wide, the frame 32); a sprite without a picture
// gets the box. Undo takes each collider and its asset away again.
await mutate(`
const scene = __gm.state.activeScene;
${MAKE_OBJECT}
${put}
put('sh-worn', 'crate', '', 150, ${F - 120}, 'Worn 42', { texture: 'sh-frame', width: 64, height: 64 });
put('sh-plain', 'crate', '', -150, ${F - 120}, 'Plain 42', { width: 64, height: 64 });
__gm.state.select(null)`);
await evaluate(`__gm.lookAt(0, ${F - 60})`);
const addedTo = async (id) => {
await evaluate('__gm.hierarchyExpandAll()');
const row = await inView(t, '__gm.leftColumn().objects', `__gm.hierarchyRow(${JSON.stringify(id)})`);
await click(row.x, row.y, 'right');
await clickText('Add collider…');
await clickText('Create new collider');
await settle();
const s = await t.state();
const placed = s.selection?.kind === 'object' ? await object(s.selection.objectId) : null;
const props = placed ? (await t.asset(placed.assetId))?.props : null;
const rect = placed ? await json(`__gm.objectRect(${JSON.stringify(placed.id)})`) : null;
return {
parentId: placed?.parentId,
shape: props?.shape,
frame: props?.frame,
scale: props?.scale,
rect,
};
};
const assetCount = () => json('__gm.state.project.assets.length');
const assetsBefore = await assetCount();
const worn = await addedTo('sh-worn');
const wornRect = await json(`__gm.objectRect('sh-worn')`);
await shot('42-add-frame-collider');
await undo();
const plain = await addedTo('sh-plain');
await undo();
check(
'Add collider on a sprite wearing a frame with an outline takes "Frame collision" of that frame, sprite-sized',
worn.parentId === 'sh-worn' &&
worn.shape === 'frame' &&
worn.frame === 'sh-frame' &&
worn.scale === 2 &&
Math.abs(worn.rect?.w - wornRect?.w) < 1 &&
plain.parentId === 'sh-plain' &&
plain.shape === 'box' &&
(await assetCount()) === assetsBefore,
JSON.stringify({ worn, wornRect, plain }),
);
await clearShapes();
// 42f. "Generate collision…" reads the outlines off the picture (#161): a wedge and a faint band