The tops-only law was blind to the neighbor's top-block SHAPE: two same-height side-by-side wedges sloping OPPOSITE ways each counted the other's sloping side as flush anchor, leaving a bowtie of exposed triangle slivers where the side faces don't overlap. wedgeDirOf() now simulates a neighbor's wedge decision from tops alone (the decision is a pure function of column tops), and a slope is refused when a same-height side neighbor would wedge the opposite way — symmetric, so both cells keep cubes. Same-way pairs (side triangles mate exactly) and perpendicular-away pairs (facing side is the neighbor's full back) remain allowed. Audited 5 seeds x 5 segments: 496 wedges, 0 opposite side-pairs.
- commit
- 736e372f6993c31346bafd7e4bedb531ec6a9e38
- parent
- dd345514c73296302c425a8f5ef1b1abfb375b87
- author
- Dami <dami.etoile@gmail.com>
| M | src/ | 36 | +++++++++++++++++++++++++++++- |
@@ -343,6 +343,27 @@ export function buildIsland(
343343 const diagRot = (k: number): Placement["rotation"] =>344344 ((4 - k) % 4) as Placement["rotation"];345345 346+ /** The slope direction a column would wedge toward under the347+ * flush-or-open law, from tops alone (null = keeps the cube; corner348+ * diagonals also report null). Pure in `cols`, so a cell can ask it of349+ * a NEIGHBOR: the tops-only law is blind to the neighbor's top-block350+ * SHAPE, and two same-height side-by-side wedges sloping OPPOSITE ways351+ * would each treat the other's sloping side as an anchor — leaving a352+ * bowtie of exposed triangle slivers where the faces don't overlap. */353+ const wedgeDirOf = (col: Col | undefined): number | null => {354+ if (!col || col.onRun || col.dist < 1 || col.depth < 1) return null;355+ let openDir = -1;356+ for (let w = 0; w < 4; w++) {357+ const d = dropAt(col, DIRS[w][0], DIRS[w][1]);358+ if (d > 0.01 && d < 0.99) return null;359+ if (d >= 0.99) {360+ if (openDir >= 0) return null;361+ openDir = w;362+ }363+ }364+ return openDir >= 0 ? openDir : null;365+ };366+346367 // PASS 2 — emit blocks with shaping.347368 for (const c of cols.values()) {348369 const cellRng = rng.child(`blocks/${c.wx}/${c.wz}`);@@ -369,8 +390,21 @@ export function buildIsland(
369390 const dirs = open.map(([dx, dz]) => dirOf(dx, dz));370391 const corner = adjacentOpen &&371392 dropAt(c, open[0][0] + open[1][0], open[0][1] + open[1][1]) >= 0.99;393+ // Side-mating: a slope's side triangles only mate with a same-height394+ // neighbor that is a cube, a wedge sloping the SAME way, or a wedge395+ // sloping perpendicular-away (whose facing side is its full back).396+ // An opposite-sloping same-height pair is forbidden (both cells keep397+ // cubes — the check is symmetric).398+ const sideMated = open.length !== 1 ||399+ [1, 3].every((t) => {400+ const w = dirs[0];401+ const s = (w + t) % 4;402+ const n = cols.get(key(c.wx + DIRS[s][0], c.wz + DIRS[s][1]));403+ if (!n || Math.abs(n.topWorld - c.topWorld) > 0.01) return true;404+ return wedgeDirOf(n) !== (w + 2) % 4;405+ });372406 const trim = trimIsland && !isFall && !c.onRun && c.dist >= 1 &&373- c.depth >= 1 && flush &&407+ c.depth >= 1 && flush && sideMated &&374408 (corner || open.length === 1);375409 376410 // One face per waterfall column — water can't zigzag around the