Give a shader room to draw around the picture it wears (#69)

A shader ran only inside the rectangle of what wears it: the quad
has the size of the sprite, and outside of it no fragment runs. An
outline or a glow built with Sample texture was cut off at the
border. The issue still spoke of Filter.padding, but since #77 the
shader is no filter any more, the limit is the geometry.

- The Output node has a number Padding next to Edge: so many pixels
  of the picture per side the wearer draws beyond it. 0 is the
  default and draws as before, 256 is the most.
- The quad grows and its UVs grow with it, so the picture keeps its
  place and size. In the margin the UVs run on below 0 and above 1.
- Transparent is a fourth Edge mode: outside of the picture there
  is nothing. It is cut in the fragment (texelAt), so it needs no
  copy of the picture with an address mode of its own.
- Edge holds everywhere alike, also for Texture color, Texture
  alpha and unconnected inputs.
- Padding above 0 with Edge on Clamp puts the hint "Padding with
  Clamp smears the border" on the Output. The shader still
  compiles. A chosen Clamp and the default cannot be told apart.
- All four wearers draw the margin. A sprite counts in pixels of
  its frame, a text in its units, a particle source grows every
  particle about its centre.
- A nine-slice sprite gets a ring around its nine fields, as wide
  as so many border pixels. The fields themselves stay.
- Without a picture a pixel is one unit of the wearer's own size,
  or of the particle size.
- Particles now take the Edge mode of their shader. It had no
  effect on them before.
- An object wearing a padded shader is never culled, or its margin
  would vanish at the edge of the screen.
- The preview in the shader tab shows the margin and leaves room
  for it.
- Picking, the selection frame, the size and the collider stay with
  the rectangle without the margin.

Tests:
- Gate on melektaus-atx right before this commit: 216 files,
  3336 tests (3319 + 17), exit 0.
- e2e 28,30,31,37,41 on melektaus-atx: 178 checks, all pass.
  e2e 1-37,47,48,58 ran before the last change (no culling with
  padding): 1281 checks, all pass.
- 17 new unit tests: the compiler (padding, Transparent, the hint)
  and the geometry (quad, nine-slice ring).
- Seven new e2e checks: four at the sprite in module 28, one each
  for text, particles and nine-slice.
- 17 unit counter-probes all fail their tests. Three browser
  counter-probes fail their checks (5, 3 and 1 of them).
- Three screenshots looked at: the sprite with Clamp and margin in
  the scene, the preview in the shader tab, the text.
- The e2e sum of 1687 is counted (1680 + 7), the full suite and
  the smokes did not run.
- Not seen: the hint at the node in a picture, particles and
  nine-slice in a picture, the game view while playing, sprites
  without a picture, vector and pixel art pictures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-02 20:33:20 +02:00
1 parent 46c620ae3b
commit e933fad446
20 files changed
+644 -55

No files matched your search

+24
View File
@@ -608,6 +608,30 @@ export async function particles(t) {
JSON.stringify({ dimmed }),
);
// 31t-ii. (#69) The shader's Padding grows every particle about its own centre. Without a
// picture a pixel is a unit of the particle's size, so 10 on a size of 40 is a quarter
// per side. Counted along the row through the source, in the grey from 31t.
const greyAcross = async () => {
await at(0.1);
const row = await json(`__gm.pixelRow(${Math.round(spotOn.x) - 240}, ${Math.round(spotOn.y)}, 480)`);
return row.filter((hex) => colourDistance(hex, '#404040') < 16).length;
};
const setPadding = (padding) =>
mutate(`
const shader = __gm.state.project.assets.find((a) => a.id === ${JSON.stringify(SHADER)});
shader.graph.nodes.find((n) => n.id === 'lv-out').params.padding = ${padding};`);
const plainRun = await greyAcross();
await setPadding(10);
const paddedRun = await greyAcross();
await shot('31-particles-padded');
await setPadding(0);
const backRun = await greyAcross();
check(
'a shader’s padding grows every particle by a quarter of its size per side, and 0 takes it back',
plainRun > 0 && paddedRun > plainRun + 8 && paddedRun <= plainRun * 1.5 + 4 && backRun === plainRun,
JSON.stringify({ plainRun, paddedRun, backRun }),
);
// 31u. (#88) A source draws rows from two graphs, so one name can arrive twice. The source's own
// row wins and the uniform keeps its default – two rows called Rate would be one row with two
// meanings, which is #63 in another coat.