Colliders take a frame's outline and tiles have their own surface (#193)
game-maker / gate (push) Successful in 2m11s
game-maker / suite (push) Successful in 7m33s

Issue #193 asked where friction, bounciness and density of a tile
live. Tiles had a fixed surface (friction 0.5, bounciness 0) and a
collider could not use the outline drawn on a texture frame (#141).
Both gaps are closed here.

- A collider has a fifth shape "Frame collision" with a Frame row.
  The frame is picked like a sprite's texture and stored as the
  value 'frame'.
- The shape is linked, not copied. Redrawing the outline in the
  texture tab changes every collider using that frame, in the editor
  and on the next play.
- The frame centre sits on the collider origin, one image pixel is
  one unit. The shape turns, scales and mirrors like any other.
- Every outline ring blocks. Each ring is split into convex parts the
  same way a polygon is.
- Without a frame, with a whole texture or with a frame that has no
  outline, the collider is drawn red as a box and blocks nothing.
- Move, Rapier, raycast, detection, hit test, drawing and footprint
  all resolve the frame. At runtime an entity looks the frame up
  live in the current project.
- A tile has Friction and Bounciness next to Solid and One-way. The
  rows only show when Solid is on. Defaults and mixing rules match
  the collider. Density is left out because a tilemap never moves.
- Tilemap cells are grouped by surface as well as by one-way angle.
  Ice and stone never merge into one rectangle, and every cell brings
  its surface to Move and Rapier.

Findings:

- A tile with a collision scale below 1 is no longer flush with a
  neighbour of another surface. It is inset there like at an open
  side, the same as next to one-way tiles today.
- A bounce test first measured 0.84. The ball had landed on the hero
  at the origin, not on the tile. On the tile it measures 0.993.

Tests:

- ./check.sh: 130 files, 1931 tests, exit 0 (was 129 / 1917).
- 14 new unit tests in tests/core/surface.test.ts. The Move tests run
  against the editor run and the physics system.
- 3 new e2e checks: 40l (surface rows follow Solid), 42e (collider
  has frame size, a walker climbs the slope in the frame).
- GM_STEPS=1-8,39-43 freshly seeded: ALL PASSED, 376 checks.
- Three counter-probes bite: Move ignores the tile surface (2 fail),
  Rapier ignores it (1 fails), frame shape falls back to a box
  (5 fail).
- Check sum 1198 is computed (1195 + 3), the full run belongs to CI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5 committed 2026-09-21 07:29:26 +02:00
1 parent 3ff7e91dbf
commit f4ec23d3d3
20 files changed
+729 -83

No files matched your search

+10
View File
@@ -715,6 +715,9 @@ export async function tilemap(t) {
await openTab(t, 'scene');
await select({ kind: 'asset', assetId: 'at-grass' });
const solidRow = await json("__gm.inspectorField('Solid')");
const surfaceRows = () =>
json("[__gm.inspectorField('Friction'), __gm.inspectorField('Bounciness')].map(Boolean)");
const surfaceBefore = await surfaceRows();
if (solidRow) await click(solidRow.x, solidRow.y);
await settle();
check(
@@ -722,6 +725,13 @@ export async function tilemap(t) {
Boolean(solidRow) && (await t.asset('at-grass'))?.props.solid === true,
JSON.stringify({ solidRow, props: (await t.asset('at-grass'))?.props }),
);
// Its surface (#193) is a solid tile's business: the rows come with the switch.
const surfaceAfter = await surfaceRows();
check(
'Friction and Bounciness show on a tile only once it is solid',
JSON.stringify(surfaceBefore) === '[false,false]' && JSON.stringify(surfaceAfter) === '[true,true]',
JSON.stringify({ surfaceBefore, surfaceAfter }),
);
await mutate(`__gm.lookAt(0, ${LANE})`);
await settle();
const outlined = await json(`__gm.tilemap.solids(${JSON.stringify(MAP_OBJECT)})`);