Keep scene view names readable on plates and in rows of their own
game-maker / gate (push) Successful in 2m57s
game-maker / suite (push) Failing after 8m37s

Symbol names in the scene view were plain grey text. On a background
of a similar colour they could not be read, and names of symbols close
together were drawn over each other.

- Each name stands on a rounded, nearly black plate at 70 % opacity,
  3 px padding left and right, 1 px top and bottom.
- The text is light (viewportLabel) instead of muted grey.
- The plate of a selected object gets an accent border.
- New rule stackLabels in src/viewport/labelRows.ts. A name that
  overlaps another name or an icon moves one row down, then one up,
  then two down and so on, until a row is free.
- Names are placed top to bottom, so the upper one keeps its row.
  A name only moves vertically and stays on the side of its icon.
- Names outside the view still claim their row. Otherwise the visible
  names would jump while panning.
- New hook __gm.sceneLabels() for the e2e check.

The frame names in the texture editor already had a plate, so they
are unchanged.

Not covered by tests: the accent border on selection. Readability on a
light background was only judged from a screenshot on a dark one.

Tests:
- 5 unit tests in tests/viewport/label-rows.test.ts: free names stay,
  the lower of two colliding names moves, the stacking order, names
  avoid icons, 30 crowded names never overlap.
- e2e 5c-ii: 8 symbols on one spot give 8 plates, no overlap with each
  other or with icons, each name on the side of its icon.
- Counter-checks: without the overlap test in the rule 4 of 5 unit
  tests fail. With the scene view ignoring the rows exactly 5c-ii
  fails.
- ./check.sh: 161 files, 2369 tests, CHECK EXIT 0.
- e2e 1-5: ALL PASSED, 140 checks. The e2e sum is now 1382 (computed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-09-25 08:05:39 +02:00
1 parent af6ac12ab5
commit 50bddf68d4
6 files changed
+223 -12

No files matched your search

+43 -1
View File
@@ -100,10 +100,52 @@ export async function drawing(t) {
JSON.stringify(wrong.length ? wrong : looks.map((s) => `${s.id} ${s.white}/${s.outline}`)),
);
await shot('15-symbols');
// 5c-ii. Eight symbols on one spot stand on a ring, their names beside them on dark plates. Names
// that would cover each other go a row up or down instead – none overlaps another or an
// icon – and every name stays on its icon's side of the ring.
const crowd = Array.from({ length: 8 }, (_, k) => `s-crowd-${k}`);
await mutate(`
const scene = __gm.state.activeScene;
${MAKE_OBJECT}
const assets = ${JSON.stringify(Object.values(PLACED))};
${JSON.stringify(crowd)}.forEach((id, k) => {
scene.objects.push(make(id, assets[k % assets.length], '', ${LANES.symbols}, 'crowded name ' + k));
});
__gm.lookAt(0, ${LANES.symbols});`);
const plates = await json(`(() => {
const crowd = ${JSON.stringify(crowd)};
const icons = crowd.map((id) => __gm.sceneIcon(id));
const labels = __gm.sceneLabels().filter((l) => crowd.includes(l.id));
return { icons: icons.map((i) => ({ x: i.x, y: i.y, size: i.size })), labels };
})()`);
const cross = (a, b) =>
a.x < b.x + b.w - 1e-6 && b.x < a.x + a.w - 1e-6 && a.y < b.y + b.h - 1e-6 && b.y < a.y + a.h - 1e-6;
const iconBoxes = plates.icons.map((i) => ({
x: i.x - i.size / 2,
y: i.y - i.size / 2,
w: i.size,
h: i.size,
}));
const clashes = [];
plates.labels.forEach((a, i) => {
plates.labels.slice(i + 1).forEach((b) => cross(a, b) && clashes.push(`${a.id}/${b.id}`));
iconBoxes.forEach((box, k) => cross(a, box) && clashes.push(`${a.id}/icon ${k}`));
});
const sides = plates.labels.filter((l) => {
const icon = plates.icons[crowd.indexOf(l.id)];
return l.left ? l.x + l.w > icon.x : l.x < icon.x;
});
check(
'names crowded on one spot stand on plates in rows of their own, on their icons’ side',
plates.labels.length === 8 && clashes.length === 0 && sides.length === 0,
`${plates.labels.length} plates, clashes ${JSON.stringify(clashes)}, wrong side ${JSON.stringify(sides.map((l) => l.id))}`,
);
await shot('15a-crowded-names');
await mutate(`
const p = __gm.state.project;
const scene = __gm.state.activeScene;
scene.objects = scene.objects.filter((o) => !${JSON.stringify(ids)}.includes(o.id));
scene.objects = scene.objects.filter((o) => ![...${JSON.stringify(ids)}, ...${JSON.stringify(crowd)}].includes(o.id));
p.assets = p.assets.filter((a) => !['s-box-a', 's-rb-a', 's-pt-a'].includes(a.id));
__gm.lookAt(0, 0);`);