From 41e95d1078e094d2dbf2237ec1a2c2dc70a6b6be Mon Sep 17 00:00:00 2001 From: agustin-littlehat Date: Fri, 29 May 2026 08:38:19 -0300 Subject: [PATCH] fix(renderer): restore small solid primitives --- AGENTS.md | 6 +++--- packages/core/src/atlas/constants.ts | 8 ++++---- packages/polycss/src/api/createPolyScene.test.ts | 6 +++--- .../render/atlas/solidTrianglePrimitive.test.ts | 2 +- .../polycss/src/render/atlas/stableTriangle.ts | 2 +- packages/polycss/src/render/polyDOM.test.ts | 11 ++++++----- .../src/snapshot/exportPolySceneSnapshot.ts | 10 +++++----- packages/polycss/src/styles/styles.ts | 10 +++++----- packages/react/src/scene/atlas/index.test.tsx | 2 +- .../react/src/scene/atlas/solidTriangleStyle.ts | 14 +++++++------- packages/react/src/styles/styles.test.ts | 6 +++--- packages/react/src/styles/styles.ts | 10 +++++----- packages/vue/src/scene/atlas/index.test.ts | 2 +- packages/vue/src/scene/atlas/solidTriangleStyle.ts | 14 +++++++------- packages/vue/src/styles/styles.test.ts | 6 +++--- packages/vue/src/styles/styles.ts | 10 +++++----- 16 files changed, 60 insertions(+), 59 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f84f3904..9b01a47b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,10 +31,10 @@ Voxel-shaped meshes are the exception to "all polygons stay mounted": meshes wit | Tag | Strategy | When chosen | Paint mechanism | Atlas memory | |---|---|---|---|---| -| `` | **Quads** | Axis-aligned rectangle, or untextured convex quad when the homography passes stability guards on non-Safari engines | `background: currentColor` on a fixed 256px rectangle; affine and projective quads normalize their `matrix3d` to that primitive, with tiny solid bleed on projective quads to overlap antialias seams. Safari-family browsers skip the projective quad path and fall through because transformed projective rectangles composite incorrectly there. | None | -| `` | **Border-shape clipped solid** | Untextured non-rect not caught by the exact corner-shape solid path, on browsers with CSS `border-shape` (Chromium + `pointer:fine` + `hover:hover`) | `border-color: currentColor` on a fixed 256px border-shape primitive, clipped by `border-shape: polygon(...)`; polygon bbox scale and tiny solid bleed are folded into `matrix3d` | None | +| `` | **Quads** | Axis-aligned rectangle, or untextured convex quad when the homography passes stability guards on non-Safari engines | `background: currentColor` on a fixed 64px rectangle; affine and projective quads normalize their `matrix3d` to that primitive, with tiny solid bleed on projective quads to overlap antialias seams. Safari-family browsers skip the projective quad path and fall through because transformed projective rectangles composite incorrectly there. | None | +| `` | **Border-shape clipped solid** | Untextured non-rect not caught by the exact corner-shape solid path, on browsers with CSS `border-shape` (Chromium + `pointer:fine` + `hover:hover`) | `border-color: currentColor` on a fixed 16px border-shape primitive, clipped by `border-shape: polygon(...)`; polygon bbox scale and tiny solid bleed are folded into `matrix3d` | None | | `` | **Atlas slice** | Textured polygons, or untextured non-rect on browsers without `border-shape` | `background-image` slice of packed bitmap on an auto-budgeted fixed primitive (128px for desktop-class `textureQuality="auto"`, 64px for mobile-class `auto` and explicit numeric quality); atlas position/size and `matrix3d` scale are normalized to the slice, shared textured edges get low-alpha atlas pixels repaired during atlas generation, and solid fallbacks get same-color edge bleed to avoid dark alpha fringes | Bounding-rect area | -| `` | **Stable solid triangle / corner-shape solid** | Triangles on non-WebKit engines; or untextured non-triangle polygons whose normalized outline is exactly a rectangle with one or more beveled corners on browsers with CSS `corner-shape` | Triangles use a 256px box with two beveled top corners and `background: currentColor` when CSS `corner-shape` support is present, progressively falling back to the CSS border-color triangle trick. Firefox uses the same 256px border-triangle primitive to avoid large-perspective compositor banding. Exact corner-shape solids use a bare fixed 256px box with inline per-corner radii + `corner-*-shape: bevel` and `background: currentColor`. Tiny solid bleed is folded into `matrix3d`. WebKit/Safari falls through to `` for border triangles because transformed CSS border triangles composite incorrectly there. | None | +| `` | **Stable solid triangle / corner-shape solid** | Triangles on non-WebKit engines; or untextured non-triangle polygons whose normalized outline is exactly a rectangle with one or more beveled corners on browsers with CSS `corner-shape` | Triangles use a 32px box with two beveled top corners and `background: currentColor` when CSS `corner-shape` support is present, progressively falling back to the CSS border-color triangle trick. Firefox uses a 96px border-triangle primitive to avoid large-perspective compositor banding. Exact corner-shape solids use a bare fixed 32px box with inline per-corner radii + `corner-*-shape: bevel` and `background: currentColor`. Tiny solid bleed is folded into `matrix3d`. WebKit/Safari falls through to `` for border triangles because transformed CSS border triangles composite incorrectly there. | None | | `` | **Cast shadow leaf** | Per casting polygon when `castShadow: true`, in either lighting mode. Applies regardless of caster strategy — ``/``/``/`` all produce a `` shadow because only the polygon's outline matters, not its surface. | Same `border-color: currentColor` + `border-shape: polygon(...)` as ``. Dynamic mode chains `var(--shadow-proj)` (driven by `--clx/y/z` + `--shadow-ground-cssz`) so the projection follows the live light vars. Baked mode CPU-bakes the projection into the leaf's inline `matrix3d(...)` and drops back-facing polys from the DOM entirely instead of opacity-gating them. | None | Strategies are ordered cheapest → most expensive. The mesher's job is to maximise `` / `` / `` and minimise `` (see "Meshing implications" below). diff --git a/packages/core/src/atlas/constants.ts b/packages/core/src/atlas/constants.ts index ba863b5d..c6f388fd 100644 --- a/packages/core/src/atlas/constants.ts +++ b/packages/core/src/atlas/constants.ts @@ -44,14 +44,14 @@ export const DEFAULT_MATRIX_DECIMALS = 3; export const DEFAULT_BORDER_SHAPE_DECIMALS = 2; export const DEFAULT_ATLAS_CSS_DECIMALS = 4; export const DECIMAL_SCALES = [1, 10, 100, 1000, 10000, 100000, 1000000]; -export const SOLID_QUAD_CANONICAL_SIZE = 256; -export const SOLID_TRIANGLE_CANONICAL_SIZE = 256; -export const SOLID_TRIANGLE_LARGE_BORDER_CANONICAL_SIZE = 256; +export const SOLID_QUAD_CANONICAL_SIZE = 64; +export const SOLID_TRIANGLE_CANONICAL_SIZE = 32; +export const SOLID_TRIANGLE_LARGE_BORDER_CANONICAL_SIZE = 96; export const ATLAS_CANONICAL_SIZE_EXPLICIT = 64; export const ATLAS_CANONICAL_SIZE_AUTO_DESKTOP = 128; export const BORDER_SHAPE_CENTER_PERCENT = 50; export const BORDER_SHAPE_POINT_EPS = 1e-7; -export const BORDER_SHAPE_CANONICAL_SIZE = 256; +export const BORDER_SHAPE_CANONICAL_SIZE = 16; export const BORDER_SHAPE_BLEED = 0.9; export const CORNER_SHAPE_POINT_EPS = 0.75; export const CORNER_SHAPE_DUPLICATE_EPS = 0.2; diff --git a/packages/polycss/src/api/createPolyScene.test.ts b/packages/polycss/src/api/createPolyScene.test.ts index 7ac6eac3..486679a3 100644 --- a/packages/polycss/src/api/createPolyScene.test.ts +++ b/packages/polycss/src/api/createPolyScene.test.ts @@ -426,11 +426,11 @@ describe("createPolyScene", () => { expect(styleEl?.textContent).toContain("transform-origin: 0 0"); expect(styleEl?.textContent).toContain("backface-visibility: hidden"); expect(styleEl?.textContent).toContain("background-repeat: no-repeat"); - expect(styleEl?.textContent).toContain("width: 256px;"); - expect(styleEl?.textContent).toContain("height: 256px;"); + expect(styleEl?.textContent).toContain("width: 64px;"); + expect(styleEl?.textContent).toContain("height: 64px;"); expect(styleEl?.textContent).toContain("width: var(--polycss-atlas-size, 64px);"); expect(styleEl?.textContent).toContain("height: var(--polycss-atlas-size, 64px);"); - expect(styleEl?.textContent).toContain("border-width: 0 128px 256px 128px;"); + expect(styleEl?.textContent).toContain("border-width: 0 16px 32px 16px;"); expect(styleEl?.textContent).toContain("width: 0;"); expect(styleEl?.textContent).toContain("height: 0;"); }); diff --git a/packages/polycss/src/render/atlas/solidTrianglePrimitive.test.ts b/packages/polycss/src/render/atlas/solidTrianglePrimitive.test.ts index 467f2dab..8143f59c 100644 --- a/packages/polycss/src/render/atlas/solidTrianglePrimitive.test.ts +++ b/packages/polycss/src/render/atlas/solidTrianglePrimitive.test.ts @@ -98,7 +98,7 @@ describe("solid triangle primitive — corner-bevel vs border", () => { expect(result).not.toBeNull(); const element = result!.rendered[0].element; expect(element.className).toBe(""); - expect(element.style.borderWidth).toBe("0px 128px 256px"); + expect(element.style.borderWidth).toBe("0px 48px 96px"); result!.dispose(); }); diff --git a/packages/polycss/src/render/atlas/stableTriangle.ts b/packages/polycss/src/render/atlas/stableTriangle.ts index 9f4d8a6b..374b0db8 100644 --- a/packages/polycss/src/render/atlas/stableTriangle.ts +++ b/packages/polycss/src/render/atlas/stableTriangle.ts @@ -36,7 +36,7 @@ import { applyPolygonDataAttrs, hasPolygonDataAttrs } from "./emit"; import { resolveSolidTrianglePrimitive } from "./strategy"; const DEFAULT_SOLID_SEAM_BLEED = 1.5; -const SOLID_TRIANGLE_BORDER_WIDTH = "0 128px 256px 128px"; +const SOLID_TRIANGLE_BORDER_WIDTH = "0 48px 96px 48px"; type RenderTextureAtlasOptionsWithSeams = RenderTextureAtlasOptions & { seamBleed?: number; diff --git a/packages/polycss/src/render/polyDOM.test.ts b/packages/polycss/src/render/polyDOM.test.ts index c9e8efaf..cb69d771 100644 --- a/packages/polycss/src/render/polyDOM.test.ts +++ b/packages/polycss/src/render/polyDOM.test.ts @@ -15,7 +15,8 @@ import { const ATLAS_CANONICAL_SIZE_EXPLICIT = 64; const ATLAS_CANONICAL_SIZE_AUTO_DESKTOP = 128; -const SOLID_QUAD_CANONICAL_SIZE = 256; +const SOLID_QUAD_CANONICAL_SIZE = 64; +const BORDER_SHAPE_CANONICAL_SIZE = 16; const CORNER_SHAPE_CORPUS = [ ["Bear.glb"], @@ -792,8 +793,8 @@ describe("renderPolygonsWithTextureAtlas", () => { const yScale = Math.hypot(matrix[4], matrix[5], matrix[6]); expect(element.tagName.toLowerCase()).toBe("i"); - expect(xScale).toBeGreaterThan(2 / 256); - expect(yScale).toBeGreaterThan(2 / 256); + expect(xScale).toBeGreaterThan(2 / BORDER_SHAPE_CANONICAL_SIZE); + expect(yScale).toBeGreaterThan(2 / BORDER_SHAPE_CANONICAL_SIZE); expect(element.style.getPropertyValue("border-shape")).toContain("polygon("); result.dispose(); }); @@ -1038,10 +1039,10 @@ describe("renderPolygonsWithTextureAtlas", () => { expect(element.style.height).toBe(""); expect(element.style.getPropertyValue("--polycss-local-w")).toBe(""); expect(element.style.getPropertyValue("--polycss-local-h")).toBe(""); - expect(matrix[0]).toBeGreaterThan(10 / 256); + expect(matrix[0]).toBeGreaterThan(10 / BORDER_SHAPE_CANONICAL_SIZE); expect(matrix[1]).toBeCloseTo(0, 6); expect(matrix[4]).toBeCloseTo(0, 6); - expect(matrix[5]).toBeGreaterThan(1 / 256); + expect(matrix[5]).toBeGreaterThan(1 / BORDER_SHAPE_CANONICAL_SIZE); result.dispose(); }); diff --git a/packages/polycss/src/snapshot/exportPolySceneSnapshot.ts b/packages/polycss/src/snapshot/exportPolySceneSnapshot.ts index b21df459..a3e30a49 100644 --- a/packages/polycss/src/snapshot/exportPolySceneSnapshot.ts +++ b/packages/polycss/src/snapshot/exportPolySceneSnapshot.ts @@ -364,8 +364,8 @@ ${selectorList(features.solidLeafTags)} { parts.push(` .polycss-scene b { background: currentColor; - width: 256px; - height: 256px; + width: 64px; + height: 64px; } `); } @@ -402,8 +402,8 @@ ${selectorList(features.solidLeafTags)} { if (features.leafTags.includes("i")) { parts.push(` .polycss-scene i { - width: 256px; - height: 256px; + width: 16px; + height: 16px; border-color: currentColor; } `); @@ -427,7 +427,7 @@ ${selectorList(features.solidLeafTags)} { box-sizing: content-box; border: 0 solid transparent; border-color: transparent transparent currentColor transparent; - border-width: 0 128px 256px 128px; + border-width: 0 16px 32px 16px; } `); } diff --git a/packages/polycss/src/styles/styles.ts b/packages/polycss/src/styles/styles.ts index 43df34d4..11166ed8 100644 --- a/packages/polycss/src/styles/styles.ts +++ b/packages/polycss/src/styles/styles.ts @@ -105,8 +105,8 @@ const CORE_BASE_STYLES = ` .polycss-scene b { background: currentColor; - width: 256px; - height: 256px; + width: 64px; + height: 64px; } .polycss-mesh.polycss-voxel-mesh > .polycss-voxel-face { @@ -135,8 +135,8 @@ const CORE_BASE_STYLES = ` } .polycss-scene i { - width: 256px; - height: 256px; + width: 16px; + height: 16px; border-color: currentColor; } @@ -152,7 +152,7 @@ const CORE_BASE_STYLES = ` box-sizing: content-box; border: 0 solid transparent; border-color: transparent transparent currentColor transparent; - border-width: 0 128px 256px 128px; + border-width: 0 16px 32px 16px; } /* — dedicated shadow leaf. Same border-shape rendering trick as diff --git a/packages/react/src/scene/atlas/index.test.tsx b/packages/react/src/scene/atlas/index.test.tsx index 7287c95c..ad84435e 100644 --- a/packages/react/src/scene/atlas/index.test.tsx +++ b/packages/react/src/scene/atlas/index.test.tsx @@ -230,7 +230,7 @@ describe("updateStableTriangleDom", () => { }; expect(updateStableTriangleDom(root, [tri])).toBe(true); - expect(leaf.style.borderWidth).toBe("0px 128px 256px"); + expect(leaf.style.borderWidth).toBe("0px 48px 96px"); }); }); diff --git a/packages/react/src/scene/atlas/solidTriangleStyle.ts b/packages/react/src/scene/atlas/solidTriangleStyle.ts index 0035ea20..d737c568 100644 --- a/packages/react/src/scene/atlas/solidTriangleStyle.ts +++ b/packages/react/src/scene/atlas/solidTriangleStyle.ts @@ -28,12 +28,12 @@ export const DEFAULT_AMBIENT_INTENSITY = 0.4; export const BASIS_EPS = 1e-9; // Matches the canonical SOLID_TRIANGLE_BLEED constant. export const SOLID_TRIANGLE_BLEED = 0.75; -const SOLID_TRIANGLE_CANONICAL_SIZE = 256; -const SOLID_TRIANGLE_LARGE_BORDER_CANONICAL_SIZE = 256; -const SOLID_TRIANGLE_LARGE_BORDER_WIDTH = "0 128px 256px 128px"; +const SOLID_TRIANGLE_CANONICAL_SIZE = 32; +const SOLID_TRIANGLE_LARGE_BORDER_CANONICAL_SIZE = 96; +const SOLID_TRIANGLE_LARGE_BORDER_WIDTH = "0 48px 96px 48px"; const CORNER_TRIANGLE_STYLE = { - width: "256px", - height: "256px", + width: "32px", + height: "32px", backgroundColor: "currentColor", borderWidth: "0", borderTopLeftRadius: "50% 100%", @@ -83,8 +83,8 @@ export function solidTrianglePaintStyle(): CSSProperties | undefined { export function applySolidTrianglePaintStyle(el: HTMLElement): void { const primitive = solidTrianglePrimitive(); if (primitive === "corner-bevel") { - el.style.width = "256px"; - el.style.height = "256px"; + el.style.width = "32px"; + el.style.height = "32px"; el.style.backgroundColor = "currentColor"; el.style.borderWidth = "0"; el.style.borderTopLeftRadius = "50% 100%"; diff --git a/packages/react/src/styles/styles.test.ts b/packages/react/src/styles/styles.test.ts index 3f568e96..3d0b6238 100644 --- a/packages/react/src/styles/styles.test.ts +++ b/packages/react/src/styles/styles.test.ts @@ -48,11 +48,11 @@ describe("injectPolyBaseStyles", () => { expect(el.textContent).toContain("transform-origin: 0 0"); expect(el.textContent).toContain("backface-visibility: hidden"); expect(el.textContent).toContain("background-repeat: no-repeat"); - expect(el.textContent).toContain("width: 256px;"); - expect(el.textContent).toContain("height: 256px;"); + expect(el.textContent).toContain("width: 64px;"); + expect(el.textContent).toContain("height: 64px;"); expect(el.textContent).toContain("width: var(--polycss-atlas-size, 64px);"); expect(el.textContent).toContain("height: var(--polycss-atlas-size, 64px);"); - expect(el.textContent).toContain("border-width: 0 128px 256px 128px;"); + expect(el.textContent).toContain("border-width: 0 16px 32px 16px;"); expect(el.textContent).toContain("width: 0;"); expect(el.textContent).toContain("height: 0;"); }); diff --git a/packages/react/src/styles/styles.ts b/packages/react/src/styles/styles.ts index 37d3ed8c..7c50b112 100644 --- a/packages/react/src/styles/styles.ts +++ b/packages/react/src/styles/styles.ts @@ -106,8 +106,8 @@ const CORE_BASE_STYLES = ` .polycss-scene b { background: currentColor; - width: 256px; - height: 256px; + width: 64px; + height: 64px; } .polycss-mesh.polycss-voxel-mesh > .polycss-voxel-face { @@ -136,8 +136,8 @@ const CORE_BASE_STYLES = ` } .polycss-scene i { - width: 256px; - height: 256px; + width: 16px; + height: 16px; border-color: currentColor; } @@ -153,7 +153,7 @@ const CORE_BASE_STYLES = ` box-sizing: content-box; border: 0 solid transparent; border-color: transparent transparent currentColor transparent; - border-width: 0 128px 256px 128px; + border-width: 0 16px 32px 16px; } /* ── Gizmo override ─────────────────────────────────────────────────────── */ diff --git a/packages/vue/src/scene/atlas/index.test.ts b/packages/vue/src/scene/atlas/index.test.ts index d1a6a97d..881a1673 100644 --- a/packages/vue/src/scene/atlas/index.test.ts +++ b/packages/vue/src/scene/atlas/index.test.ts @@ -174,7 +174,7 @@ describe("updateStableTriangleDom", () => { }; expect(updateStableTriangleDom(root, [tri])).toBe(true); - expect(leaf.style.borderWidth).toBe("0px 128px 256px"); + expect(leaf.style.borderWidth).toBe("0px 48px 96px"); }); }); diff --git a/packages/vue/src/scene/atlas/solidTriangleStyle.ts b/packages/vue/src/scene/atlas/solidTriangleStyle.ts index ac8fa3d7..8d802f72 100644 --- a/packages/vue/src/scene/atlas/solidTriangleStyle.ts +++ b/packages/vue/src/scene/atlas/solidTriangleStyle.ts @@ -29,12 +29,12 @@ export const BASIS_EPS = 1e-9; const RECT_EPS = 1e-3; // Matches the canonical SOLID_TRIANGLE_BLEED constant. export const SOLID_TRIANGLE_BLEED = 0.75; -const SOLID_TRIANGLE_CANONICAL_SIZE = 256; -const SOLID_TRIANGLE_LARGE_BORDER_CANONICAL_SIZE = 256; -const SOLID_TRIANGLE_LARGE_BORDER_WIDTH = "0 128px 256px 128px"; +const SOLID_TRIANGLE_CANONICAL_SIZE = 32; +const SOLID_TRIANGLE_LARGE_BORDER_CANONICAL_SIZE = 96; +const SOLID_TRIANGLE_LARGE_BORDER_WIDTH = "0 48px 96px 48px"; const CORNER_TRIANGLE_STYLE = { - width: "256px", - height: "256px", + width: "32px", + height: "32px", backgroundColor: "currentColor", borderWidth: "0", borderTopLeftRadius: "50% 100%", @@ -84,8 +84,8 @@ export function solidTrianglePaintStyle(): CSSProperties | undefined { export function applySolidTrianglePaintStyle(el: HTMLElement): void { const primitive = solidTrianglePrimitive(); if (primitive === "corner-bevel") { - el.style.width = "256px"; - el.style.height = "256px"; + el.style.width = "32px"; + el.style.height = "32px"; el.style.backgroundColor = "currentColor"; el.style.borderWidth = "0"; el.style.borderTopLeftRadius = "50% 100%"; diff --git a/packages/vue/src/styles/styles.test.ts b/packages/vue/src/styles/styles.test.ts index 93a4b722..d163db85 100644 --- a/packages/vue/src/styles/styles.test.ts +++ b/packages/vue/src/styles/styles.test.ts @@ -47,11 +47,11 @@ describe("injectPolyBaseStyles", () => { expect(el.textContent).toContain("transform-origin: 0 0"); expect(el.textContent).toContain("backface-visibility: hidden"); expect(el.textContent).toContain("background-repeat: no-repeat"); - expect(el.textContent).toContain("width: 256px;"); - expect(el.textContent).toContain("height: 256px;"); + expect(el.textContent).toContain("width: 64px;"); + expect(el.textContent).toContain("height: 64px;"); expect(el.textContent).toContain("width: var(--polycss-atlas-size, 64px);"); expect(el.textContent).toContain("height: var(--polycss-atlas-size, 64px);"); - expect(el.textContent).toContain("border-width: 0 128px 256px 128px;"); + expect(el.textContent).toContain("border-width: 0 16px 32px 16px;"); expect(el.textContent).toContain("width: 0;"); expect(el.textContent).toContain("height: 0;"); }); diff --git a/packages/vue/src/styles/styles.ts b/packages/vue/src/styles/styles.ts index 93c1c1f0..c973d8cf 100644 --- a/packages/vue/src/styles/styles.ts +++ b/packages/vue/src/styles/styles.ts @@ -106,8 +106,8 @@ const CORE_BASE_STYLES = ` .polycss-scene b { background: currentColor; - width: 256px; - height: 256px; + width: 64px; + height: 64px; } .polycss-mesh.polycss-voxel-mesh > .polycss-voxel-face { @@ -136,8 +136,8 @@ const CORE_BASE_STYLES = ` } .polycss-scene i { - width: 256px; - height: 256px; + width: 16px; + height: 16px; border-color: currentColor; } @@ -153,7 +153,7 @@ const CORE_BASE_STYLES = ` box-sizing: content-box; border: 0 solid transparent; border-color: transparent transparent currentColor transparent; - border-width: 0 128px 256px 128px; + border-width: 0 16px 32px 16px; } /* ── Dynamic lighting cascade vars (scene root → polygons) ─────────────── */