diff --git a/README.md b/README.md
index b1d296a..9eaa0d0 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,21 @@ pnpm build:3dpipes:full
pnpm dev:3dpipes
```
+### Flower Box
+
+[`src/adapters/flowerbox`](src/adapters/flowerbox) is an independent PolyCSS
+reconstruction of the classic Flower Box. Its rounded prepared cycle uses
+1,200 stable retained triangle leaves, prepared Morph transforms, and a
+hash-bound q60 space-texel lighting atlas.
+
+```sh
+pnpm prepare:flowerbox:artifact
+pnpm build:flowerbox
+pnpm dev:flowerbox
+```
+
+Microsoft source, binaries, captures, and oracle packets are not included.
+
## License
cssGraphics source code is [MIT licensed](LICENSE). Third-party models retain
diff --git a/netlify.toml b/netlify.toml
index f5fa88d..43fb4ef 100644
--- a/netlify.toml
+++ b/netlify.toml
@@ -10,3 +10,8 @@
to = "/pipes/"
status = 302
force = true
+
+[[headers]]
+ for = "/cssflower/assets/*"
+ [headers.values]
+ Cache-Control = "public, max-age=31536000, immutable"
diff --git a/package.json b/package.json
index 3785177..fa682aa 100644
--- a/package.json
+++ b/package.json
@@ -35,20 +35,29 @@
"scripts": {
"dev": "vite --host 127.0.0.1",
"dev:3dpipes": "vite --config src/adapters/3dpipes/vite.config.mjs --host 127.0.0.1",
+ "dev:flowerbox": "vite --config src/adapters/flowerbox/vite.config.mjs --host 127.0.0.1",
"build": "pnpm build:site && pnpm build:lib",
"build:site": "vite build",
"build:lib": "vite build --mode library && node scripts/build-cli.mjs",
"build:3dpipes": "vite build --config src/adapters/3dpipes/vite.config.mjs",
"build:3dpipes:full": "pnpm prepare:3dpipes && pnpm build:3dpipes",
- "build:deploy": "pnpm exec playwright install chromium && pnpm prepare:3dpipes && CSSPIPES_DEPLOY_BUILD=1 pnpm build:3dpipes",
+ "build:flowerbox": "vite build --config src/adapters/flowerbox/vite.config.mjs",
+ "build:flowerbox:full": "pnpm prepare:flowerbox:artifact && pnpm build:flowerbox",
+ "build:deploy": "pnpm exec playwright install chromium && pnpm prepare:3dpipes && pnpm prepare:flowerbox:artifact && CSSPIPES_DEPLOY_BUILD=1 pnpm build:3dpipes && CSSFLOWER_DEPLOY_BUILD=1 pnpm build:flowerbox",
"prepack": "pnpm build:lib",
"preview": "vite preview --host 127.0.0.1",
"prepare:super-mario-64": "pnpm build:lib && node scripts/prepare/super-mario-64.mjs",
"prepare:3dpipes": "node src/adapters/3dpipes/tools/prepare-csspipes.mjs",
+ "prepare:flowerbox:artifact": "node scripts/prepare/flowerbox-product-bank.mjs",
+ "prepare:flowerbox:source": "node src/adapters/flowerbox/tools/prepare-cssflower.mjs && node src/adapters/flowerbox/tools/prepare-polycss-snapshot.mjs",
"prepare:catalog": "pnpm build:lib && node scripts/prepare/catalog.mjs",
"prepare:site-models": "node scripts/prepare/site-models.mjs",
"prepare:site-previews": "pnpm prepare:site-models && node scripts/prepare/site-previews.mjs",
"verify:source-only": "node scripts/verify-source-only.mjs --strict",
+ "verify:flowerbox:bank": "node src/adapters/flowerbox/tools/verify-product-bank.mjs",
+ "verify:flowerbox:runtime": "node src/adapters/flowerbox/tools/audit-runtime-surface.mjs",
+ "test:flowerbox:browser": "node src/adapters/flowerbox/tools/smoke-browser.mjs",
+ "test:flowerbox:deploy": "node src/adapters/flowerbox/tools/smoke-browser.mjs --deploy",
"typecheck": "tsc --noEmit"
},
"dependencies": {
diff --git a/scripts/prepare/flowerbox-product-bank.mjs b/scripts/prepare/flowerbox-product-bank.mjs
new file mode 100644
index 0000000..a47dc1a
--- /dev/null
+++ b/scripts/prepare/flowerbox-product-bank.mjs
@@ -0,0 +1,101 @@
+#!/usr/bin/env node
+
+import { createHash } from "node:crypto";
+import { spawnSync } from "node:child_process";
+import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
+import { dirname, join, resolve } from "node:path";
+import { fileURLToPath } from "node:url";
+import { inspectFlowerboxProductBank } from "../../src/adapters/flowerbox/tools/productBank.mjs";
+
+const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
+const lock = JSON.parse(await readFile(
+ join(repositoryRoot, "src/adapters/flowerbox/prepared-bank.lock.json"),
+ "utf8",
+));
+if (lock.schema !== "cssflower-prepared-bank-lock@3" ||
+ lock.retainedTriangleLeafCount !== 1_200 || lock.retainedRotationRootCount !== 1 ||
+ lock.timelineStateCount !== 360 || lock.geometryStateCount !== 46 ||
+ lock.transformBlockCount !== 3 || lock.lightingAssetCount !== 1 ||
+ lock.lightingQuality !== 60 || lock.visibilityMinimumOwnedPixels !== 8) {
+ throw new Error("Flower Box prepared-bank lock does not bind the rounded retained Morph product");
+}
+const generatedRoot = resolve(
+ process.env.CSSFLOWER_GENERATED_ROOT ?? join(repositoryRoot, "build", "generated"),
+);
+const publicRoot = join(generatedRoot, "public");
+const targetRoot = join(publicRoot, "cssflower");
+
+try {
+ const current = await inspectFlowerboxProductBank(targetRoot);
+ const descriptorBytes = await readFile(join(targetRoot, "product-bank.json"));
+ if (current.closureSha256 === lock.productClosureSha256 &&
+ current.closureBytes === lock.productClosureBytes &&
+ current.fileCount === lock.productFileCount &&
+ descriptorBytes.length === lock.productDescriptorByteLength &&
+ sha256(descriptorBytes) === lock.productDescriptorSha256) {
+ process.stdout.write(`${JSON.stringify({ status: "ready", source: "existing", ...current }, null, 2)}\n`);
+ process.exit(0);
+ }
+} catch {
+ // Missing or stale output is replaced only after the downloaded bank verifies.
+}
+
+const localArchive = process.env.CSSFLOWER_PRODUCT_BANK_ARCHIVE;
+const cacheRoot = join(repositoryRoot, ".local", "downloads", "cssflower");
+const archivePath = localArchive
+ ? resolve(localArchive)
+ : join(cacheRoot, `${lock.archiveSha256}.tar.gz`);
+let archiveBytes;
+try {
+ archiveBytes = await readFile(archivePath);
+} catch (error) {
+ if (localArchive || error?.code !== "ENOENT") throw error;
+ const response = await fetch(lock.url, { redirect: "follow" });
+ if (!response.ok) throw new Error(`Flower Box product bank download failed: ${response.status}`);
+ archiveBytes = Buffer.from(await response.arrayBuffer());
+ await mkdir(cacheRoot, { recursive: true });
+ await writeFile(archivePath, archiveBytes);
+}
+if (archiveBytes.length !== lock.archiveByteLength || sha256(archiveBytes) !== lock.archiveSha256) {
+ throw new Error("Flower Box product bank archive identity mismatch");
+}
+
+const listing = spawnSync("tar", ["-tzf", archivePath], { encoding: "utf8" });
+if (listing.error) throw listing.error;
+if (listing.status !== 0) throw new Error(`Unable to inspect Flower Box product bank:\n${listing.stderr}`);
+const entries = listing.stdout.trim().split("\n").filter(Boolean);
+if (entries.length === 0 || entries.some((entry) =>
+ entry.startsWith("/") || entry.split("/").includes("..") ||
+ !(entry === "cssflower" || entry === "cssflower/" || entry.startsWith("cssflower/")))) {
+ throw new Error("Flower Box product bank archive contains an unsafe path");
+}
+
+const stagingRoot = join(generatedRoot, `.flowerbox-product-${process.pid}`);
+await rm(stagingRoot, { recursive: true, force: true });
+await mkdir(stagingRoot, { recursive: true });
+const extraction = spawnSync("tar", ["-xzf", archivePath, "-C", stagingRoot], { encoding: "utf8" });
+if (extraction.error) throw extraction.error;
+if (extraction.status !== 0) throw new Error(`Unable to extract Flower Box product bank:\n${extraction.stderr}`);
+try {
+ const stagedProduct = join(stagingRoot, "cssflower");
+ const summary = await inspectFlowerboxProductBank(stagedProduct);
+ const descriptorBytes = await readFile(join(stagedProduct, "product-bank.json"));
+ if (summary.closureSha256 !== lock.productClosureSha256 || summary.closureBytes !== lock.productClosureBytes ||
+ summary.fileCount !== lock.productFileCount) {
+ throw new Error("Flower Box unpacked product bank identity mismatch");
+ }
+ if (descriptorBytes.length !== lock.productDescriptorByteLength ||
+ sha256(descriptorBytes) !== lock.productDescriptorSha256) {
+ throw new Error("Flower Box product-bank descriptor identity mismatch");
+ }
+ await mkdir(publicRoot, { recursive: true });
+ await rm(targetRoot, { recursive: true, force: true });
+ await rename(stagedProduct, targetRoot);
+ process.stdout.write(`${JSON.stringify({ status: "ready", source: localArchive ? "local-archive" : "release", ...summary }, null, 2)}\n`);
+} finally {
+ await rm(stagingRoot, { recursive: true, force: true });
+}
+
+function sha256(bytes) {
+ return createHash("sha256").update(bytes).digest("hex");
+}
diff --git a/scripts/verify-source-only.mjs b/scripts/verify-source-only.mjs
index 6080dd1..eab5acf 100644
--- a/scripts/verify-source-only.mjs
+++ b/scripts/verify-source-only.mjs
@@ -15,7 +15,9 @@ const STRICT_SOURCE_RENDERER_PATTERNS = Object.freeze([
["wasm-runtime", /WebAssembly\.(?:compile|instantiate)|instantiateStreaming\(|\.wasm(?:\b|["'])/iu],
["emulator-runtime", /(?:from|import\s*)[\s\S]{0,80}["'][^"']*(?:dosbox|mupen64|retroarch|emulator)[^"']*["']/iu],
]);
-const STRICT_AUDITOR_PATHS = new Set();
+const STRICT_AUDITOR_PATHS = new Set([
+ "src/adapters/flowerbox/tools/audit-runtime-surface.mjs",
+]);
const STRICT_PATH_GUARD_PATHS = new Set([
"scripts/verify-source-only.mjs",
]);
@@ -34,9 +36,12 @@ const DISTRIBUTION_PREFIXES = Object.freeze([
"site/public/previews/",
]);
const README_MEDIA_PATHS = new Set([
+ "site/public/favicon.ico",
"site/readme/animated-morph-sphere.gif",
"site/readme/cube-to-sphere.gif",
"site/readme/pipes.gif",
+ "src/adapters/3dpipes/public/pipes-social.png",
+ "src/adapters/flowerbox/public/flower-social.png",
]);
const SHA256 = /^[a-f0-9]{64}$/u;
diff --git a/site/public/sitemap.xml b/site/public/sitemap.xml
index f5f56e1..8df1557 100644
--- a/site/public/sitemap.xml
+++ b/site/public/sitemap.xml
@@ -6,4 +6,7 @@
https://css.graphics/pipes/
+
+ https://css.graphics/flower/
+
diff --git a/src/adapters/flowerbox/README.md b/src/adapters/flowerbox/README.md
new file mode 100644
index 0000000..1e240bf
--- /dev/null
+++ b/src/adapters/flowerbox/README.md
@@ -0,0 +1,38 @@
+# Flower Box
+
+An independently authored PolyCSS reconstruction of the classic 1995 Flower
+Box. The rounded default-cube bloom and complete rotation cycle are rendered
+through 1,200 stable retained HTML triangle leaves and one retained rotation
+root.
+
+The browser loads one prepared PolyCSS snapshot, three prepared matrix3d
+blocks, and one q60 prepared space-texel lighting grid. PolyCSS Morph writes
+only selected prepared leaf transforms; a prepared source-camera visibility
+schedule suppresses cells owning fewer than eight pixels. The runtime does not
+construct geometry, project vertices, calculate normals or lighting,
+rasterize, or grow the DOM. The canonical page is the responsive `/` route;
+there is no separate presentation or oracle mode.
+
+From the repository root:
+
+```sh
+pnpm prepare:flowerbox:artifact
+pnpm build:flowerbox
+pnpm dev:flowerbox
+```
+
+The public rounded q60 product bank is a content-addressed build artifact. Its
+lock binds the archive and unpacked closure; generated output remains ignored
+under `build/generated/`. A full source preparation is available separately
+through `pnpm prepare:flowerbox:source` when the pinned `avifenc` is supplied.
+
+The source profile is bound to `DigitalMars/dmc` revision
+`9478d25a677f70dbe4fc0ed317cc5a5e5050ef8b`. Exact native-state qualification
+was performed locally across 9,331 ticks. That evidence and the owned native
+inputs are not part of the product bank; native/browser pixel parity is not
+claimed.
+
+The implementation is covered by the repository's [MIT license](../../../LICENSE).
+Microsoft source, binaries, native captures, and oracle packets are not
+included or downloaded. This independent experiment is not affiliated with or
+endorsed by Microsoft.
diff --git a/src/adapters/flowerbox/index.html b/src/adapters/flowerbox/index.html
new file mode 100644
index 0000000..13df2c6
--- /dev/null
+++ b/src/adapters/flowerbox/index.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+ Flower Box — HTML and CSS experiment
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/adapters/flowerbox/prepared-bank.lock.json b/src/adapters/flowerbox/prepared-bank.lock.json
new file mode 100644
index 0000000..804c6e8
--- /dev/null
+++ b/src/adapters/flowerbox/prepared-bank.lock.json
@@ -0,0 +1,30 @@
+{
+ "schema": "cssflower-prepared-bank-lock@3",
+ "tag": "cssflower-product-rounded-q60-v1",
+ "asset": "cssflower-product-rounded-q60-v1.tar.gz",
+ "url": "https://github.com/layoutit/cssGraphics/releases/download/cssflower-product-rounded-q60-v1/cssflower-product-rounded-q60-v1.tar.gz",
+ "archiveByteLength": 5312404,
+ "archiveSha256": "8ad850187c297ab8f1671a29f656f4eb098d8370219183e4b37f9e6c4b1fe9e5",
+ "productClosureBytes": 5324102,
+ "productClosureSha256": "f7e8f9144b859fc34ccbc74a980e07180154ece23eb0783f85f92d6470655b83",
+ "productFileCount": 7,
+ "productDescriptorByteLength": 1886,
+ "productDescriptorSha256": "2418737362c5c50f2f245dabef45571b002fc279ca165863e352d2f91d517758",
+ "retainedTriangleLeafCount": 1200,
+ "retainedRotationRootCount": 1,
+ "timelineStateCount": 360,
+ "geometryStateCount": 46,
+ "retainedSourceOracleTimelineStateCount": 9331,
+ "transformBlockCount": 3,
+ "transformAssetBytes": 1347149,
+ "lightingAssetCount": 1,
+ "lightingAssetBytes": 3848891,
+ "lightingQuality": 60,
+ "visibilityMinimumOwnedPixels": 8,
+ "publicBoundary": {
+ "microsoftSourceIncluded": false,
+ "microsoftBinaryIncluded": false,
+ "nativeCaptureIncluded": false,
+ "oraclePacketIncluded": false
+ }
+}
diff --git a/src/adapters/flowerbox/public/flower-social.png b/src/adapters/flowerbox/public/flower-social.png
new file mode 100644
index 0000000..6f42891
Binary files /dev/null and b/src/adapters/flowerbox/public/flower-social.png differ
diff --git a/src/adapters/flowerbox/src/cssflower/client.mjs b/src/adapters/flowerbox/src/cssflower/client.mjs
new file mode 100644
index 0000000..25d80b9
--- /dev/null
+++ b/src/adapters/flowerbox/src/cssflower/client.mjs
@@ -0,0 +1,86 @@
+import {
+ loadPreparedManifest,
+ loadPreparedScene,
+} from "./manifestClient.mjs";
+import { mountPreparedPolycssSnapshot } from "./polycssScene.mjs";
+import { createCssflowerPreparedPlayer } from "./preparedPlayback.mjs";
+import { createRouteState } from "./routeState.mjs";
+import { installCssflowerDebugApi } from "./debugApi.mjs";
+import { installCssflowerStagePresentation } from "./stagePresentation.mjs";
+
+export function mountCssflowerClient(host) {
+ if (!(host instanceof HTMLElement)) throw new TypeError("cssFlower scene host is missing");
+ const state = {
+ ready: false,
+ status: "loading",
+ route: null,
+ manifest: null,
+ sceneData: null,
+ mount: null,
+ errors: [],
+ };
+ installCssflowerDebugApi(state);
+
+ window.addEventListener("error", (event) => {
+ recordError(state, host, event.message || String(event.error || "error"));
+ });
+ window.addEventListener("unhandledrejection", (event) => {
+ recordError(state, host, String(event.reason?.message || event.reason || "unhandled rejection"));
+ });
+
+ main().catch((error) => {
+ recordError(state, host, error.stack || error.message || String(error));
+ });
+
+ async function main() {
+ const route = createRouteState();
+ state.route = route;
+
+ const manifest = await loadPreparedManifest(route);
+ state.manifest = manifest;
+
+ const { entry, sceneData, snapshotHtml, preparedAssets } = await loadPreparedScene(manifest, route);
+ state.sceneData = sceneData;
+
+ const snapshot = mountPreparedPolycssSnapshot({ host, sceneData, snapshotHtml, preparedAssets });
+ const presentation = installCssflowerStagePresentation({ host, camera: snapshot.camera });
+ const player = await createCssflowerPreparedPlayer({
+ playback: sceneData.playback,
+ lighting: sceneData.lighting,
+ rotationRoot: snapshot.rotationRoot,
+ mesh: snapshot.mesh,
+ leaves: snapshot.leaves,
+ transformBlocks: preparedAssets.transformBlocks,
+ lightingPages: preparedAssets.lightingPages,
+ });
+ state.mount = Object.freeze({
+ ...snapshot,
+ player,
+ stats() {
+ return Object.freeze({
+ ...player.stats(),
+ ...snapshot.stats(),
+ ...presentation.stats(),
+ });
+ },
+ destroy() {
+ presentation.destroy();
+ player.destroy();
+ preparedAssets.transformBlocks.destroy();
+ preparedAssets.lightingPages.destroy();
+ snapshot.destroy();
+ },
+ });
+ state.ready = true;
+ state.status = "ready";
+ host.classList.add("r");
+ requestAnimationFrame(() => player.resume());
+ }
+}
+
+function recordError(state, host, message) {
+ state.errors.push(message);
+ state.status = "error";
+ host.classList.remove("r");
+ host.classList.add("e");
+}
diff --git a/src/adapters/flowerbox/src/cssflower/debugApi.mjs b/src/adapters/flowerbox/src/cssflower/debugApi.mjs
new file mode 100644
index 0000000..a26eb8f
--- /dev/null
+++ b/src/adapters/flowerbox/src/cssflower/debugApi.mjs
@@ -0,0 +1,54 @@
+export function installCssflowerDebugApi(state) {
+ const api = {
+ get ready() {
+ return state.ready;
+ },
+ get status() {
+ return state.status;
+ },
+ get manifest() {
+ return state.manifest;
+ },
+ get scene() {
+ return state.sceneData;
+ },
+ get route() {
+ return state.route;
+ },
+ errors() {
+ return [...state.errors];
+ },
+ stats() {
+ return state.mount?.stats?.() ?? null;
+ },
+ pause() {
+ return state.mount?.player?.pause?.() ?? null;
+ },
+ resume() {
+ return state.mount?.player?.resume?.() ?? null;
+ },
+ step(count = 1) {
+ return state.mount?.player?.step?.(count) ?? null;
+ },
+ setTick(tick) {
+ return state.mount?.player?.setTick?.(tick) ?? null;
+ },
+ sample() {
+ return state.mount?.player?.sample?.() ?? null;
+ },
+ nodes() {
+ return state.mount?.player?.nodes?.() ?? null;
+ },
+ assertStableDomIdentity() {
+ state.mount?.assertStableDomIdentity?.();
+ state.mount?.player?.assertStableDomIdentity?.();
+ return true;
+ },
+ meshes() {
+ const stats = state.mount?.stats?.();
+ return stats ? [{ id: "flower-box-default-cube", polygons: stats.retainedTriangleLeafCount }] : [];
+ },
+ };
+ globalThis.__cssFlowerDebug = api;
+ return api;
+}
diff --git a/src/adapters/flowerbox/src/cssflower/manifestClient.mjs b/src/adapters/flowerbox/src/cssflower/manifestClient.mjs
new file mode 100644
index 0000000..d88894f
--- /dev/null
+++ b/src/adapters/flowerbox/src/cssflower/manifestClient.mjs
@@ -0,0 +1,296 @@
+import {
+ sceneEntryForRoute,
+ routeSceneLabel,
+} from "./routeState.mjs";
+import {
+ createPreparedLightingPageLoader,
+ createPreparedTransformBlockLoader,
+ validatePreparedMorphAssets,
+} from "./preparedAssetLoaders.mjs";
+import {
+ CSSFLOWER_BOUNDARY_SEAM_BLEED,
+ CSSFLOWER_LIGHTING_ATLAS_HEIGHT,
+ CSSFLOWER_LIGHTING_ATLAS_WIDTH,
+ CSSFLOWER_LIGHTING_GUTTER,
+ CSSFLOWER_LIGHTING_GRID_COLUMNS,
+ CSSFLOWER_LIGHTING_GRID_HEIGHT,
+ CSSFLOWER_LIGHTING_GRID_ROWS,
+ CSSFLOWER_LIGHTING_GRID_WIDTH,
+ CSSFLOWER_LIGHTING_LAYOUT,
+ CSSFLOWER_LIGHTING_PAGE_COUNT,
+ CSSFLOWER_LIGHTING_PAGE_ROWS,
+ CSSFLOWER_LIGHTING_RASTER_MODE,
+ CSSFLOWER_LIGHTING_SAMPLING,
+ CSSFLOWER_LIGHTING_SCHEMA,
+ CSSFLOWER_LIGHTING_STATE_SLICE_HEIGHT,
+ CSSFLOWER_FRONT_FACE_DILATION_TICKS,
+ CSSFLOWER_FRONT_FACE_SCHEDULE_ENCODING,
+ CSSFLOWER_FRONT_FACE_SCHEDULE_SCHEMA,
+ CSSFLOWER_SEAM_BLEED,
+ CSSFLOWER_SEAM_BLEED_POLICY,
+ CSSFLOWER_VISIBILITY_POLICY,
+} from "./renderContract.mjs";
+
+export async function loadPreparedManifest(routeState) {
+ const manifest = await fetchJson(routeState.manifestUrl, {
+ notFoundMessage: "Missing generated cssFlower — Microsoft Flower Box manifest at " + routeState.manifestUrl + ". Run pnpm prepare:cssflower first.",
+ });
+ if (manifest?.schema !== "cssflower-manifest@1" || manifest?.status !== "ready") {
+ throw new Error("Generated cssFlower — Microsoft Flower Box manifest is not ready (" + (manifest?.status ?? "missing status") + "). Run pnpm prepare:cssflower first.");
+ }
+ return manifest;
+}
+
+export async function loadPreparedScene(manifest, routeState) {
+ const entry = sceneEntryForRoute(manifest, routeState);
+ if (!entry || typeof entry.sceneUrl !== "string") {
+ throw new Error("Generated cssFlower — Microsoft Flower Box manifest does not include " + routeSceneLabel(routeState) + ". Run pnpm prepare:cssflower first.");
+ }
+ const sceneData = await fetchJson(entry.sceneUrl, {
+ notFoundMessage: "Missing generated cssFlower — Microsoft Flower Box scene at " + entry.sceneUrl + ". Run pnpm prepare:cssflower first.",
+ });
+ const snapshotHtml = typeof entry.snapshotUrl === "string" && entry.snapshotUrl
+ ? await fetchText(entry.snapshotUrl, {
+ notFoundMessage: "Missing generated cssFlower — Microsoft Flower Box PolyCSS snapshot at " + entry.snapshotUrl + ". Run pnpm prepare:cssflower first.",
+ })
+ : null;
+ if (sceneData?.schema !== "cssflower-prepared-scene@1" ||
+ sceneData?.playback?.schema !== "cssflower-prepared-playback@1" ||
+ sceneData?.renderer?.morphTarget !== "createPolyMorphPreparedDomTarget" ||
+ sceneData?.renderer?.stableDom !== true ||
+ sceneData?.renderer?.seamBleed !== CSSFLOWER_SEAM_BLEED ||
+ sceneData?.renderer?.boundarySeamBleed !== CSSFLOWER_BOUNDARY_SEAM_BLEED ||
+ sceneData?.renderer?.seamBleedPolicy !== CSSFLOWER_SEAM_BLEED_POLICY ||
+ sceneData?.renderer?.seamBleedSharedEdgeCount !== 1680 ||
+ sceneData?.renderer?.seamBleedBoundaryEdgeCount !== 240 ||
+ sceneData?.renderer?.seamBleedBoundaryVertexCount !== 240 ||
+ sceneData?.renderer?.seamBleedBoundaryAdjacentTriangleCount !== 432 ||
+ sceneData?.renderer?.merge !== false ||
+ sceneData?.lighting?.schema !== CSSFLOWER_LIGHTING_SCHEMA ||
+ sceneData?.lighting?.physicalLayout !== CSSFLOWER_LIGHTING_LAYOUT ||
+ sceneData?.lighting?.rasterMode !== CSSFLOWER_LIGHTING_RASTER_MODE ||
+ sceneData?.lighting?.sampling !== CSSFLOWER_LIGHTING_SAMPLING ||
+ sceneData?.lighting?.gutter !== CSSFLOWER_LIGHTING_GUTTER ||
+ sceneData?.lighting?.stateSliceHeight !== CSSFLOWER_LIGHTING_STATE_SLICE_HEIGHT ||
+ sceneData?.lighting?.atlasWidth !== CSSFLOWER_LIGHTING_ATLAS_WIDTH ||
+ sceneData?.lighting?.atlasHeight !== CSSFLOWER_LIGHTING_ATLAS_HEIGHT ||
+ sceneData?.lighting?.gridColumns !== CSSFLOWER_LIGHTING_GRID_COLUMNS ||
+ sceneData?.lighting?.gridRows !== CSSFLOWER_LIGHTING_GRID_ROWS ||
+ sceneData?.lighting?.gridWidth !== CSSFLOWER_LIGHTING_GRID_WIDTH ||
+ sceneData?.lighting?.gridHeight !== CSSFLOWER_LIGHTING_GRID_HEIGHT ||
+ sceneData?.lighting?.faceCount !== 1200 ||
+ sceneData?.lighting?.timelineRowCount !== 360 ||
+ sceneData?.lighting?.pageRowCount !== CSSFLOWER_LIGHTING_PAGE_ROWS ||
+ sceneData?.lighting?.pageCount !== CSSFLOWER_LIGHTING_PAGE_COUNT ||
+ sceneData?.lighting?.pages?.length !== CSSFLOWER_LIGHTING_PAGE_COUNT ||
+ sceneData?.lighting?.leafSizing !== "raster" ||
+ sceneData?.lighting?.boundarySeamBleed !== CSSFLOWER_BOUNDARY_SEAM_BLEED ||
+ sceneData?.lighting?.seamBleedPolicy !== CSSFLOWER_SEAM_BLEED_POLICY ||
+ sceneData?.lighting?.boundaryVertexCount !== 240 ||
+ sceneData?.lighting?.boundaryAdjacentTriangleCount !== 432 ||
+ sceneData?.lighting?.sharedEdgeIncidenceCount !== 3360 ||
+ sceneData?.lighting?.boundaryEdgeIncidenceCount !== 240 ||
+ sceneData?.lighting?.faces?.length !== 1200 ||
+ !sceneData?.lighting?.faces?.every((face, index) =>
+ face?.sourceOrder === index &&
+ Number.isSafeInteger(face.leafWidth) && face.leafWidth >= 2 &&
+ Number.isSafeInteger(face.leafHeight) && face.leafHeight >= 2 &&
+ face.tileWidth === face.leafWidth &&
+ face.tileHeight === face.leafHeight &&
+ Number.isSafeInteger(face.contentX) && Number.isSafeInteger(face.contentY) &&
+ face.contentX >= CSSFLOWER_LIGHTING_GUTTER &&
+ face.contentX + face.tileWidth <= CSSFLOWER_LIGHTING_ATLAS_WIDTH - CSSFLOWER_LIGHTING_GUTTER &&
+ face.contentY >= CSSFLOWER_LIGHTING_GUTTER &&
+ face.contentY + face.tileHeight <= CSSFLOWER_LIGHTING_STATE_SLICE_HEIGHT - CSSFLOWER_LIGHTING_GUTTER &&
+ typeof face.backgroundSize === "string" &&
+ face.backgroundPositionX === `${-face.contentX}px` &&
+ face.backgroundPositionY === `${-face.contentY}px` &&
+ Number.isSafeInteger(face?.seamEdgeMask) && face.seamEdgeMask >= 1 && face.seamEdgeMask <= 7 &&
+ (face.boundaryAdjacent === true
+ ? face.seamBleed === CSSFLOWER_BOUNDARY_SEAM_BLEED
+ : face.boundaryAdjacent === false && face.seamBleed === CSSFLOWER_SEAM_BLEED)) ||
+ sceneData?.lighting?.backgroundPositionYs?.length !== CSSFLOWER_LIGHTING_PAGE_ROWS ||
+ sceneData?.lighting?.backgroundPositionXs?.length !== CSSFLOWER_LIGHTING_PAGE_COUNT ||
+ !validPreparedLightingAddressSchedule(sceneData?.lighting?.addressSchedule) ||
+ sceneData?.lighting?.rowSelection !== "prepared-exact-rgb8-sparse-leaf-address-schedule" ||
+ sceneData?.lighting?.temporalInterpolation !== false ||
+ sceneData?.lighting?.runtimeRootFrameVariables !== 1 ||
+ !validPreparedFrontFacingSchedule(sceneData?.playback?.frontFacingSchedule) ||
+ sceneData?.metrics?.preparedLeafCount !== 1200 ||
+ sceneData?.metrics?.preparedRootCount !== 1 ||
+ sceneData?.metrics?.runtimePolygonConstructionCount !== 0 ||
+ sceneData?.metrics?.runtimeRadialProjectionCount !== 0 ||
+ sceneData?.metrics?.runtimeNormalCalculationCount !== 0 ||
+ sceneData?.metrics?.runtimeLightingCalculationCount !== 0 ||
+ sceneData?.metrics?.runtimeDomGrowth !== false) {
+ throw new Error("Generated cssFlower scene is not the retained default-cube contract.");
+ }
+ if (!snapshotHtml || entry.snapshot?.schema !== "cssflower-retained-snapshot-contract@1") {
+ throw new Error("Generated cssFlower retained snapshot binding is missing. Run pnpm prepare:cssflower first.");
+ }
+ const addressBytes = decodePreparedLightingAddressBytes(sceneData.lighting.addressSchedule);
+ const frontFacingBytes = decodePreparedFrontFacingBytes(sceneData.playback.frontFacingSchedule);
+ await assertSha256(
+ addressBytes,
+ sceneData.lighting.addressSchedule.faceIndicesSha256,
+ "exact sparse-lighting address schedule",
+ );
+ await assertSha256(
+ frontFacingBytes,
+ sceneData.playback.frontFacingSchedule.dataSha256,
+ "front-face transform schedule",
+ );
+ await assertSha256(new TextEncoder().encode(snapshotHtml), entry.snapshot.sha256, "snapshot");
+ validatePreparedMorphAssets(sceneData.playback, sceneData.lighting);
+ const transformBlocks = createPreparedTransformBlockLoader(sceneData.playback.transformAsset);
+ const lightingPages = createPreparedLightingPageLoader(sceneData.lighting);
+ const initialState = sceneData.playback.cycle.states[0];
+ try {
+ await Promise.all([
+ transformBlocks.prime(
+ initialState.geometryStateIndex,
+ initialState.nextTransformBlockGeometryStateIndex,
+ ),
+ lightingPages.prime(initialState.lightingPageIndex, initialState.nextLightingPageIndex),
+ ]);
+ } catch (error) {
+ transformBlocks.destroy();
+ lightingPages.destroy();
+ throw error;
+ }
+ return {
+ entry,
+ sceneData,
+ snapshotHtml,
+ preparedAssets: Object.freeze({ transformBlocks, lightingPages }),
+ };
+}
+
+function validPreparedLightingAddressSchedule(schedule) {
+ return schedule?.schema === "cssflower-prepared-exact-sparse-lighting-address-schedule@1" &&
+ schedule.stateCount === 360 && schedule.faceCount === 1_200 && schedule.threshold === 0 &&
+ schedule.selectionDomain === "prepared-source-vertex-lighting-rgb8" &&
+ schedule.comparison === "exact-three-canonical-point-rgb8-signature-per-retained-triangle" &&
+ schedule.cycleBoundaryPolicy === "force-all-faces-to-state-zero-on-each-360-state-wrap" &&
+ Number.isSafeInteger(schedule.updateCount) && schedule.updateCount >= 1_200 &&
+ Number.isFinite(schedule.meanUpdatesPerState) && schedule.meanUpdatesPerState > 0 &&
+ Number.isSafeInteger(schedule.p95UpdatesPerState) && schedule.p95UpdatesPerState >= 0 &&
+ Number.isSafeInteger(schedule.maximumUpdatesPerState) && schedule.maximumUpdatesPerState === 1_200 &&
+ schedule.offsets?.length === 361 && schedule.offsets[0] === 0 && schedule.offsets[1] === 1_200 &&
+ schedule.offsets.at(-1) === schedule.updateCount && schedule.offsets.every((value, index) =>
+ Number.isSafeInteger(value) && value >= 0 && (index === 0 || value >= schedule.offsets[index - 1])) &&
+ schedule.faceIndicesEncoding === "base64-u16le-state-major-updated-face-indices" &&
+ schedule.faceIndicesByteLength === schedule.updateCount * 2 &&
+ /^[a-f0-9]{64}$/u.test(schedule.faceIndicesSha256 ?? "") &&
+ typeof schedule.faceIndicesBase64 === "string" && schedule.faceIndicesBase64.length > 0 &&
+ schedule.runtimeSelection === "prepared-state-range-only-no-lighting-or-geometry-calculation";
+}
+
+function validPreparedFrontFacingSchedule(schedule) {
+ return schedule?.schema === CSSFLOWER_FRONT_FACE_SCHEDULE_SCHEMA &&
+ schedule.stateCount === 360 && schedule.faceCount === 1_200 &&
+ schedule.dilationTicks === CSSFLOWER_FRONT_FACE_DILATION_TICKS &&
+ schedule.selectionDomain === CSSFLOWER_VISIBILITY_POLICY.selectionDomain &&
+ schedule.depthComparison === CSSFLOWER_VISIBILITY_POLICY.depthComparison &&
+ schedule.minimumOwnedPixels === CSSFLOWER_VISIBILITY_POLICY.minimumOwnedPixels &&
+ schedule.sampleGrid === CSSFLOWER_VISIBILITY_POLICY.sampleGrid &&
+ schedule.adjacency === CSSFLOWER_VISIBILITY_POLICY.adjacency &&
+ schedule.adjacencyRings === CSSFLOWER_VISIBILITY_POLICY.adjacencyRings &&
+ schedule.dilationPolicy === CSSFLOWER_VISIBILITY_POLICY.dilationPolicy &&
+ schedule.encoding === CSSFLOWER_FRONT_FACE_SCHEDULE_ENCODING &&
+ schedule.bytesPerState === 150 && schedule.byteLength === schedule.stateCount * schedule.bytesPerState &&
+ Number.isSafeInteger(schedule.selectedFaceCount) && schedule.selectedFaceCount > 0 &&
+ schedule.selectedFaceCount < schedule.stateCount * schedule.faceCount &&
+ schedule.suppressedFaceCount === schedule.stateCount * schedule.faceCount - schedule.selectedFaceCount &&
+ schedule.meanSelectedFacesPerState === schedule.selectedFaceCount / schedule.stateCount &&
+ Number.isSafeInteger(schedule.minimumSelectedFacesPerState) && schedule.minimumSelectedFacesPerState > 0 &&
+ Number.isSafeInteger(schedule.maximumSelectedFacesPerState) &&
+ schedule.maximumSelectedFacesPerState >= schedule.minimumSelectedFacesPerState &&
+ schedule.maximumSelectedFacesPerState <= schedule.faceCount &&
+ schedule.initialVisibilitySelectionCount === schedule.faceCount &&
+ Number.isSafeInteger(schedule.visibilityChangeCount) && schedule.visibilityChangeCount > 0 &&
+ /^[a-f0-9]{64}$/u.test(schedule.dataSha256 ?? "") &&
+ typeof schedule.dataBase64 === "string" && schedule.dataBase64.length > 0 &&
+ schedule.runtimeSelection === "prepared-bit-test-only-no-geometry-projection-normal-or-lighting-calculation";
+}
+
+function decodePreparedFrontFacingBytes(schedule) {
+ let encoded;
+ try {
+ encoded = atob(schedule.dataBase64);
+ } catch (error) {
+ throw new Error(`Generated cssFlower front-face schedule encoding is invalid: ${error.message}`);
+ }
+ if (encoded.length !== schedule.byteLength) {
+ throw new Error("Generated cssFlower front-face schedule byte length drifted.");
+ }
+ return Uint8Array.from(encoded, (character) => character.charCodeAt(0));
+}
+
+function decodePreparedLightingAddressBytes(schedule) {
+ let encoded;
+ try {
+ encoded = atob(schedule.faceIndicesBase64);
+ } catch (error) {
+ throw new Error(`Generated cssFlower sparse-lighting address encoding is invalid: ${error.message}`);
+ }
+ if (encoded.length !== schedule.faceIndicesByteLength) {
+ throw new Error("Generated cssFlower sparse-lighting address byte length drifted.");
+ }
+ return Uint8Array.from(encoded, (character) => character.charCodeAt(0));
+}
+
+async function fetchJson(url, { notFoundMessage = "" } = {}) {
+ const response = await fetch(url);
+ if (!response.ok) {
+ if (response.status === 404 && notFoundMessage) throw new Error(notFoundMessage);
+ throw new Error("Failed to load " + url + ": " + response.status);
+ }
+ if (url.endsWith(".gz")) {
+ const decoded = await decodedGzipResponseBytes(response);
+ try {
+ return JSON.parse(new TextDecoder().decode(decoded));
+ } catch (error) {
+ throw new Error(`Prepared cssFlower gzip JSON ${url} is invalid: ${error.message}`);
+ }
+ }
+ const contentType = response.headers.get("content-type") ?? "";
+ if (!contentType.includes("application/json")) {
+ throw new Error(notFoundMessage || ("Expected JSON from " + url + " but got " + (contentType || "unknown content type")));
+ }
+ return response.json();
+}
+
+async function fetchText(url, { notFoundMessage = "" } = {}) {
+ const response = await fetch(url);
+ if (!response.ok) {
+ if (response.status === 404 && notFoundMessage) throw new Error(notFoundMessage);
+ throw new Error("Failed to load " + url + ": " + response.status);
+ }
+ if (url.endsWith(".gz")) {
+ return new TextDecoder().decode(await decodedGzipResponseBytes(response));
+ }
+ return response.text();
+}
+
+async function decompressGzip(bytes) {
+ if (typeof DecompressionStream !== "function") {
+ throw new Error("This browser cannot decode prepared cssFlower gzip assets.");
+ }
+ const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("gzip"));
+ return new Uint8Array(await new Response(stream).arrayBuffer());
+}
+
+async function decodedGzipResponseBytes(response) {
+ const bytes = new Uint8Array(await response.arrayBuffer());
+ return (response.headers.get("content-encoding") ?? "").toLowerCase().includes("gzip")
+ ? bytes
+ : decompressGzip(bytes);
+}
+
+async function assertSha256(bytes, expected, label) {
+ if (!/^[a-f0-9]{64}$/.test(expected ?? "")) throw new Error(`Generated cssFlower ${label} hash is missing.`);
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
+ const actual = [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
+ if (actual !== expected) throw new Error(`Generated cssFlower ${label} identity mismatch (${actual}).`);
+}
diff --git a/src/adapters/flowerbox/src/cssflower/polycssScene.mjs b/src/adapters/flowerbox/src/cssflower/polycssScene.mjs
new file mode 100644
index 0000000..2c236e2
--- /dev/null
+++ b/src/adapters/flowerbox/src/cssflower/polycssScene.mjs
@@ -0,0 +1,140 @@
+import { collectPolyRenderStats } from "@layoutit/polycss";
+
+let preparedStyle = null;
+
+export function mountPreparedPolycssSnapshot({ host, sceneData, snapshotHtml, preparedAssets }) {
+ if (!(host instanceof HTMLElement)) throw new Error("Missing cssFlower host.");
+ const doc = new DOMParser().parseFromString(snapshotHtml, "text/html");
+ if (doc.querySelector("script, canvas, svg")) {
+ throw new Error("Prepared PolyCSS snapshot contains a forbidden runtime/render element.");
+ }
+ if ([...doc.querySelectorAll("*")].some((element) =>
+ element.getAttributeNames().some((name) => name.startsWith("data-")))) {
+ throw new Error("Prepared PolyCSS snapshot contains forbidden data attributes.");
+ }
+
+ const styleElement = doc.querySelector("style");
+ const preparedLeafRuleCount = (styleElement?.textContent.match(/\.polycss-mesh>u\.[a-zA-Z]{1,2} \{/gu) ?? []).length;
+ const cameraElement = doc.body.firstElementChild;
+ const sceneElement = cameraElement?.firstElementChild;
+ const rootElement = sceneElement?.firstElementChild;
+ const meshElement = rootElement?.firstElementChild;
+ if (!(styleElement instanceof HTMLStyleElement) || !(cameraElement instanceof HTMLElement) ||
+ !cameraElement.classList.contains("polycss-camera") || !(sceneElement instanceof HTMLElement) ||
+ !sceneElement.classList.contains("polycss-scene") || !(rootElement instanceof HTMLElement) ||
+ !(meshElement instanceof HTMLElement) || !meshElement.classList.contains("polycss-mesh") ||
+ preparedLeafRuleCount !== 1200 || !preparedAssets?.lightingPages?.urlFor) {
+ throw new Error("Prepared cssFlower direct PolyCSS hierarchy is missing.");
+ }
+
+ removePreparedSnapshotStyles();
+ const importedStyle = document.importNode(styleElement, true);
+ document.head.appendChild(importedStyle);
+ preparedStyle = importedStyle;
+ const importedCamera = document.importNode(cameraElement, true);
+ const importedScene = importedCamera.firstElementChild;
+ const importedRoot = importedScene?.firstElementChild;
+ const importedMesh = importedRoot?.firstElementChild;
+ if (!(importedRoot instanceof HTMLElement) || !(importedMesh instanceof HTMLElement)) {
+ throw new Error("Prepared cssFlower retained root is missing.");
+ }
+ importedRoot.style.setProperty(
+ "--cssflower-space-texels",
+ `url("${preparedAssets.lightingPages.urlFor(0)}")`,
+ );
+ host.replaceChildren(importedCamera);
+
+ const camera = host.firstElementChild;
+ const scene = camera?.firstElementChild;
+ const rotationRoot = scene?.firstElementChild;
+ const mesh = rotationRoot?.firstElementChild;
+ if (!(camera instanceof HTMLElement) || !camera.classList.contains("polycss-camera") ||
+ !(scene instanceof HTMLElement) || !scene.classList.contains("polycss-scene") ||
+ !(rotationRoot instanceof HTMLElement) || !(mesh instanceof HTMLElement) ||
+ !mesh.classList.contains("polycss-mesh")) {
+ throw new Error("Prepared cssFlower camera, scene, root, or mesh is missing.");
+ }
+
+ const leaves = [...mesh.children];
+ const triangleIds = sceneData.lighting.faces.map((face) => face.triangleId);
+ const retainedLeafClasses = leaves.map((leaf) => leaf.className);
+ if (leaves.length !== 1200 || triangleIds.length !== 1200 || new Set(triangleIds).size !== 1200 ||
+ new Set(retainedLeafClasses).size !== 1200 ||
+ scene.children.length !== 1 || rotationRoot.children.length !== 1) {
+ throw new Error(`Prepared cssFlower retained target count drifted (${leaves.length} leaves).`);
+ }
+ for (let index = 0; index < leaves.length; index += 1) {
+ const leaf = leaves[index];
+ const face = sceneData.lighting.faces[index];
+ if (!(leaf instanceof HTMLElement) || leaf.tagName !== "U" ||
+ !/^[a-zA-Z]{1,2}$/u.test(leaf.className) ||
+ leaf.style.length !== 0 ||
+ !Number.isSafeInteger(face.leafWidth) || !Number.isSafeInteger(face.leafHeight) ||
+ face.sourceOrder !== index || face.triangleId !== triangleIds[index]) {
+ throw new Error(`Prepared cssFlower retained leaf ${index} is not source-addressable by prepared order.`);
+ }
+ }
+
+ const stableNodes = Object.freeze([camera, scene, rotationRoot, mesh, ...leaves]);
+ let runtimeDomCreationCount = 0;
+ let runtimeDomRemovalCount = 0;
+ const observer = new MutationObserver((records) => {
+ for (const record of records) {
+ runtimeDomCreationCount += record.addedNodes.length;
+ runtimeDomRemovalCount += record.removedNodes.length;
+ }
+ });
+ observer.observe(host, { childList: true, subtree: true });
+
+ function assertStableDomIdentity() {
+ const currentCamera = host.firstElementChild;
+ const currentScene = currentCamera?.firstElementChild;
+ const currentRoot = currentScene?.firstElementChild;
+ const currentMesh = currentRoot?.firstElementChild;
+ const currentLeaves = currentMesh ? [...currentMesh.children] : [];
+ if (currentCamera !== stableNodes[0] || currentScene !== stableNodes[1] ||
+ currentRoot !== stableNodes[2] || currentMesh !== stableNodes[3] ||
+ currentLeaves.length !== leaves.length ||
+ currentLeaves.some((leaf, index) => leaf !== stableNodes[index + 4])) {
+ throw new Error("Prepared cssFlower retained DOM identity changed.");
+ }
+ return true;
+ }
+
+ return Object.freeze({
+ camera,
+ scene,
+ mesh,
+ rotationRoot,
+ leaves: Object.freeze(leaves),
+ triangleIds: Object.freeze([...triangleIds]),
+ assertStableDomIdentity,
+ stats() {
+ assertStableDomIdentity();
+ const polycss = collectPolyRenderStats(rotationRoot, {
+ polygonCount: sceneData.metrics.preparedLeafCount,
+ });
+ return Object.freeze({
+ mode: "prepared-snapshot",
+ retainedRotationRootCount: 1,
+ retainedTriangleLeafCount: leaves.length,
+ retainedTriangleIdCount: triangleIds.length,
+ runtimeDomCreationCount,
+ runtimeDomRemovalCount,
+ runtimeDomMutationCount: runtimeDomCreationCount + runtimeDomRemovalCount,
+ runtimeDomGrowth: false,
+ polycss,
+ });
+ },
+ destroy() {
+ observer.disconnect();
+ host.replaceChildren();
+ removePreparedSnapshotStyles();
+ },
+ });
+}
+
+function removePreparedSnapshotStyles() {
+ preparedStyle?.remove();
+ preparedStyle = null;
+}
diff --git a/src/adapters/flowerbox/src/cssflower/preparedAssetLoaders.mjs b/src/adapters/flowerbox/src/cssflower/preparedAssetLoaders.mjs
new file mode 100644
index 0000000..1bac73b
--- /dev/null
+++ b/src/adapters/flowerbox/src/cssflower/preparedAssetLoaders.mjs
@@ -0,0 +1,423 @@
+import {
+ CSSFLOWER_LIGHTING_ATLAS_ENCODING,
+ CSSFLOWER_LIGHTING_ATLAS_MIME_TYPE,
+ CSSFLOWER_LIGHTING_ATLAS_QUALITY,
+ CSSFLOWER_LIGHTING_GRID_COLUMNS,
+ CSSFLOWER_LIGHTING_GRID_DECODED_BYTES,
+ CSSFLOWER_LIGHTING_GRID_HEIGHT,
+ CSSFLOWER_LIGHTING_GRID_ROWS,
+ CSSFLOWER_LIGHTING_GRID_WIDTH,
+ CSSFLOWER_LIGHTING_PAGE_COUNT,
+ CSSFLOWER_FRONT_FACE_DILATION_TICKS,
+ CSSFLOWER_FRONT_FACE_SCHEDULE_ENCODING,
+ CSSFLOWER_FRONT_FACE_SCHEDULE_SCHEMA,
+ CSSFLOWER_TRANSFORM_BLOCK_GEOMETRY_STATES,
+ CSSFLOWER_TRANSFORM_BLOCK_SCHEMA,
+ CSSFLOWER_VISIBILITY_POLICY,
+} from "./renderContract.mjs";
+
+export function validatePreparedMorphAssets(playback, lighting) {
+ const transforms = playback?.transformAsset;
+ const frontFacing = playback?.frontFacingSchedule;
+ if (frontFacing?.schema !== CSSFLOWER_FRONT_FACE_SCHEDULE_SCHEMA ||
+ frontFacing.stateCount !== playback?.cycle?.stateCount || frontFacing.stateCount !== 360 ||
+ frontFacing.faceCount !== 1_200 ||
+ frontFacing.dilationTicks !== CSSFLOWER_FRONT_FACE_DILATION_TICKS ||
+ frontFacing.selectionDomain !== CSSFLOWER_VISIBILITY_POLICY.selectionDomain ||
+ frontFacing.depthComparison !== CSSFLOWER_VISIBILITY_POLICY.depthComparison ||
+ frontFacing.minimumOwnedPixels !== CSSFLOWER_VISIBILITY_POLICY.minimumOwnedPixels ||
+ frontFacing.sampleGrid !== CSSFLOWER_VISIBILITY_POLICY.sampleGrid ||
+ frontFacing.adjacency !== CSSFLOWER_VISIBILITY_POLICY.adjacency ||
+ frontFacing.adjacencyRings !== CSSFLOWER_VISIBILITY_POLICY.adjacencyRings ||
+ frontFacing.dilationPolicy !== CSSFLOWER_VISIBILITY_POLICY.dilationPolicy ||
+ frontFacing.encoding !== CSSFLOWER_FRONT_FACE_SCHEDULE_ENCODING ||
+ frontFacing.bytesPerState !== Math.ceil(frontFacing.faceCount / 8) ||
+ frontFacing.byteLength !== frontFacing.stateCount * frontFacing.bytesPerState ||
+ !Number.isSafeInteger(frontFacing.selectedFaceCount) || frontFacing.selectedFaceCount < 1 ||
+ frontFacing.selectedFaceCount >= frontFacing.stateCount * frontFacing.faceCount ||
+ frontFacing.suppressedFaceCount !== frontFacing.stateCount * frontFacing.faceCount - frontFacing.selectedFaceCount ||
+ frontFacing.meanSelectedFacesPerState !== frontFacing.selectedFaceCount / frontFacing.stateCount ||
+ !Number.isSafeInteger(frontFacing.minimumSelectedFacesPerState) || frontFacing.minimumSelectedFacesPerState < 1 ||
+ !Number.isSafeInteger(frontFacing.maximumSelectedFacesPerState) ||
+ frontFacing.maximumSelectedFacesPerState > frontFacing.faceCount ||
+ frontFacing.maximumSelectedFacesPerState < frontFacing.minimumSelectedFacesPerState ||
+ frontFacing.initialVisibilitySelectionCount !== frontFacing.faceCount ||
+ !Number.isSafeInteger(frontFacing.visibilityChangeCount) || frontFacing.visibilityChangeCount < 1 ||
+ !/^[a-f0-9]{64}$/u.test(frontFacing.dataSha256 ?? "") ||
+ typeof frontFacing.dataBase64 !== "string" || frontFacing.dataBase64.length < 1 ||
+ frontFacing.runtimeSelection !== "prepared-bit-test-only-no-geometry-projection-normal-or-lighting-calculation") {
+ throw new Error("Complete prepared cssFlower owned-pixel visibility transform schedule is required");
+ }
+ if (transforms?.schema !== CSSFLOWER_TRANSFORM_BLOCK_SCHEMA ||
+ transforms.distribution !== "public-independent-prepared-transform-blocks" ||
+ transforms.encoding !== "gzip-newline-utf8-geometry-state-major-triangle-major-matrix3d" ||
+ transforms.componentCount !== 16 || transforms.triangleCount !== 1_200 ||
+ transforms.geometryStateCount !== playback?.cycle?.geometryStateCount ||
+ transforms.blockGeometryStateCount !== CSSFLOWER_TRANSFORM_BLOCK_GEOMETRY_STATES ||
+ transforms.blockCount !== Math.ceil(transforms.geometryStateCount / transforms.blockGeometryStateCount) ||
+ transforms.blocks?.length !== transforms.blockCount) {
+ throw new Error("Complete prepared cssFlower Morph transform blocks are required");
+ }
+ let coveredGeometryStates = 0;
+ for (let blockIndex = 0; blockIndex < transforms.blocks.length; blockIndex += 1) {
+ const block = transforms.blocks[blockIndex];
+ const expectedStateCount = Math.min(
+ transforms.blockGeometryStateCount,
+ transforms.geometryStateCount - coveredGeometryStates,
+ );
+ if (block?.index !== blockIndex || block.startGeometryStateIndex !== coveredGeometryStates ||
+ block.geometryStateCount !== expectedStateCount || block.triangleCount !== 1_200 ||
+ block.transformCount !== expectedStateCount * 1_200 ||
+ !validAssetDescriptor(block, "/cssflower/assets/transforms/")) {
+ throw new Error(`Prepared cssFlower transform block ${blockIndex} is invalid`);
+ }
+ coveredGeometryStates += expectedStateCount;
+ }
+ if (coveredGeometryStates !== transforms.geometryStateCount ||
+ transforms.byteLength !== transforms.blocks.reduce((sum, block) => sum + block.byteLength, 0) ||
+ transforms.decodedByteLength !== transforms.blocks.reduce((sum, block) => sum + block.decodedByteLength, 0)) {
+ throw new Error("Prepared cssFlower transform block coverage is incomplete");
+ }
+
+ if (lighting?.visualEncoding?.encoding !== CSSFLOWER_LIGHTING_ATLAS_ENCODING ||
+ lighting.visualEncoding.mimeType !== CSSFLOWER_LIGHTING_ATLAS_MIME_TYPE ||
+ lighting.visualEncoding.quality !== CSSFLOWER_LIGHTING_ATLAS_QUALITY ||
+ lighting.visualEncoding.exactGeometry !== true ||
+ lighting.visualEncoding.exactPreparedPixels !== false ||
+ lighting.pageCount !== CSSFLOWER_LIGHTING_PAGE_COUNT || lighting.pages?.length !== lighting.pageCount ||
+ lighting.grid?.schema !== "cssflower-prepared-leaf-lighting-grid@1" ||
+ lighting.grid.encoding !== CSSFLOWER_LIGHTING_ATLAS_ENCODING ||
+ lighting.grid.mimeType !== CSSFLOWER_LIGHTING_ATLAS_MIME_TYPE ||
+ lighting.grid.quality !== CSSFLOWER_LIGHTING_ATLAS_QUALITY ||
+ lighting.grid.columns !== CSSFLOWER_LIGHTING_GRID_COLUMNS ||
+ lighting.grid.rows !== CSSFLOWER_LIGHTING_GRID_ROWS ||
+ lighting.grid.width !== CSSFLOWER_LIGHTING_GRID_WIDTH ||
+ lighting.grid.height !== CSSFLOWER_LIGHTING_GRID_HEIGHT ||
+ lighting.grid.decodedBytes !== CSSFLOWER_LIGHTING_GRID_DECODED_BYTES ||
+ !validAssetDescriptor(lighting.grid, "/cssflower/assets/lighting/grid-")) {
+ throw new Error("Complete prepared cssFlower leaf-lighting grid is required");
+ }
+ let coveredTimelineStates = 0;
+ for (let pageIndex = 0; pageIndex < lighting.pages.length; pageIndex += 1) {
+ const page = lighting.pages[pageIndex];
+ const expectedRows = Math.min(lighting.pageRowCount, lighting.timelineRowCount - coveredTimelineStates);
+ if (page?.index !== pageIndex || page.startStateIndex !== coveredTimelineStates ||
+ page.usedRowCount !== expectedRows || page.rowCount !== lighting.pageRowCount ||
+ page.width !== lighting.atlasWidth || page.height !== lighting.atlasHeight ||
+ page.decodedBytes !== page.width * page.height * 4 ||
+ page.gridColumn !== pageIndex || page.gridRow !== 0 ||
+ page.gridOffsetX !== pageIndex * lighting.atlasWidth || page.gridOffsetY !== 0 ||
+ page.sourceEncoding !== "PNG-RGB8" ||
+ !Number.isSafeInteger(page.sourcePngByteLength) || page.sourcePngByteLength < 1 ||
+ !/^[a-f0-9]{64}$/u.test(page.sourcePngSha256 ?? "")) {
+ throw new Error(`Prepared cssFlower lighting page ${pageIndex} is invalid`);
+ }
+ coveredTimelineStates += expectedRows;
+ }
+ if (coveredTimelineStates !== lighting.timelineRowCount) {
+ throw new Error("Prepared cssFlower lighting page coverage is incomplete");
+ }
+ if (playback.cycle.states.some((state) => {
+ const expectedBlockIndex = Math.floor(
+ state.geometryStateIndex / transforms.blockGeometryStateCount,
+ );
+ const nextBlockIndex = Math.floor(
+ state.nextTransformBlockGeometryStateIndex / transforms.blockGeometryStateCount,
+ );
+ return state.transformBlockIndex !== expectedBlockIndex ||
+ !Number.isSafeInteger(state.nextTransformBlockGeometryStateIndex) ||
+ state.nextTransformBlockGeometryStateIndex < 0 ||
+ state.nextTransformBlockGeometryStateIndex >= transforms.geometryStateCount ||
+ nextBlockIndex === expectedBlockIndex ||
+ !Number.isSafeInteger(state.lightingPageIndex) || state.lightingPageIndex < 0 ||
+ state.lightingPageIndex >= lighting.pageCount ||
+ !Number.isSafeInteger(state.lightingPageRowIndex) || state.lightingPageRowIndex < 0 ||
+ state.lightingPageRowIndex >= lighting.pageRowCount ||
+ !Number.isSafeInteger(state.nextLightingPageIndex) || state.nextLightingPageIndex < 0 ||
+ state.nextLightingPageIndex >= lighting.pageCount ||
+ state.nextLightingPageIndex === state.lightingPageIndex;
+ })) {
+ throw new Error("Prepared cssFlower asset prefetch schedule is incomplete");
+ }
+}
+
+export function createPreparedTransformBlockLoader(transforms) {
+ const records = new Map();
+ const errors = [];
+ let currentBlockIndex = 0;
+ let loadCount = 0;
+ let releaseCount = 0;
+ let residentDecodedBytes = 0;
+ let peakResidentDecodedBytes = 0;
+ let desiredBlockIndices = new Set();
+ let destroyed = false;
+
+ async function ensure(blockIndex) {
+ if (destroyed) throw new Error("Prepared cssFlower transform loader is destroyed");
+ const block = transforms.blocks[blockIndex];
+ if (!block || block.index !== blockIndex) throw new RangeError(`Prepared transform block ${blockIndex} is missing`);
+ const existing = records.get(blockIndex);
+ if (existing?.transforms) return existing;
+ if (existing?.promise) return existing.promise;
+ const promise = (async () => {
+ const encoded = new Uint8Array(await fetchBytes(block.assetUrl));
+ if (encoded.byteLength !== block.byteLength) throw new Error(`Prepared transform block ${blockIndex} byte length drifted`);
+ await assertSha256(encoded, block.sha256, `transform block ${blockIndex}`);
+ const decoded = await decompressGzip(encoded);
+ if (decoded.byteLength !== block.decodedByteLength) throw new Error(`Prepared transform block ${blockIndex} decoded length drifted`);
+ await assertSha256(decoded, block.decodedSha256, `decoded transform block ${blockIndex}`);
+ const text = new TextDecoder().decode(decoded);
+ if (!text.endsWith("\n")) throw new Error(`Prepared transform block ${blockIndex} is not newline terminated`);
+ const rows = text.slice(0, -1).split("\n");
+ if (rows.length !== block.transformCount || rows.some((row) => !/^matrix3d\([^)]+\)$/u.test(row))) {
+ throw new Error(`Prepared transform block ${blockIndex} rows are invalid`);
+ }
+ const record = Object.freeze({ blockIndex, transforms: Object.freeze(rows) });
+ records.set(blockIndex, record);
+ loadCount += 1;
+ residentDecodedBytes += block.decodedByteLength;
+ peakResidentDecodedBytes = Math.max(peakResidentDecodedBytes, residentDecodedBytes);
+ if (destroyed || !desiredBlockIndices.has(blockIndex)) release(blockIndex, record);
+ return record;
+ })();
+ records.set(blockIndex, { promise });
+ try {
+ return await promise;
+ } catch (error) {
+ if (records.get(blockIndex)?.promise === promise) records.delete(blockIndex);
+ errors.push(String(error?.message || error));
+ throw error;
+ }
+ }
+
+ function release(blockIndex, record) {
+ if (!record?.transforms || records.get(blockIndex) !== record) return;
+ records.delete(blockIndex);
+ residentDecodedBytes -= transforms.blocks[blockIndex].decodedByteLength;
+ releaseCount += 1;
+ }
+
+ function releaseExcept(indices) {
+ desiredBlockIndices = new Set(indices);
+ for (const [blockIndex, record] of records) {
+ if (!indices.has(blockIndex)) release(blockIndex, record);
+ }
+ }
+
+ function blockIndexForGeometryState(geometryStateIndex) {
+ if (!Number.isSafeInteger(geometryStateIndex) || geometryStateIndex < 0 ||
+ geometryStateIndex >= transforms.geometryStateCount) {
+ throw new RangeError(`Prepared geometry state ${geometryStateIndex} is invalid`);
+ }
+ return Math.floor(geometryStateIndex / transforms.blockGeometryStateCount);
+ }
+
+ function prefetch(blockIndex) {
+ if (records.get(blockIndex)?.transforms) return;
+ void ensure(blockIndex).catch(() => undefined);
+ }
+
+ return Object.freeze({
+ async prime(geometryStateIndex, nextGeometryStateIndex) {
+ const current = blockIndexForGeometryState(geometryStateIndex);
+ const next = blockIndexForGeometryState(nextGeometryStateIndex);
+ currentBlockIndex = current;
+ releaseExcept(new Set([current, next]));
+ await Promise.all([...new Set([current, next])].map(ensure));
+ },
+ async activate(geometryStateIndex, nextGeometryStateIndex) {
+ const target = blockIndexForGeometryState(geometryStateIndex);
+ const next = blockIndexForGeometryState(nextGeometryStateIndex);
+ releaseExcept(new Set([currentBlockIndex, target, next]));
+ const record = await ensure(target);
+ currentBlockIndex = target;
+ prefetch(next);
+ return record;
+ },
+ forEachTransform(geometryStateIndex, visit) {
+ if (typeof visit !== "function") throw new TypeError("Prepared transform visitor is required");
+ const blockIndex = blockIndexForGeometryState(geometryStateIndex);
+ const record = records.get(blockIndex);
+ if (!record?.transforms) throw new Error(`Prepared transform block ${blockIndex} is not decoded`);
+ const localState = geometryStateIndex - transforms.blocks[blockIndex].startGeometryStateIndex;
+ const start = localState * transforms.triangleCount;
+ for (let leafIndex = 0; leafIndex < transforms.triangleCount; leafIndex += 1) {
+ visit(record.transforms[start + leafIndex], leafIndex);
+ }
+ },
+ transformAt(geometryStateIndex, leafIndex) {
+ if (!Number.isSafeInteger(leafIndex) || leafIndex < 0 || leafIndex >= transforms.triangleCount) {
+ throw new RangeError(`Prepared transform leaf ${leafIndex} is invalid`);
+ }
+ const blockIndex = blockIndexForGeometryState(geometryStateIndex);
+ const record = records.get(blockIndex);
+ if (!record?.transforms) throw new Error(`Prepared transform block ${blockIndex} is not decoded`);
+ const localState = geometryStateIndex - transforms.blocks[blockIndex].startGeometryStateIndex;
+ return record.transforms[localState * transforms.triangleCount + leafIndex];
+ },
+ commitPresented(geometryStateIndex, nextGeometryStateIndex) {
+ const current = blockIndexForGeometryState(geometryStateIndex);
+ const next = blockIndexForGeometryState(nextGeometryStateIndex);
+ releaseExcept(new Set([current, next]));
+ prefetch(next);
+ },
+ stats() {
+ return Object.freeze({
+ schema: "cssflower-prepared-transform-block-loader@1",
+ currentBlockIndex,
+ loadCount,
+ releaseCount,
+ residentBlockCount: [...records.values()].filter((record) => record?.transforms).length,
+ residentDecodedBytes,
+ peakResidentDecodedBytes,
+ desiredBlockIndices: Object.freeze([...desiredBlockIndices].sort((a, b) => a - b)),
+ errors: Object.freeze([...errors]),
+ });
+ },
+ destroy() {
+ destroyed = true;
+ releaseExcept(new Set());
+ },
+ });
+}
+
+export function createPreparedLightingPageLoader(lighting) {
+ const errors = [];
+ let record = null;
+ let promise = null;
+ let currentPageIndex = 0;
+ let loadCount = 0;
+ let releaseCount = 0;
+ let destroyed = false;
+
+ async function ensure() {
+ if (destroyed) throw new Error("Prepared cssFlower lighting loader is destroyed");
+ if (record?.url) return record;
+ if (promise) return promise;
+ promise = (async () => {
+ const bytes = new Uint8Array(await fetchBytes(lighting.grid.assetUrl));
+ if (bytes.byteLength !== lighting.grid.byteLength) throw new Error("Prepared lighting grid byte length drifted");
+ await assertSha256(bytes, lighting.grid.sha256, "lighting grid");
+ const url = URL.createObjectURL(new Blob([bytes], { type: lighting.grid.mimeType }));
+ let image;
+ try {
+ image = await decodeImage(url);
+ } catch (error) {
+ URL.revokeObjectURL(url);
+ throw error;
+ }
+ if (image.naturalWidth !== lighting.grid.width || image.naturalHeight !== lighting.grid.height) {
+ image.removeAttribute("src");
+ URL.revokeObjectURL(url);
+ throw new Error(`Prepared lighting grid dimensions drifted (${image.naturalWidth}x${image.naturalHeight})`);
+ }
+ record = Object.freeze({ gridIndex: 0, url, image });
+ loadCount += 1;
+ if (destroyed) release();
+ return record;
+ })();
+ try {
+ return await promise;
+ } catch (error) {
+ errors.push(String(error?.message || error));
+ throw error;
+ } finally {
+ promise = null;
+ }
+ }
+
+ function release() {
+ if (!record?.url) return;
+ record.image.removeAttribute("src");
+ URL.revokeObjectURL(record.url);
+ record = null;
+ releaseCount += 1;
+ }
+
+ function assertPageIndex(pageIndex) {
+ if (!Number.isSafeInteger(pageIndex) || pageIndex < 0 || pageIndex >= lighting.pageCount) {
+ throw new RangeError(`Prepared lighting page ${pageIndex} is missing`);
+ }
+ }
+
+ return Object.freeze({
+ async prime(pageIndex, nextPageIndex) {
+ assertPageIndex(pageIndex);
+ assertPageIndex(nextPageIndex);
+ currentPageIndex = pageIndex;
+ await ensure();
+ },
+ urlFor(pageIndex) {
+ assertPageIndex(pageIndex);
+ if (!record?.url) throw new Error(`Prepared lighting page ${pageIndex} is not decoded`);
+ return record.url;
+ },
+ async activate(pageIndex, nextPageIndex) {
+ assertPageIndex(pageIndex);
+ assertPageIndex(nextPageIndex);
+ await ensure();
+ currentPageIndex = pageIndex;
+ return record;
+ },
+ commitPresented(pageIndex, nextPageIndex) {
+ assertPageIndex(pageIndex);
+ assertPageIndex(nextPageIndex);
+ if (pageIndex !== currentPageIndex || !record?.url) {
+ throw new Error(`Prepared lighting page ${pageIndex} cannot be committed before presentation`);
+ }
+ },
+ stats() {
+ return Object.freeze({
+ schema: "cssflower-prepared-lighting-grid-loader@1",
+ currentPageIndex,
+ loadCount,
+ releaseCount,
+ residentGridCount: record?.url ? 1 : 0,
+ residentDecodedBytes: record?.url ? lighting.grid.decodedBytes : 0,
+ peakResidentDecodedBytes: loadCount > 0 ? lighting.grid.decodedBytes : 0,
+ errors: Object.freeze([...errors]),
+ });
+ },
+ destroy() {
+ destroyed = true;
+ release();
+ },
+ });
+}
+
+function validAssetDescriptor(value, prefix) {
+ return typeof value?.assetUrl === "string" && value.assetUrl.startsWith(prefix) &&
+ Number.isSafeInteger(value.byteLength) && value.byteLength > 0 &&
+ /^[a-f0-9]{64}$/u.test(value.sha256 ?? "") &&
+ (!Object.hasOwn(value, "decodedByteLength") ||
+ Number.isSafeInteger(value.decodedByteLength) && value.decodedByteLength > 0 &&
+ /^[a-f0-9]{64}$/u.test(value.decodedSha256 ?? ""));
+}
+
+async function fetchBytes(url) {
+ const response = await fetch(url);
+ if (!response.ok) throw new Error(`Failed to load ${url}: ${response.status}`);
+ return response.arrayBuffer();
+}
+
+async function decompressGzip(bytes) {
+ if (typeof DecompressionStream !== "function") {
+ throw new Error("This browser cannot decode prepared cssFlower gzip assets");
+ }
+ const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("gzip"));
+ return new Uint8Array(await new Response(stream).arrayBuffer());
+}
+
+async function assertSha256(bytes, expected, label) {
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
+ const actual = [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
+ if (actual !== expected) throw new Error(`Generated cssFlower ${label} identity mismatch (${actual})`);
+}
+
+async function decodeImage(url) {
+ const image = new Image();
+ image.decoding = "async";
+ image.src = url;
+ await image.decode();
+ return image;
+}
diff --git a/src/adapters/flowerbox/src/cssflower/preparedPlayback.mjs b/src/adapters/flowerbox/src/cssflower/preparedPlayback.mjs
new file mode 100644
index 0000000..8f2552f
--- /dev/null
+++ b/src/adapters/flowerbox/src/cssflower/preparedPlayback.mjs
@@ -0,0 +1,504 @@
+import { createPolyMorphPreparedDomTarget } from "@layoutit/polycss-morph";
+import {
+ CSSFLOWER_FRONT_FACE_DILATION_TICKS,
+ CSSFLOWER_FRONT_FACE_SCHEDULE_ENCODING,
+ CSSFLOWER_FRONT_FACE_SCHEDULE_SCHEMA,
+ CSSFLOWER_VISIBILITY_POLICY,
+} from "./renderContract.mjs";
+
+export function timelineStateIndexForTick(tick, cycle) {
+ if (!Number.isSafeInteger(tick) || tick < 0) throw new RangeError("cssFlower tick must be a non-negative safe integer");
+ if (tick < cycle.stateCount) return tick;
+ return cycle.cycleStartState + ((tick - cycle.cycleStartState) % cycle.cycleLength);
+}
+
+export async function createCssflowerPreparedPlayer(options) {
+ const {
+ lighting,
+ lightingPages,
+ mesh,
+ playback,
+ rotationRoot,
+ transformBlocks,
+ } = options;
+ const leaves = [...options.leaves];
+ validatePlayback({ playback, lighting, transformBlocks, lightingPages, rotationRoot, mesh, leaves });
+ const requestFrame = options.requestFrame ?? globalThis.requestAnimationFrame.bind(globalThis);
+ const cancelFrame = options.cancelFrame ?? globalThis.cancelAnimationFrame.bind(globalThis);
+ const frameMilliseconds = 1000 / playback.sourceTicksPerSecond;
+ let paused = true;
+ let destroyed = false;
+ let request = null;
+ let nextFrameAt = null;
+ let globalTick = 0;
+ let timelineStateIndex = -1;
+ let geometryStateIndex = -1;
+ let rootStateIndex = -1;
+ let lightingPageIndex = -1;
+ let lightingPageRowIndex = -1;
+ let preparedStatesApplied = 0;
+ let preparedGeometryStatePublishes = 0;
+ let modelTransformWrites = 0;
+ let leafTransformWrites = 0;
+ let selectedLeafTransformAttempts = 0;
+ let leafTransformSelectionTests = 0;
+ let suppressedLeafTransformWrites = 0;
+ let visibilityCatchupTransformAttempts = 0;
+ let visibilityCatchupTransformWrites = 0;
+ let leafVisibilityWrites = 0;
+ let preparedFrontFacingStateSelections = 0;
+ let presentedFrontFacingStateIndex = -1;
+ let currentFrontFacingLeafCount = 0;
+ let lightingAtlasWrites = 0;
+ let lightingColumnWrites = 0;
+ let lightingRowWrites = 0;
+ let preparedLightingAddressWrites = 0;
+ let preparedLightingStateSelections = 0;
+ let preparedLightingSkippedStateSelections = 0;
+ let runtimeSchedulerCallbacks = 0;
+ const lightingAddressSchedule = decodePreparedLightingAddressSchedule(lighting.addressSchedule);
+ const frontFacingSchedule = decodePreparedFrontFacingSchedule(playback.frontFacingSchedule);
+ const selectedFrontFacingByFace = new Uint8Array(leaves.length);
+ selectedFrontFacingByFace.fill(255);
+ const newlySelectedFaces = [];
+ const selectedLightingStateByFace = new Uint16Array(leaves.length);
+ const pendingLightingStateByFace = new Int16Array(leaves.length);
+ pendingLightingStateByFace.fill(-1);
+ const pendingLightingFaces = [];
+ let presentedLightingStateIndex = -1;
+
+ const morphTarget = createPolyMorphPreparedDomTarget({
+ model: {
+ element: rotationRoot,
+ writeTransform(transform) {
+ if (rotationRoot.style.transform === transform) return false;
+ rotationRoot.style.transform = transform;
+ return true;
+ },
+ },
+ shapes: [{ element: mesh }],
+ leaves: leaves.map((element) => ({ element })),
+ });
+
+ function applyPreparedFrontFacingSelection(nextStateIndex) {
+ newlySelectedFaces.length = 0;
+ if (presentedFrontFacingStateIndex === nextStateIndex) return;
+ const stateOffset = nextStateIndex * frontFacingSchedule.bytesPerState;
+ let selectedCount = 0;
+ for (let faceIndex = 0; faceIndex < leaves.length; faceIndex += 1) {
+ const selected = (
+ frontFacingSchedule.bytes[stateOffset + (faceIndex >> 3)] & (1 << (faceIndex & 7))
+ ) !== 0;
+ selectedCount += Number(selected);
+ const selectedByte = Number(selected);
+ if (selectedFrontFacingByFace[faceIndex] === selectedByte) continue;
+ if (selected && selectedFrontFacingByFace[faceIndex] === 0) newlySelectedFaces.push(faceIndex);
+ if (morphTarget.leaves[faceIndex].writeVisibility(selected)) leafVisibilityWrites += 1;
+ selectedFrontFacingByFace[faceIndex] = selectedByte;
+ }
+ currentFrontFacingLeafCount = selectedCount;
+ presentedFrontFacingStateIndex = nextStateIndex;
+ preparedFrontFacingStateSelections += 1;
+ }
+
+ function isPreparedFrontFacing(faceIndex) {
+ return selectedFrontFacingByFace[faceIndex] === 1;
+ }
+
+ function applyPreparedLightingAddress(faceIndex, stateIndex) {
+ if (selectedLightingStateByFace[faceIndex] === stateIndex) return;
+ const addressState = playback.cycle.states[stateIndex];
+ const face = lighting.faces[faceIndex];
+ const x = -(addressState.lightingPageIndex * lighting.atlasWidth + face.contentX);
+ const y = -(addressState.lightingPageRowIndex * lighting.stateSliceHeight + face.contentY);
+ leaves[faceIndex].style.backgroundPosition = `${x}px ${y}px`;
+ selectedLightingStateByFace[faceIndex] = stateIndex;
+ preparedLightingAddressWrites += 1;
+ }
+
+ function applyPreparedLightingAddresses(nextStateIndex) {
+ if (presentedLightingStateIndex === -1) {
+ if (nextStateIndex !== playback.cycle.initialState) {
+ throw new Error("Prepared cssFlower lighting addresses must initialize at the prepared initial state");
+ }
+ presentedLightingStateIndex = nextStateIndex;
+ return;
+ }
+ if (nextStateIndex === presentedLightingStateIndex) return;
+ const advance = (
+ nextStateIndex - presentedLightingStateIndex + lightingAddressSchedule.stateCount
+ ) % lightingAddressSchedule.stateCount;
+ if (advance > 1) preparedLightingSkippedStateSelections += advance - 1;
+ if (advance === 1) {
+ const start = lightingAddressSchedule.offsets[nextStateIndex];
+ const end = lightingAddressSchedule.offsets[nextStateIndex + 1];
+ for (let update = start; update < end; update += 1) {
+ applyPreparedLightingAddress(lightingAddressSchedule.faceIndices[update], nextStateIndex);
+ }
+ presentedLightingStateIndex = nextStateIndex;
+ preparedLightingStateSelections += 1;
+ return;
+ }
+ pendingLightingFaces.length = 0;
+ for (let offset = 1; offset <= advance; offset += 1) {
+ const stateIndex = (presentedLightingStateIndex + offset) % lightingAddressSchedule.stateCount;
+ const start = lightingAddressSchedule.offsets[stateIndex];
+ const end = lightingAddressSchedule.offsets[stateIndex + 1];
+ for (let update = start; update < end; update += 1) {
+ const faceIndex = lightingAddressSchedule.faceIndices[update];
+ if (pendingLightingStateByFace[faceIndex] < 0) pendingLightingFaces.push(faceIndex);
+ pendingLightingStateByFace[faceIndex] = stateIndex;
+ }
+ }
+ for (const faceIndex of pendingLightingFaces) {
+ const stateIndex = pendingLightingStateByFace[faceIndex];
+ pendingLightingStateByFace[faceIndex] = -1;
+ applyPreparedLightingAddress(faceIndex, stateIndex);
+ }
+ presentedLightingStateIndex = nextStateIndex;
+ preparedLightingStateSelections += 1;
+ }
+
+ async function applyTick(tick) {
+ if (destroyed) throw new Error("Prepared cssFlower player is destroyed");
+ if (!Number.isSafeInteger(tick) || tick < 0) throw new RangeError("cssFlower tick must be a non-negative safe integer");
+ const nextTimelineStateIndex = timelineStateIndexForTick(tick, playback.cycle);
+ const state = playback.cycle.states[nextTimelineStateIndex];
+ if (!state) throw new Error(`Prepared cssFlower timeline state ${nextTimelineStateIndex} is missing`);
+ const geometryChanged = state.geometryStateIndex !== geometryStateIndex;
+ const lightingPageChanged = state.lightingPageIndex !== lightingPageIndex;
+ applyPreparedFrontFacingSelection(nextTimelineStateIndex);
+
+ if (geometryChanged) {
+ await transformBlocks.activate(
+ state.geometryStateIndex,
+ state.nextTransformBlockGeometryStateIndex,
+ );
+ transformBlocks.forEachTransform(state.geometryStateIndex, (transform, leafIndex) => {
+ leafTransformSelectionTests += 1;
+ if (!isPreparedFrontFacing(leafIndex)) {
+ suppressedLeafTransformWrites += 1;
+ return;
+ }
+ selectedLeafTransformAttempts += 1;
+ if (morphTarget.leaves[leafIndex].writeTransform(transform)) leafTransformWrites += 1;
+ });
+ preparedGeometryStatePublishes += 1;
+ transformBlocks.commitPresented(
+ state.geometryStateIndex,
+ state.nextTransformBlockGeometryStateIndex,
+ );
+ } else if (newlySelectedFaces.length > 0) {
+ for (const leafIndex of newlySelectedFaces) {
+ visibilityCatchupTransformAttempts += 1;
+ const transform = transformBlocks.transformAt(state.geometryStateIndex, leafIndex);
+ if (morphTarget.leaves[leafIndex].writeTransform(transform)) {
+ leafTransformWrites += 1;
+ visibilityCatchupTransformWrites += 1;
+ }
+ }
+ }
+
+ if (lightingPageChanged) {
+ await lightingPages.activate(
+ state.lightingPageIndex,
+ state.nextLightingPageIndex,
+ );
+ lightingPages.commitPresented(state.lightingPageIndex, state.nextLightingPageIndex);
+ }
+ applyPreparedLightingAddresses(nextTimelineStateIndex);
+ if (morphTarget.model.writeTransform(playback.cycle.rootTransforms[state.rootStateIndex])) {
+ modelTransformWrites += 1;
+ }
+
+ globalTick = tick;
+ timelineStateIndex = nextTimelineStateIndex;
+ geometryStateIndex = state.geometryStateIndex;
+ rootStateIndex = state.rootStateIndex;
+ lightingPageIndex = state.lightingPageIndex;
+ lightingPageRowIndex = state.lightingPageRowIndex;
+ preparedStatesApplied += 1;
+ morphTarget.assertStableDomIdentity();
+ return globalTick;
+ }
+
+ async function loop(timestamp) {
+ request = null;
+ runtimeSchedulerCallbacks += 1;
+ if (paused) return;
+ if (nextFrameAt === null) {
+ nextFrameAt = timestamp + frameMilliseconds;
+ } else if (timestamp >= nextFrameAt - 0.5) {
+ const elapsedSteps = Math.max(1, Math.floor((timestamp - nextFrameAt) / frameMilliseconds) + 1);
+ await applyTick(globalTick + elapsedSteps);
+ nextFrameAt += elapsedSteps * frameMilliseconds;
+ }
+ if (!paused) request = requestFrame(loop);
+ }
+
+ function pause() {
+ paused = true;
+ nextFrameAt = null;
+ if (request !== null) cancelFrame(request);
+ request = null;
+ return globalTick;
+ }
+
+ await applyTick(0);
+ return Object.freeze({
+ get tick() { return globalTick; },
+ get paused() { return paused; },
+ pause,
+ resume() {
+ if (!paused) return globalTick;
+ paused = false;
+ nextFrameAt = null;
+ request = requestFrame(loop);
+ return globalTick;
+ },
+ async step(count = 1) {
+ pause();
+ const amount = Math.trunc(Number(count));
+ if (!Number.isSafeInteger(amount) || amount < 1) throw new RangeError("cssFlower step count must be a positive integer");
+ return applyTick(globalTick + amount);
+ },
+ async setTick(value) {
+ pause();
+ const tick = Math.trunc(Number(value));
+ return applyTick(tick);
+ },
+ assertStableDomIdentity() {
+ morphTarget.assertStableDomIdentity();
+ return true;
+ },
+ sample() {
+ return Object.freeze({
+ globalTick,
+ timelineStateIndex,
+ geometryStateIndex,
+ rootStateIndex,
+ lightingPageIndex,
+ lightingPageRowIndex,
+ });
+ },
+ stats() {
+ morphTarget.assertStableDomIdentity();
+ const state = playback.cycle.states[timelineStateIndex];
+ return Object.freeze({
+ schema: "cssflower-prepared-player-stats@1",
+ morphTarget: "@layoutit/polycss-morph#createPolyMorphPreparedDomTarget",
+ morphAdopted: true,
+ morphStableDomIdentity: true,
+ paused,
+ globalTick,
+ timelineStateIndex,
+ geometryStateIndex,
+ transformBlockIndex: state.transformBlockIndex,
+ rootStateIndex,
+ lightingPageIndex,
+ lightingPageRowIndex,
+ sourceSf: state.sf,
+ sourceSfHex: state.sfHex,
+ sourceSfi: state.sfi,
+ sourceSfiHex: state.sfiHex,
+ sourceRotationDegrees: [state.rotationXDegrees, state.rotationYDegrees, state.rotationZDegrees],
+ sourceRotationIncrementDegrees: [3, 2, 0],
+ retainedTriangleLeafCount: leaves.length,
+ retainedRotationRootCount: 1,
+ retainedPreparedShapeCount: 1,
+ preparedTimelineStateCount: playback.cycle.stateCount,
+ preparedGeometryStateCount: playback.cycle.geometryStateCount,
+ preparedRootStateCount: playback.cycle.rootStateCount,
+ preparedTransformBlockCount: playback.transformAsset.blockCount,
+ preparedLightingPageCount: lighting.pageCount,
+ preparedStatesApplied,
+ runtimePreparedGeometryStatePublishes: preparedGeometryStatePublishes,
+ runtimeModelTransformWrites: modelTransformWrites,
+ runtimeShapeTransformWrites: 0,
+ runtimeLeafTransformWrites: leafTransformWrites,
+ runtimeSelectedLeafTransformAttempts: selectedLeafTransformAttempts,
+ runtimeLeafTransformSelectionTests: leafTransformSelectionTests,
+ runtimeSuppressedLeafTransformWrites: suppressedLeafTransformWrites,
+ runtimeVisibilityCatchupTransformAttempts: visibilityCatchupTransformAttempts,
+ runtimeVisibilityCatchupTransformWrites: visibilityCatchupTransformWrites,
+ runtimeLeafVisibilityWrites: leafVisibilityWrites,
+ runtimePreparedFrontFacingStateSelections: preparedFrontFacingStateSelections,
+ preparedFrontFacingDilationTicks: frontFacingSchedule.dilationTicks,
+ preparedFrontFacingSelectedFaceCount: frontFacingSchedule.selectedFaceCount,
+ preparedFrontFacingVisibilityChangeCount: frontFacingSchedule.visibilityChangeCount,
+ preparedVisibilitySelectionDomain: frontFacingSchedule.selectionDomain,
+ preparedVisibilityMinimumOwnedPixels: frontFacingSchedule.minimumOwnedPixels,
+ preparedVisibilitySampleGrid: frontFacingSchedule.sampleGrid,
+ preparedVisibilityAdjacencyRings: frontFacingSchedule.adjacencyRings,
+ currentFrontFacingLeafCount,
+ runtimeLightingAtlasWrites: lightingAtlasWrites,
+ runtimeLightingColumnWrites: lightingColumnWrites,
+ runtimeLightingRowWrites: lightingRowWrites,
+ runtimePreparedLightingAddressWrites: preparedLightingAddressWrites,
+ runtimePreparedLightingStateSelections: preparedLightingStateSelections,
+ runtimePreparedLightingSkippedStateSelections: preparedLightingSkippedStateSelections,
+ runtimeDirectLeafCssTextWrites: 0,
+ runtimeProjectedFrameWrites: 0,
+ runtimeProjectedAtlasWrites: 0,
+ runtimePreparedPageLayoutAdoptions: 0,
+ runtimePreparedPageBoundaryLeafStyleWrites: 0,
+ transformBlockLoader: transformBlocks.stats(),
+ lightingPageLoader: lightingPages.stats(),
+ pendingLightingCommitCount: 0,
+ runtimeSchedulerCallbacks,
+ runtimePolygonConstructionCount: 0,
+ runtimeGeometryConstructionCount: 0,
+ runtimeRadialProjectionCount: 0,
+ runtimeProjectionCalculationCount: 0,
+ runtimeRasterizationCount: 0,
+ runtimeNormalCalculationCount: 0,
+ runtimeLightingCalculationCount: 0,
+ runtimeAtlasConstructionCount: 0,
+ runtimeDomCreationCount: 0,
+ runtimeDomRemovalCount: 0,
+ runtimeDomGrowth: false,
+ });
+ },
+ nodes() {
+ return Object.freeze({ rotationRoot, mesh, leaves: Object.freeze([...leaves]) });
+ },
+ destroy() {
+ if (destroyed) return;
+ destroyed = true;
+ pause();
+ morphTarget.destroy();
+ },
+ });
+}
+
+function validatePlayback({ playback, lighting, transformBlocks, lightingPages, rotationRoot, mesh, leaves }) {
+ if (playback?.schema !== "cssflower-prepared-playback@1" ||
+ playback.target !== "createPolyMorphPreparedDomTarget" ||
+ playback.scope !== "rounded-product-cycle-spike-phase-omitted" ||
+ playback.sourceTicksPerSecond !== 30 ||
+ playback.cycle?.schema !== "cssflower-prepared-rounded-product-cycle@1" ||
+ playback.cycle?.stateCount !== 360 ||
+ playback.cycle?.cycleStartState !== 0 ||
+ playback.cycle?.cycleLength !== 360 ||
+ playback.cycle?.bloomTraceStateCount !== 90 ||
+ playback.cycle?.bloomCycleLength !== 90 ||
+ playback.cycle?.bloomPeakGeometryStateIndex !== 45 ||
+ playback.cycle?.bloomPeakSfHex !== "400ffffc" ||
+ playback.cycle?.bloomPeakSfNominal !== 2.25 ||
+ playback.cycle?.omittedSourceSfAtOrAbove !== 2.5 ||
+ playback.cycle?.geometryStateCount !== 46 ||
+ playback.cycle?.rootStateCount !== 360 ||
+ playback.cycle?.states?.length !== 360 ||
+ playback.cycle?.rootTransforms?.length !== 360 ||
+ playback.cycle.states.some((state) =>
+ !Number.isSafeInteger(state.rootStateIndex) || state.rootStateIndex < 0 || state.rootStateIndex >= 360 ||
+ !Number.isSafeInteger(state.geometryStateIndex) || state.geometryStateIndex < 0 ||
+ state.geometryStateIndex >= playback.cycle.geometryStateCount ||
+ !Number.isSafeInteger(state.transformBlockIndex) ||
+ !Number.isSafeInteger(state.nextTransformBlockGeometryStateIndex) ||
+ !Number.isSafeInteger(state.lightingPageIndex) ||
+ !Number.isSafeInteger(state.lightingPageRowIndex) ||
+ !Number.isSafeInteger(state.nextLightingPageIndex)) ||
+ lighting?.backgroundPositionXs?.length !== lighting?.pageCount ||
+ lighting?.backgroundPositionYs?.length !== lighting?.pageRowCount ||
+ !transformBlocks?.activate || !transformBlocks?.forEachTransform ||
+ !transformBlocks?.commitPresented || !transformBlocks?.stats ||
+ !lightingPages?.activate || !lightingPages?.commitPresented ||
+ !lightingPages?.urlFor || !lightingPages?.stats ||
+ !(rotationRoot instanceof HTMLElement) || !(mesh instanceof HTMLElement) ||
+ leaves.length !== 1_200) {
+ throw new Error("Complete prepared cssFlower retained PolyCSS Morph playback is required");
+ }
+}
+
+function decodePreparedLightingAddressSchedule(schedule) {
+ if (schedule?.schema !== "cssflower-prepared-exact-sparse-lighting-address-schedule@1" ||
+ schedule.stateCount !== 360 || schedule.faceCount !== 1_200 || schedule.threshold !== 0 ||
+ schedule.faceIndicesEncoding !== "base64-u16le-state-major-updated-face-indices" ||
+ schedule.offsets?.length !== 361 || schedule.offsets[0] !== 0 ||
+ schedule.offsets.at(-1) !== schedule.updateCount ||
+ !schedule.offsets.every((value, index) => Number.isSafeInteger(value) && value >= 0 &&
+ (index === 0 || value >= schedule.offsets[index - 1])) ||
+ typeof schedule.faceIndicesBase64 !== "string") {
+ throw new Error("Prepared cssFlower exact sparse-lighting schedule is invalid");
+ }
+ const encoded = atob(schedule.faceIndicesBase64);
+ if (encoded.length !== schedule.faceIndicesByteLength || encoded.length !== schedule.updateCount * 2) {
+ throw new Error("Prepared cssFlower sparse-lighting schedule byte length drifted");
+ }
+ const faceIndices = new Uint16Array(schedule.updateCount);
+ for (let index = 0; index < faceIndices.length; index += 1) {
+ faceIndices[index] = encoded.charCodeAt(index * 2) | (encoded.charCodeAt(index * 2 + 1) << 8);
+ if (faceIndices[index] >= schedule.faceCount) {
+ throw new Error(`Prepared cssFlower sparse-lighting face ${index} is out of range`);
+ }
+ }
+ return Object.freeze({
+ stateCount: schedule.stateCount,
+ faceCount: schedule.faceCount,
+ offsets: Object.freeze([...schedule.offsets]),
+ faceIndices,
+ });
+}
+
+function decodePreparedFrontFacingSchedule(schedule) {
+ if (schedule?.schema !== CSSFLOWER_FRONT_FACE_SCHEDULE_SCHEMA ||
+ schedule.stateCount !== 360 || schedule.faceCount !== 1_200 ||
+ schedule.dilationTicks !== CSSFLOWER_FRONT_FACE_DILATION_TICKS ||
+ schedule.selectionDomain !== CSSFLOWER_VISIBILITY_POLICY.selectionDomain ||
+ schedule.depthComparison !== CSSFLOWER_VISIBILITY_POLICY.depthComparison ||
+ schedule.minimumOwnedPixels !== CSSFLOWER_VISIBILITY_POLICY.minimumOwnedPixels ||
+ schedule.sampleGrid !== CSSFLOWER_VISIBILITY_POLICY.sampleGrid ||
+ schedule.adjacency !== CSSFLOWER_VISIBILITY_POLICY.adjacency ||
+ schedule.adjacencyRings !== CSSFLOWER_VISIBILITY_POLICY.adjacencyRings ||
+ schedule.dilationPolicy !== CSSFLOWER_VISIBILITY_POLICY.dilationPolicy ||
+ schedule.encoding !== CSSFLOWER_FRONT_FACE_SCHEDULE_ENCODING ||
+ schedule.bytesPerState !== Math.ceil(schedule.faceCount / 8) ||
+ schedule.byteLength !== schedule.stateCount * schedule.bytesPerState ||
+ !Number.isSafeInteger(schedule.selectedFaceCount) || schedule.selectedFaceCount < 1 ||
+ schedule.suppressedFaceCount !== schedule.stateCount * schedule.faceCount - schedule.selectedFaceCount ||
+ schedule.initialVisibilitySelectionCount !== schedule.faceCount ||
+ !Number.isSafeInteger(schedule.visibilityChangeCount) || schedule.visibilityChangeCount < 1 ||
+ typeof schedule.dataBase64 !== "string") {
+ throw new Error("Prepared cssFlower owned-pixel visibility transform schedule is invalid");
+ }
+ const encoded = atob(schedule.dataBase64);
+ if (encoded.length !== schedule.byteLength) {
+ throw new Error("Prepared cssFlower front-face transform schedule byte length drifted");
+ }
+ const bytes = Uint8Array.from(encoded, (character) => character.charCodeAt(0));
+ let selectedFaceCount = 0;
+ let visibilityChangeCount = 0;
+ for (let stateIndex = 0; stateIndex < schedule.stateCount; stateIndex += 1) {
+ const stateOffset = stateIndex * schedule.bytesPerState;
+ for (let faceIndex = 0; faceIndex < schedule.faceCount; faceIndex += 1) {
+ const selected = Number((bytes[stateOffset + (faceIndex >> 3)] & (1 << (faceIndex & 7))) !== 0);
+ selectedFaceCount += selected;
+ const previousStateIndex = (stateIndex + schedule.stateCount - 1) % schedule.stateCount;
+ const previousStateOffset = previousStateIndex * schedule.bytesPerState;
+ const previouslySelected = Number(
+ (bytes[previousStateOffset + (faceIndex >> 3)] & (1 << (faceIndex & 7))) !== 0,
+ );
+ visibilityChangeCount += Number(previouslySelected !== selected);
+ }
+ }
+ if (selectedFaceCount !== schedule.selectedFaceCount) {
+ throw new Error("Prepared cssFlower front-face transform selection count drifted");
+ }
+ if (visibilityChangeCount !== schedule.visibilityChangeCount) {
+ throw new Error("Prepared cssFlower front-face visibility-change count drifted");
+ }
+ return Object.freeze({
+ stateCount: schedule.stateCount,
+ faceCount: schedule.faceCount,
+ dilationTicks: schedule.dilationTicks,
+ selectionDomain: schedule.selectionDomain,
+ minimumOwnedPixels: schedule.minimumOwnedPixels,
+ sampleGrid: schedule.sampleGrid,
+ adjacencyRings: schedule.adjacencyRings,
+ bytesPerState: schedule.bytesPerState,
+ bytes,
+ selectedFaceCount,
+ visibilityChangeCount,
+ });
+}
diff --git a/src/adapters/flowerbox/src/cssflower/renderContract.mjs b/src/adapters/flowerbox/src/cssflower/renderContract.mjs
new file mode 100644
index 0000000..35cc18f
--- /dev/null
+++ b/src/adapters/flowerbox/src/cssflower/renderContract.mjs
@@ -0,0 +1,64 @@
+export const CSSFLOWER_SEAM_BLEED = 0.2;
+export const CSSFLOWER_SEAM_BLEED_TEXT = String(CSSFLOWER_SEAM_BLEED);
+export const CSSFLOWER_BOUNDARY_SEAM_BLEED = 0.05;
+export const CSSFLOWER_BOUNDARY_SEAM_BLEED_TEXT = String(CSSFLOWER_BOUNDARY_SEAM_BLEED);
+export const CSSFLOWER_SEAM_BLEED_POLICY = "side-local-shared-edges-boundary-ring-damped";
+export const CSSFLOWER_LIGHTING_SCHEMA = "cssflower-prepared-space-texel-lighting@4";
+export const CSSFLOWER_LIGHTING_LAYOUT = "guttered-leaf-raster-shelves-by-horizontal-page-grid-and-timeline-state-slices";
+export const CSSFLOWER_LIGHTING_RASTER_MODE = "leaf-resolution";
+export const CSSFLOWER_LIGHTING_SAMPLING = "endpoint-aligned-pixel-centers";
+export const CSSFLOWER_LIGHTING_PAGE_ROWS = 32;
+export const CSSFLOWER_LIGHTING_PAGE_COUNT = 12;
+export const CSSFLOWER_LIGHTING_MICRO_CONTENT_SIZE = 2;
+export const CSSFLOWER_LIGHTING_GUTTER = 1;
+export const CSSFLOWER_LIGHTING_MICRO_SLOT_SIZE = CSSFLOWER_LIGHTING_MICRO_CONTENT_SIZE + CSSFLOWER_LIGHTING_GUTTER * 2;
+export const CSSFLOWER_LIGHTING_ATLAS_WIDTH = 768;
+export const CSSFLOWER_LIGHTING_MICRO_TILES_PER_SHELF = Math.floor(
+ CSSFLOWER_LIGHTING_ATLAS_WIDTH / CSSFLOWER_LIGHTING_MICRO_SLOT_SIZE,
+);
+export const CSSFLOWER_LIGHTING_STATE_SLICE_HEIGHT = 226;
+export const CSSFLOWER_LIGHTING_ATLAS_HEIGHT = 7_232;
+export const CSSFLOWER_LIGHTING_GRID_COLUMNS = CSSFLOWER_LIGHTING_PAGE_COUNT;
+export const CSSFLOWER_LIGHTING_GRID_ROWS = 1;
+export const CSSFLOWER_LIGHTING_GRID_WIDTH = CSSFLOWER_LIGHTING_ATLAS_WIDTH * CSSFLOWER_LIGHTING_GRID_COLUMNS;
+export const CSSFLOWER_LIGHTING_GRID_HEIGHT = CSSFLOWER_LIGHTING_ATLAS_HEIGHT;
+export const CSSFLOWER_LIGHTING_GRID_DECODED_BYTES = CSSFLOWER_LIGHTING_GRID_WIDTH * CSSFLOWER_LIGHTING_GRID_HEIGHT * 4;
+export const CSSFLOWER_LIGHTING_ATLAS_ENCODING = "avif-grid-12x1-lossy-q60-speed6-yuv444";
+export const CSSFLOWER_LIGHTING_ATLAS_MIME_TYPE = "image/avif";
+export const CSSFLOWER_LIGHTING_ATLAS_EXTENSION = "avif";
+export const CSSFLOWER_LIGHTING_ATLAS_QUALITY = 60;
+export const CSSFLOWER_LIGHTING_VISUAL_BANK_MAX_BYTES = 125_000_000;
+export const CSSFLOWER_TRANSFORM_BLOCK_SCHEMA = "cssflower-prepared-matrix3d-blocks@1";
+export const CSSFLOWER_TRANSFORM_BLOCK_GEOMETRY_STATES = 16;
+export const CSSFLOWER_FRONT_FACE_SCHEDULE_SCHEMA = "cssflower-prepared-front-face-transform-schedule@1";
+export const CSSFLOWER_FRONT_FACE_SCHEDULE_ENCODING = "base64-lsb0-state-major-bitset";
+export const CSSFLOWER_VISIBILITY_POLICY = Object.freeze({
+ schema: "cssflower-prepared-depth16-owned-pixel-visibility-policy@1",
+ selectionDomain: "prepared-source-camera-depth16-owned-pixel-occlusion",
+ depthComparison: "source-depth16-less",
+ minimumOwnedPixels: 8,
+ sampleGrid: 1,
+ adjacency: "edge",
+ adjacencyRings: 0,
+ temporalDilationTicks: 1,
+ dilationPolicy: "cyclic-union-of-previous-current-next-prepared-state",
+});
+export const CSSFLOWER_FRONT_FACE_DILATION_TICKS = CSSFLOWER_VISIBILITY_POLICY.temporalDilationTicks;
+export const CSSFLOWER_PROJECTED_ATLAS_ENCODING = "avif-lossy-q60-speed6-yuv444";
+export const CSSFLOWER_PROJECTED_ATLAS_MIME_TYPE = "image/avif";
+export const CSSFLOWER_PROJECTED_ATLAS_EXTENSION = "avif";
+export const CSSFLOWER_PROJECTED_ATLAS_QUALITY = 60;
+export const CSSFLOWER_PROJECTED_VISUAL_BANK_MAX_BYTES = 45_000_000;
+export const CSSFLOWER_PROJECTED_VISUAL_ACCEPTANCE = Object.freeze({
+ schema: "cssflower-q60-exact-reference-visual-envelope@1",
+ selection: "user-rejected-visible-q40-compression-promote-q60-from-calibrated-q90-to-q20-sweep",
+ reference: "lossless-webp-retained-dom-browser-sequence",
+ meanAbsDelta: 0.35,
+ rmsDelta: 2.5,
+ changedPixelRatio: 0.38,
+ maxAbsDelta: 225,
+ alphaMaxAbsDelta: 0,
+ interiorMeanAbsDelta: 1.75,
+ interiorRmsDelta: 2.5,
+ exactBackgroundMaxAbsDelta: 0,
+});
diff --git a/src/adapters/flowerbox/src/cssflower/routeState.mjs b/src/adapters/flowerbox/src/cssflower/routeState.mjs
new file mode 100644
index 0000000..9cb8378
--- /dev/null
+++ b/src/adapters/flowerbox/src/cssflower/routeState.mjs
@@ -0,0 +1,40 @@
+export const DEFAULT_SCENE_ID = "default-cube";
+export const PUBLIC_ROUTE_PARAMS = ["scene"];
+
+export function createRouteState(search = globalThis.location?.search ?? "") {
+ const params = new URLSearchParams(search);
+ const scene = cleanSceneId(params.get("scene")) ?? DEFAULT_SCENE_ID;
+ return {
+ params,
+ scene,
+ manifestUrl: "/cssflower/manifest.json",
+ publicRoute: publicRouteFor({ scene }),
+ routeContract: "?scene=",
+ };
+}
+
+export function publicRouteFor({ scene = DEFAULT_SCENE_ID } = {}) {
+ const base = globalThis.location?.pathname?.startsWith("/flower") ? "/flower/" : "/";
+ if (scene === DEFAULT_SCENE_ID) return base;
+ const params = new URLSearchParams();
+ params.set("scene", scene);
+ return base + "?" + params.toString();
+}
+
+export function sceneEntryForRoute(manifest, routeState) {
+ const scenes = Array.isArray(manifest?.scenes) ? manifest.scenes : [];
+ return scenes.find((scene) => scene.id === routeState.scene)
+ ?? scenes.find((scene) => scene.id === manifest?.defaultScene?.id)
+ ?? scenes[0]
+ ?? null;
+}
+
+export function routeSceneLabel(routeState) {
+ return "scene=" + routeState.scene;
+}
+
+export function cleanSceneId(value) {
+ if (typeof value !== "string" || !value.trim()) return null;
+ const clean = value.trim().toLowerCase();
+ return /^[a-z0-9._-]+$/.test(clean) ? clean : null;
+}
diff --git a/src/adapters/flowerbox/src/cssflower/stagePresentation.mjs b/src/adapters/flowerbox/src/cssflower/stagePresentation.mjs
new file mode 100644
index 0000000..41dee6a
--- /dev/null
+++ b/src/adapters/flowerbox/src/cssflower/stagePresentation.mjs
@@ -0,0 +1,61 @@
+export const CSSFLOWER_PREPARED_STAGE_EDGE = 720;
+export const CSSFLOWER_RESPONSIVE_STAGE_FRACTION = 1;
+
+export function cssflowerStageScale(viewportWidth, viewportHeight) {
+ if (!Number.isFinite(viewportWidth) || !Number.isFinite(viewportHeight) ||
+ viewportWidth <= 0 || viewportHeight <= 0) {
+ throw new RangeError("cssFlower viewport dimensions must be positive finite numbers");
+ }
+ return Math.min(viewportWidth, viewportHeight) / CSSFLOWER_PREPARED_STAGE_EDGE *
+ CSSFLOWER_RESPONSIVE_STAGE_FRACTION;
+}
+
+export function installCssflowerStagePresentation({ host, camera }) {
+ if (!(host instanceof HTMLElement)) throw new TypeError("cssFlower stage host is missing");
+ if (!(camera instanceof HTMLElement) || !camera.classList.contains("polycss-camera")) {
+ throw new TypeError("cssFlower prepared camera is missing");
+ }
+ let scale = 1;
+ let writes = 0;
+ let resizeObserver = null;
+
+ function apply() {
+ const rect = host.getBoundingClientRect();
+ const nextScale = cssflowerStageScale(rect.width, rect.height);
+ const serialized = String(Number(nextScale.toFixed(8)));
+ if (camera.style.scale !== serialized) {
+ camera.style.scale = serialized;
+ writes += 1;
+ }
+ scale = Number(serialized);
+ }
+
+ apply();
+ if (typeof ResizeObserver === "function") {
+ resizeObserver = new ResizeObserver(apply);
+ resizeObserver.observe(host);
+ } else {
+ window.addEventListener("resize", apply, { passive: true });
+ }
+
+ return Object.freeze({
+ stats() {
+ return Object.freeze({
+ stagePresentation: "responsive",
+ preparedStageEdgePixels: CSSFLOWER_PREPARED_STAGE_EDGE,
+ responsivePresentationFit: "contain",
+ responsivePresentationStageFraction: CSSFLOWER_RESPONSIVE_STAGE_FRACTION,
+ presentationScale: scale,
+ runtimePresentationScaleWrites: writes,
+ runtimeModelGeometryCalculations: 0,
+ });
+ },
+ destroy() {
+ resizeObserver?.disconnect();
+ if (resizeObserver === null) {
+ window.removeEventListener("resize", apply);
+ }
+ camera.style.removeProperty("scale");
+ },
+ });
+}
diff --git a/src/adapters/flowerbox/src/cssflower/styles.css b/src/adapters/flowerbox/src/cssflower/styles.css
new file mode 100644
index 0000000..f8f9968
--- /dev/null
+++ b/src/adapters/flowerbox/src/cssflower/styles.css
@@ -0,0 +1,58 @@
+:root {
+ background: #000;
+ color-scheme: dark;
+}
+
+html,
+html > body {
+ margin: 0;
+ overflow: hidden;
+ background: #000;
+}
+
+html {
+ width: 100%;
+ height: 100%;
+}
+
+html > body {
+ position: relative;
+ width: 100vw;
+ height: 100vh;
+ height: 100dvh;
+}
+
+html > body:not(.r):not(.e)::after {
+ content: "";
+ position: fixed;
+ inset: 50% auto auto 50%;
+ z-index: 1;
+ width: 18px;
+ height: 18px;
+ margin: -9px 0 0 -9px;
+ border: 1px solid rgb(240 240 240 / 18%);
+ border-top-color: rgb(240 240 240 / 70%);
+ border-radius: 50%;
+ animation: l 0.8s linear infinite;
+}
+
+@keyframes l {
+ to {
+ transform: rotate(1turn);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ html > body:not(.r):not(.e)::after {
+ animation: none;
+ }
+}
+
+body > .polycss-camera {
+ position: absolute !important;
+ inset: calc(50% - 360px) auto auto calc(50% - 360px) !important;
+ width: 720px !important;
+ height: 720px !important;
+ scale: 1;
+ transform-origin: 50% 50%;
+}
diff --git a/src/adapters/flowerbox/src/main.mjs b/src/adapters/flowerbox/src/main.mjs
new file mode 100644
index 0000000..96c90f1
--- /dev/null
+++ b/src/adapters/flowerbox/src/main.mjs
@@ -0,0 +1,3 @@
+import { mountCssflowerClient } from "./cssflower/client.mjs";
+
+mountCssflowerClient(document.body);
diff --git a/src/adapters/flowerbox/src/prepare/cssflower/bloomCycle.mjs b/src/adapters/flowerbox/src/prepare/cssflower/bloomCycle.mjs
new file mode 100644
index 0000000..ea14c38
--- /dev/null
+++ b/src/adapters/flowerbox/src/prepare/cssflower/bloomCycle.mjs
@@ -0,0 +1,195 @@
+import {
+ CSSFLOWER_SOURCE_PROFILE,
+ FLOAT,
+ floatBits,
+ floatHex,
+ preparedRootTransform,
+} from "./sourceProfile.mjs";
+
+export const CSSFLOWER_PRODUCT_BLOOM_PEAK_GEOMETRY_STATE = 45;
+export const CSSFLOWER_PRODUCT_BLOOM_PEAK_SF_NOMINAL = 2.25;
+export const CSSFLOWER_PRODUCT_BLOOM_CYCLE_LENGTH = 90;
+export const CSSFLOWER_PRODUCT_ROTATION_CYCLE_LENGTH = 360;
+
+export function buildPreparedBloomCycle() {
+ const bloom = CSSFLOWER_SOURCE_PROFILE.bloom;
+ let sf = FLOAT(bloom.resetSf);
+ let sfi = FLOAT(bloom.sfIncrement);
+ const min = FLOAT(bloom.minSf);
+ const max = FLOAT(bloom.maxSf);
+ const seen = new Map();
+ const states = [];
+
+ while (states.length < 10_000) {
+ const key = `${floatBits(sf)}:${floatBits(sfi)}`;
+ if (seen.has(key)) {
+ const cycleStartState = seen.get(key);
+ const geometryBySf = new Map();
+ const geometryStates = [];
+ for (const state of states) {
+ const sfKey = floatBits(state.sf);
+ let geometryStateIndex = geometryBySf.get(sfKey);
+ if (geometryStateIndex === undefined) {
+ geometryStateIndex = geometryStates.length;
+ geometryBySf.set(sfKey, geometryStateIndex);
+ geometryStates.push(Object.freeze({
+ index: geometryStateIndex,
+ sf: state.sf,
+ sfHex: state.sfHex,
+ firstTick: state.tick,
+ }));
+ }
+ state.geometryStateIndex = geometryStateIndex;
+ }
+ const rootTransforms = Object.freeze(Array.from({ length: 360 }, (_, tick) =>
+ preparedRootTransform(
+ tick * CSSFLOWER_SOURCE_PROFILE.rotation.xDegreesPerUpdate,
+ tick * CSSFLOWER_SOURCE_PROFILE.rotation.yDegreesPerUpdate,
+ )));
+ return Object.freeze({
+ schema: "cssflower-prepared-bloom-cycle@1",
+ initialState: 0,
+ stateCount: states.length,
+ cycleStartState,
+ cycleLength: states.length - cycleStartState,
+ repeatKey: key,
+ geometryStateCount: geometryStates.length,
+ geometryStates: Object.freeze(geometryStates),
+ rootStateCount: rootTransforms.length,
+ rootTransforms,
+ states: Object.freeze(states.map((state) => Object.freeze(state))),
+ });
+ }
+ const tick = states.length;
+ seen.set(key, tick);
+ states.push({
+ tick,
+ sf,
+ sfHex: floatHex(sf),
+ sfi,
+ sfiHex: floatHex(sfi),
+ rotationXDegrees: tick * CSSFLOWER_SOURCE_PROFILE.rotation.xDegreesPerUpdate,
+ rotationYDegrees: tick * CSSFLOWER_SOURCE_PROFILE.rotation.yDegreesPerUpdate,
+ rotationZDegrees: 0,
+ rootStateIndex: tick % 360,
+ geometryStateIndex: -1,
+ });
+ sf = FLOAT(sf + sfi);
+ if (sf > max || sf < min) sfi = FLOAT(-sfi);
+ }
+ throw new Error("cssFlower bloom state failed to enter a repeatable binary32 cycle");
+}
+
+export function buildPreparedFullRotationCycle() {
+ const bloomCycle = buildPreparedBloomCycle();
+ const combinedCycleLength = leastCommonMultiple(
+ bloomCycle.cycleLength,
+ bloomCycle.rootStateCount,
+ );
+ const stateCount = bloomCycle.cycleStartState + combinedCycleLength;
+ const states = Array.from({ length: stateCount }, (_, tick) => {
+ const bloomStateIndex = tick < bloomCycle.stateCount
+ ? tick
+ : bloomCycle.cycleStartState + ((tick - bloomCycle.cycleStartState) % bloomCycle.cycleLength);
+ const bloomState = bloomCycle.states[bloomStateIndex];
+ return Object.freeze({
+ ...bloomState,
+ tick,
+ bloomStateIndex,
+ rotationXDegrees: tick * CSSFLOWER_SOURCE_PROFILE.rotation.xDegreesPerUpdate,
+ rotationYDegrees: tick * CSSFLOWER_SOURCE_PROFILE.rotation.yDegreesPerUpdate,
+ rotationZDegrees: 0,
+ rootStateIndex: tick % bloomCycle.rootStateCount,
+ });
+ });
+ const repeatState = states[bloomCycle.cycleStartState];
+ return Object.freeze({
+ schema: "cssflower-prepared-full-rotation-cycle@1",
+ initialState: 0,
+ stateCount,
+ cycleStartState: bloomCycle.cycleStartState,
+ cycleLength: combinedCycleLength,
+ repeatKey: `${floatBits(repeatState.sf)}:${floatBits(repeatState.sfi)}:${repeatState.rootStateIndex}`,
+ bloomTraceStateCount: bloomCycle.stateCount,
+ bloomCycleStartState: bloomCycle.cycleStartState,
+ bloomCycleLength: bloomCycle.cycleLength,
+ geometryStateCount: bloomCycle.geometryStateCount,
+ geometryStates: bloomCycle.geometryStates,
+ rootStateCount: bloomCycle.rootStateCount,
+ rootTransforms: bloomCycle.rootTransforms,
+ states: Object.freeze(states),
+ });
+}
+
+export function buildPreparedRoundedProductCycle() {
+ const sourceBloom = buildPreparedBloomCycle();
+ const peakGeometryStateIndex = CSSFLOWER_PRODUCT_BLOOM_PEAK_GEOMETRY_STATE;
+ const geometryStates = sourceBloom.geometryStates.slice(0, peakGeometryStateIndex + 1);
+ const bloomGeometryStateIndices = Object.freeze([
+ ...Array.from({ length: peakGeometryStateIndex + 1 }, (_, index) => index),
+ ...Array.from({ length: peakGeometryStateIndex - 1 }, (_, index) => peakGeometryStateIndex - 1 - index),
+ ]);
+ if (bloomGeometryStateIndices.length !== CSSFLOWER_PRODUCT_BLOOM_CYCLE_LENGTH ||
+ geometryStates.length !== peakGeometryStateIndex + 1 ||
+ geometryStates.at(-1)?.sfHex !== "400ffffc") {
+ throw new Error("cssFlower rounded product bloom contract drifted");
+ }
+
+ const sourceIncrement = Math.fround(CSSFLOWER_SOURCE_PROFILE.bloom.sfIncrement);
+ const states = Array.from({ length: CSSFLOWER_PRODUCT_ROTATION_CYCLE_LENGTH }, (_, tick) => {
+ const productBloomPhaseIndex = tick % CSSFLOWER_PRODUCT_BLOOM_CYCLE_LENGTH;
+ const geometryStateIndex = bloomGeometryStateIndices[productBloomPhaseIndex];
+ const geometryState = geometryStates[geometryStateIndex];
+ const sfi = Math.fround(productBloomPhaseIndex < peakGeometryStateIndex
+ ? sourceIncrement
+ : -sourceIncrement);
+ return Object.freeze({
+ tick,
+ sf: geometryState.sf,
+ sfHex: geometryState.sfHex,
+ sfi,
+ sfiHex: floatHex(sfi),
+ rotationXDegrees: tick * CSSFLOWER_SOURCE_PROFILE.rotation.xDegreesPerUpdate,
+ rotationYDegrees: tick * CSSFLOWER_SOURCE_PROFILE.rotation.yDegreesPerUpdate,
+ rotationZDegrees: 0,
+ rootStateIndex: tick,
+ geometryStateIndex,
+ bloomStateIndex: productBloomPhaseIndex,
+ productBloomPhaseIndex,
+ });
+ });
+
+ return Object.freeze({
+ schema: "cssflower-prepared-rounded-product-cycle@1",
+ scope: "source-derived-rounded-cube-to-bloom-with-spike-phase-omitted",
+ initialState: 0,
+ stateCount: CSSFLOWER_PRODUCT_ROTATION_CYCLE_LENGTH,
+ cycleStartState: 0,
+ cycleLength: CSSFLOWER_PRODUCT_ROTATION_CYCLE_LENGTH,
+ repeatKey: `${floatBits(states[0].sf)}:${floatBits(states[0].sfi)}:${states[0].rootStateIndex}`,
+ bloomTraceStateCount: CSSFLOWER_PRODUCT_BLOOM_CYCLE_LENGTH,
+ bloomCycleStartState: 0,
+ bloomCycleLength: CSSFLOWER_PRODUCT_BLOOM_CYCLE_LENGTH,
+ bloomPeakGeometryStateIndex: peakGeometryStateIndex,
+ bloomPeakSf: geometryStates.at(-1).sf,
+ bloomPeakSfHex: geometryStates.at(-1).sfHex,
+ bloomPeakSfNominal: CSSFLOWER_PRODUCT_BLOOM_PEAK_SF_NOMINAL,
+ omittedSourceSfAtOrAbove: 2.5,
+ geometryStateCount: geometryStates.length,
+ geometryStates,
+ rootStateCount: sourceBloom.rootStateCount,
+ rootTransforms: sourceBloom.rootTransforms,
+ states: Object.freeze(states),
+ });
+}
+
+function leastCommonMultiple(left, right) {
+ return left / greatestCommonDivisor(left, right) * right;
+}
+
+function greatestCommonDivisor(left, right) {
+ let a = Math.abs(left);
+ let b = Math.abs(right);
+ while (b !== 0) [a, b] = [b, a % b];
+ return a;
+}
diff --git a/src/adapters/flowerbox/src/prepare/cssflower/compilePreparedCycle.mjs b/src/adapters/flowerbox/src/prepare/cssflower/compilePreparedCycle.mjs
new file mode 100644
index 0000000..68ca2e2
--- /dev/null
+++ b/src/adapters/flowerbox/src/prepare/cssflower/compilePreparedCycle.mjs
@@ -0,0 +1,725 @@
+import { createHash } from "node:crypto";
+import {
+ SOLID_TRIANGLE_CANONICAL_SIZE,
+ computeSolidTrianglePlan,
+ computeTextureAtlasPlanPublic,
+ resolveAtlasLeafBox,
+} from "@layoutit/polycss";
+import { PNG } from "pngjs";
+import {
+ CSSFLOWER_BOUNDARY_SEAM_BLEED,
+ CSSFLOWER_LIGHTING_GRID_COLUMNS,
+ CSSFLOWER_LIGHTING_GRID_DECODED_BYTES,
+ CSSFLOWER_LIGHTING_GRID_HEIGHT,
+ CSSFLOWER_LIGHTING_GRID_ROWS,
+ CSSFLOWER_LIGHTING_GRID_WIDTH,
+ CSSFLOWER_LIGHTING_LAYOUT,
+ CSSFLOWER_LIGHTING_RASTER_MODE,
+ CSSFLOWER_LIGHTING_SAMPLING,
+ CSSFLOWER_LIGHTING_SCHEMA,
+ CSSFLOWER_SEAM_BLEED,
+ CSSFLOWER_SEAM_BLEED_POLICY,
+} from "../../cssflower/renderContract.mjs";
+import {
+ buildPreparedFullRotationCycle,
+ buildPreparedRoundedProductCycle,
+} from "./bloomCycle.mjs";
+import {
+ buildCubeTopology,
+ buildSideSiblingSeamPlan,
+ computeSmoothPointNormals,
+ deformCubePoints,
+ trianglePolygon,
+} from "./cubeTopology.mjs";
+import { CSSFLOWER_CAMERA, CSSFLOWER_SOURCE_PROFILE } from "./sourceProfile.mjs";
+import { auditPreparedQuadMergeEligibility } from "./quadMergeAudit.mjs";
+import {
+ CSSFLOWER_LEAF_RASTER_PAGE_ROWS,
+ buildPreparedLeafRasterLayout,
+ writePreparedLeafRasterLightingTile,
+} from "./leafRasterLighting.mjs";
+
+const MATRIX_COMPONENTS = 16;
+const MINIMUM_RASTER_LEAF_SIZE = 4;
+export const CSSFLOWER_LIGHTING_PAGE_ROWS = CSSFLOWER_LEAF_RASTER_PAGE_ROWS;
+
+export async function compilePreparedCssflowerCycle({
+ nativeAuthorityStatus = "missing",
+ readLightingPage,
+ writeLightingPage,
+} = {}) {
+ if (typeof readLightingPage !== "function" || typeof writeLightingPage !== "function") {
+ throw new TypeError("cssFlower production compilation requires a streaming cached lighting-page store");
+ }
+ assertLittleEndian();
+ const topology = buildCubeTopology();
+ const siblingSeamPlan = buildSideSiblingSeamPlan(topology);
+ if (siblingSeamPlan.policy !== CSSFLOWER_SEAM_BLEED_POLICY) {
+ throw new Error("cssFlower prepared seam policy drifted");
+ }
+ const seamEdgesByMask = buildSeamEdgesByMask();
+ const sourceCycle = buildPreparedFullRotationCycle();
+ const cycle = attachPreparedLightingRows(buildPreparedRoundedProductCycle());
+ const quadMergeAudit = auditPreparedQuadMergeEligibility(topology, sourceCycle);
+ const rasterFaces = selectPreparedRasterFaces(topology, sourceCycle, siblingSeamPlan, seamEdgesByMask);
+ const rasterLayout = buildPreparedLeafRasterLayout(rasterFaces);
+ const matrixValues = new Float32Array(sourceCycle.geometryStateCount * topology.triangleCount * MATRIX_COMPONENTS);
+ const atlasWidth = rasterLayout.atlasWidth;
+ const atlasHeight = rasterLayout.atlasHeight;
+ const stateEvidence = [];
+ const geometryByState = new Array(sourceCycle.geometryStateCount);
+ const canonicalPointIndices = new Uint16Array(sourceCycle.geometryStateCount * topology.triangleCount * 3);
+ let initialPolygons = null;
+
+ for (const geometryState of sourceCycle.geometryStates) {
+ const positions = deformCubePoints(topology, geometryState.sf);
+ const normals = computeSmoothPointNormals(topology, positions);
+ geometryByState[geometryState.index] = Object.freeze({ positions, normals });
+ const transformStateOffset = geometryState.index * topology.triangleCount * MATRIX_COMPONENTS;
+
+ for (const triangle of topology.triangles) {
+ const polygon = trianglePolygon(topology, triangle, positions);
+ const rasterFace = rasterFaces[triangle.index];
+ const seamEdgeMask = siblingSeamPlan.edgeMasks[triangle.index];
+ const seamEdges = seamEdgesByMask[seamEdgeMask];
+ const texturePlan = computeTextureAtlasPlanPublic(polygon, triangle.index, {
+ seamBleed: rasterFace.seamBleed,
+ seamEdges,
+ });
+ const solidPlan = computeSolidTrianglePlan(polygon, triangle.index, {
+ seamBleed: rasterFace.seamBleed,
+ seamEdges,
+ stableTriangleMatrixDecimals: 6,
+ textureLighting: "baked",
+ }, {
+ primitive: "corner",
+ includeColor: false,
+ matrixDecimals: 6,
+ });
+ if (!texturePlan || texturePlan.bleedRatio !== rasterFace.seamBleed ||
+ !sameEdgeSet(texturePlan.seamBleedEdges, seamEdges) ||
+ texturePlan.projectiveMatrix !== null || !solidPlan?.transformText) {
+ throw new Error(`cssFlower state ${geometryState.index} triangle ${triangle.index} lost its prepared seam-bleed triangle plan`);
+ }
+ matrixValues.set(
+ fitCanonicalTransformToRasterLeaf(
+ parseMatrix3d(solidPlan.transformText),
+ rasterFace.leafWidth,
+ rasterFace.leafHeight,
+ ),
+ transformStateOffset + triangle.index * MATRIX_COMPONENTS,
+ );
+ const { a, b, c } = solidPlan.basis;
+ canonicalPointIndices.set([
+ triangle.pointIndices[c],
+ triangle.pointIndices[a],
+ triangle.pointIndices[b],
+ ], (geometryState.index * topology.triangleCount + triangle.index) * 3);
+ }
+
+ if (geometryState.index === sourceCycle.states[0].geometryStateIndex) {
+ initialPolygons = topology.triangles.map((triangle) => trianglePolygon(topology, triangle, positions));
+ }
+ const transformStart = transformStateOffset * Float32Array.BYTES_PER_ELEMENT;
+ const transformEnd = transformStart + topology.triangleCount * MATRIX_COMPONENTS * Float32Array.BYTES_PER_ELEMENT;
+ stateEvidence.push(Object.freeze({
+ geometryStateIndex: geometryState.index,
+ firstTick: geometryState.firstTick,
+ sf: geometryState.sf,
+ sfHex: geometryState.sfHex,
+ positionsSha256: sha256(typedArrayBytes(positions)),
+ normalsSha256: sha256(typedArrayBytes(normals)),
+ transformsSha256: sha256(Buffer.from(matrixValues.buffer, transformStart, transformEnd - transformStart)),
+ }));
+ }
+
+ if (!initialPolygons || initialPolygons.length !== 1200) {
+ throw new Error("cssFlower initial 1,200-triangle product was not compiled");
+ }
+ const productTransformByteLength = cycle.geometryStateCount * topology.triangleCount * MATRIX_COMPONENTS * Float32Array.BYTES_PER_ELEMENT;
+ const transformBytes = Buffer.from(matrixValues.buffer, 0, productTransformByteLength);
+ const vertexLightingByState = Object.freeze(cycle.states.map((state) => {
+ const geometry = geometryByState[state.geometryStateIndex];
+ if (!geometry) throw new Error(`cssFlower lighting state ${state.tick} has no prepared geometry`);
+ return computePreparedVertexLightingUnquantized(
+ topology,
+ geometry.positions,
+ geometry.normals,
+ state,
+ );
+ }));
+ const lightingAddressSchedule = buildPreparedLightingAddressSchedule({
+ topology,
+ cycle,
+ canonicalPointIndices,
+ vertexLightingByState,
+ });
+ const lightingPreparation = await prepareLightingPages({
+ topology,
+ cycle,
+ canonicalPointIndices,
+ atlasWidth,
+ atlasHeight,
+ rasterLayout,
+ vertexLightingByState,
+ readLightingPage,
+ writeLightingPage,
+ });
+ const lightingPages = lightingPreparation.pages;
+ const firstLightingPage = lightingPages[0];
+ const topologyEvidence = topologyEvidenceFor(topology);
+ const evidence = Object.freeze({
+ schema: "cssflower-prepared-state-evidence@1",
+ profileId: CSSFLOWER_SOURCE_PROFILE.id,
+ implementation: "independently-authored-cssflower-preparer",
+ nativeAuthorityStatus,
+ engineIndependence: Object.freeze({
+ status: "pass",
+ preparedInputReadsNativeReplay: false,
+ preparedInputReadsNativeState: false,
+ browserReadsNativeReplay: false,
+ }),
+ topology: topologyEvidence,
+ update: Object.freeze({
+ stateCount: sourceCycle.stateCount,
+ geometryStateCount: sourceCycle.geometryStateCount,
+ cycleStartState: sourceCycle.cycleStartState,
+ cycleLength: sourceCycle.cycleLength,
+ bloomTraceStateCount: sourceCycle.bloomTraceStateCount,
+ bloomCycleLength: sourceCycle.bloomCycleLength,
+ rootStateCount: sourceCycle.rootStateCount,
+ productStateCount: cycle.stateCount,
+ productGeometryStateCount: cycle.geometryStateCount,
+ productCycleLength: cycle.cycleLength,
+ productBloomCycleLength: cycle.bloomCycleLength,
+ productBloomPeakSf: cycle.bloomPeakSf,
+ }),
+ camera: CSSFLOWER_CAMERA,
+ light: CSSFLOWER_SOURCE_PROFILE.light,
+ materials: topologyEvidence.materials,
+ rasterAtlas: Object.freeze({
+ leafSizing: "raster",
+ seamBleed: CSSFLOWER_SEAM_BLEED,
+ boundarySeamBleed: CSSFLOWER_BOUNDARY_SEAM_BLEED,
+ seamBleedPolicy: siblingSeamPlan.policy,
+ boundaryVertexCount: siblingSeamPlan.boundaryVertexCount,
+ boundaryAdjacentTriangleCount: siblingSeamPlan.boundaryAdjacentTriangleCount,
+ sharedEdgeCount: siblingSeamPlan.sharedEdgeCount,
+ sharedEdgeIncidenceCount: siblingSeamPlan.sharedEdgeIncidenceCount,
+ boundaryEdgeCount: siblingSeamPlan.boundaryEdgeCount,
+ boundaryEdgeIncidenceCount: siblingSeamPlan.boundaryEdgeIncidenceCount,
+ canonicalSize: SOLID_TRIANGLE_CANONICAL_SIZE,
+ sampling: rasterLayout.sampling,
+ gutter: rasterLayout.gutter,
+ gutterPolicy: rasterLayout.gutterPolicy,
+ packing: rasterLayout.packing,
+ packingEfficiency: rasterLayout.packingEfficiency,
+ stateSliceHeight: rasterLayout.stateSliceHeight,
+ atlasWidth,
+ atlasHeight,
+ selection: "per-face maximum PolyCSS raster leaf box across every prepared geometry state",
+ faceCount: rasterFaces.length,
+ minimumLeafWidth: Math.min(...rasterFaces.map((face) => face.leafWidth)),
+ maximumLeafWidth: Math.max(...rasterFaces.map((face) => face.leafWidth)),
+ minimumLeafHeight: Math.min(...rasterFaces.map((face) => face.leafHeight)),
+ maximumLeafHeight: Math.max(...rasterFaces.map((face) => face.leafHeight)),
+ pageRows: CSSFLOWER_LIGHTING_PAGE_ROWS,
+ pageCount: lightingPages.length,
+ gridColumns: CSSFLOWER_LIGHTING_GRID_COLUMNS,
+ gridRows: CSSFLOWER_LIGHTING_GRID_ROWS,
+ gridWidth: CSSFLOWER_LIGHTING_GRID_WIDTH,
+ gridHeight: CSSFLOWER_LIGHTING_GRID_HEIGHT,
+ gridDecodedBytes: CSSFLOWER_LIGHTING_GRID_DECODED_BYTES,
+ }),
+ quadMergeAudit,
+ geometryStates: Object.freeze(stateEvidence),
+ ticks: Object.freeze(sourceCycle.states.map((state) => Object.freeze({
+ tick: state.tick,
+ sf: state.sf,
+ sfHex: state.sfHex,
+ sfi: state.sfi,
+ sfiHex: state.sfiHex,
+ rotationXDegrees: state.rotationXDegrees,
+ rotationYDegrees: state.rotationYDegrees,
+ rotationZDegrees: state.rotationZDegrees,
+ rootStateIndex: state.rootStateIndex,
+ geometryStateIndex: state.geometryStateIndex,
+ }))),
+ });
+
+ return Object.freeze({
+ topology,
+ cycle,
+ sourceCycle,
+ initialPolygons: Object.freeze(initialPolygons),
+ transformBytes,
+ transformSha256: sha256(transformBytes),
+ lightingSha256: firstLightingPage.sha256,
+ lightingPages,
+ lightingPageCache: Object.freeze({
+ hitCount: lightingPreparation.cacheHitCount,
+ missCount: lightingPreparation.cacheMissCount,
+ }),
+ lighting: Object.freeze({
+ schema: CSSFLOWER_LIGHTING_SCHEMA,
+ techniqueReference: "cssGraphics Mario prepared space-time texel seam with per-leaf raster-sized fields",
+ topology: "stable PolyCSS triangles select opaque endpoint-aligned leaf-resolution lighting rasters from bounded prepared timeline pages",
+ physicalLayout: CSSFLOWER_LIGHTING_LAYOUT,
+ assetUrl: firstLightingPage.assetUrl,
+ assetSha256: firstLightingPage.sha256,
+ rasterMode: CSSFLOWER_LIGHTING_RASTER_MODE,
+ sampling: CSSFLOWER_LIGHTING_SAMPLING,
+ gutter: rasterLayout.gutter,
+ gutterPolicy: rasterLayout.gutterPolicy,
+ packing: rasterLayout.packing,
+ packingEfficiency: rasterLayout.packingEfficiency,
+ stateSliceHeight: rasterLayout.stateSliceHeight,
+ minimumTileWidth: Math.min(...rasterFaces.map((face) => face.leafWidth)),
+ maximumTileWidth: Math.max(...rasterFaces.map((face) => face.leafWidth)),
+ minimumTileHeight: Math.min(...rasterFaces.map((face) => face.leafHeight)),
+ maximumTileHeight: Math.max(...rasterFaces.map((face) => face.leafHeight)),
+ minimumLeafWidth: Math.min(...rasterFaces.map((face) => face.leafWidth)),
+ maximumLeafWidth: Math.max(...rasterFaces.map((face) => face.leafWidth)),
+ minimumLeafHeight: Math.min(...rasterFaces.map((face) => face.leafHeight)),
+ maximumLeafHeight: Math.max(...rasterFaces.map((face) => face.leafHeight)),
+ leafSizing: "raster",
+ seamBleed: CSSFLOWER_SEAM_BLEED,
+ boundarySeamBleed: CSSFLOWER_BOUNDARY_SEAM_BLEED,
+ seamBleedPolicy: siblingSeamPlan.policy,
+ boundaryVertexCount: siblingSeamPlan.boundaryVertexCount,
+ boundaryAdjacentTriangleCount: siblingSeamPlan.boundaryAdjacentTriangleCount,
+ sharedEdgeCount: siblingSeamPlan.sharedEdgeCount,
+ sharedEdgeIncidenceCount: siblingSeamPlan.sharedEdgeIncidenceCount,
+ boundaryEdgeCount: siblingSeamPlan.boundaryEdgeCount,
+ boundaryEdgeIncidenceCount: siblingSeamPlan.boundaryEdgeIncidenceCount,
+ canonicalLeafSize: SOLID_TRIANGLE_CANONICAL_SIZE,
+ rasterSelection: "per-face maximum resolveAtlasLeafBox(..., raster) across every prepared geometry state",
+ faceCount: topology.triangleCount,
+ timelineRowCount: cycle.stateCount,
+ geometryStateCount: cycle.geometryStateCount,
+ atlasWidth,
+ atlasHeight,
+ gridColumns: CSSFLOWER_LIGHTING_GRID_COLUMNS,
+ gridRows: CSSFLOWER_LIGHTING_GRID_ROWS,
+ gridWidth: CSSFLOWER_LIGHTING_GRID_WIDTH,
+ gridHeight: CSSFLOWER_LIGHTING_GRID_HEIGHT,
+ pageRowCount: CSSFLOWER_LIGHTING_PAGE_ROWS,
+ pageCount: lightingPages.length,
+ pages: lightingPages,
+ totalEncodedBytes: lightingPages.reduce((sum, page) => sum + page.byteLength, 0),
+ decodedBytesPerFullPage: atlasWidth * atlasHeight * 4,
+ decodedGridBytes: CSSFLOWER_LIGHTING_GRID_DECODED_BYTES,
+ addressSchedule: lightingAddressSchedule,
+ backgroundPositionXs: Object.freeze(Array.from(
+ { length: lightingPages.length },
+ (_, pageIndex) => `${-pageIndex * atlasWidth}px`,
+ )),
+ backgroundPositionYs: Object.freeze(Array.from(
+ { length: CSSFLOWER_LIGHTING_PAGE_ROWS },
+ (_, rowIndex) => `${-rowIndex * rasterLayout.stateSliceHeight}px`,
+ )),
+ faces: Object.freeze(rasterFaces.map((face, faceIndex) => {
+ const placement = rasterLayout.placements[faceIndex];
+ return Object.freeze({
+ ...face,
+ tileWidth: face.leafWidth,
+ tileHeight: face.leafHeight,
+ slotX: placement.slotX,
+ slotY: placement.slotY,
+ slotWidth: placement.slotWidth,
+ slotHeight: placement.slotHeight,
+ contentX: placement.contentX,
+ contentY: placement.contentY,
+ backgroundSize: `${CSSFLOWER_LIGHTING_GRID_WIDTH}px ${CSSFLOWER_LIGHTING_GRID_HEIGHT}px`,
+ backgroundPositionX: `${-placement.contentX}px`,
+ backgroundPositionY: `${-placement.contentY}px`,
+ });
+ })),
+ rowSelection: "prepared-exact-rgb8-sparse-leaf-address-schedule",
+ temporalInterpolation: false,
+ sourceModelviewLighting: "identity-set-positional-light-then-Rx-Ry-Rz-with-normalize-and-infinite-viewer",
+ rotationAware: true,
+ runtimeRootFrameVariables: 1,
+ runtimePreparedPagePreload: false,
+ runtimeLightingAssetCount: 1,
+ runtimeLightingCalculations: 0,
+ runtimeAtlasConstruction: 0,
+ }),
+ evidence,
+ quadMergeAudit,
+ siblingSeamPlan,
+ });
+}
+
+function attachPreparedLightingRows(sourceCycle) {
+ return Object.freeze({
+ ...sourceCycle,
+ states: Object.freeze(sourceCycle.states.map((state) => Object.freeze({
+ ...state,
+ lightingPageIndex: Math.floor(state.tick / CSSFLOWER_LIGHTING_PAGE_ROWS),
+ lightingPageRowIndex: state.tick % CSSFLOWER_LIGHTING_PAGE_ROWS,
+ }))),
+ });
+}
+
+async function prepareLightingPages({
+ topology,
+ cycle,
+ canonicalPointIndices,
+ atlasWidth,
+ atlasHeight,
+ rasterLayout,
+ vertexLightingByState,
+ readLightingPage,
+ writeLightingPage,
+}) {
+ const pageCount = Math.ceil(cycle.stateCount / CSSFLOWER_LIGHTING_PAGE_ROWS);
+ const pages = [];
+ let cacheHitCount = 0;
+ let cacheMissCount = 0;
+ for (let pageIndex = 0; pageIndex < pageCount; pageIndex += 1) {
+ const startStateIndex = pageIndex * CSSFLOWER_LIGHTING_PAGE_ROWS;
+ const usedRowCount = Math.min(CSSFLOWER_LIGHTING_PAGE_ROWS, cycle.stateCount - startStateIndex);
+ const expectedPage = Object.freeze({
+ index: pageIndex,
+ startStateIndex,
+ usedRowCount,
+ rowCount: CSSFLOWER_LIGHTING_PAGE_ROWS,
+ assetUrl: lightingPageAssetUrl(pageIndex),
+ role: "cssflower-prepared-full-rotation-leaf-raster-space-time-page",
+ encoding: "PNG-RGB8",
+ opaqueUsedRows: true,
+ width: atlasWidth,
+ height: atlasHeight,
+ decodedBytes: atlasWidth * atlasHeight * 4,
+ });
+ const cachedPage = await readLightingPage(expectedPage);
+ if (cachedPage) {
+ assertPreparedLightingPageDescriptor(cachedPage, expectedPage);
+ pages.push(Object.freeze(cachedPage));
+ cacheHitCount += 1;
+ continue;
+ }
+ cacheMissCount += 1;
+ const atlas = new PNG({ width: atlasWidth, height: atlasHeight, colorType: 6 });
+ const atlasPixels = new Uint32Array(
+ atlas.data.buffer,
+ atlas.data.byteOffset,
+ atlas.data.byteLength / Uint32Array.BYTES_PER_ELEMENT,
+ );
+ for (let rowIndex = 0; rowIndex < usedRowCount; rowIndex += 1) {
+ const state = cycle.states[startStateIndex + rowIndex];
+ const vertexColors = vertexLightingByState[startStateIndex + rowIndex];
+ if (!(vertexColors instanceof Float64Array)) {
+ throw new Error(`cssFlower lighting state ${state.tick} has no prepared vertex colors`);
+ }
+ for (const triangle of topology.triangles) {
+ writePreparedLeafRasterLightingTile({
+ atlasData: atlas.data,
+ atlasPixels,
+ atlasWidth,
+ rowIndex,
+ faceIndex: triangle.index,
+ layout: rasterLayout,
+ canonicalPointIndices,
+ canonicalPointOffset: (state.geometryStateIndex * topology.triangleCount + triangle.index) * 3,
+ vertexColors,
+ });
+ }
+ }
+ const bytes = PNG.sync.write(atlas, { colorType: 2, inputColorType: 6 });
+ const pageWithBytes = Object.freeze({
+ ...expectedPage,
+ byteLength: bytes.length,
+ sha256: sha256(bytes),
+ bytes,
+ });
+ await writeLightingPage(pageWithBytes);
+ const { bytes: _bytes, ...page } = pageWithBytes;
+ pages.push(Object.freeze(page));
+ }
+ return Object.freeze({
+ pages: Object.freeze(pages),
+ cacheHitCount,
+ cacheMissCount,
+ });
+}
+
+function buildPreparedLightingAddressSchedule({
+ topology,
+ cycle,
+ canonicalPointIndices,
+ vertexLightingByState,
+}) {
+ const faceCount = topology.triangleCount;
+ const signatureStride = 9;
+ if (cycle?.stateCount !== 360 || vertexLightingByState?.length !== cycle.stateCount ||
+ !(canonicalPointIndices instanceof Uint16Array) ||
+ canonicalPointIndices.length < cycle.geometryStateCount * faceCount * 3) {
+ throw new Error("cssFlower sparse lighting preparation inputs are incomplete");
+ }
+ const selectedSignatures = new Uint8Array(faceCount * signatureStride);
+ const currentSignature = new Uint8Array(signatureStride);
+ const updateOffsets = new Uint32Array(cycle.stateCount + 1);
+ const updatedFaceIndices = [];
+ for (let stateIndex = 0; stateIndex < cycle.stateCount; stateIndex += 1) {
+ const state = cycle.states[stateIndex];
+ const vertexColors = vertexLightingByState[stateIndex];
+ updateOffsets[stateIndex] = updatedFaceIndices.length;
+ for (let faceIndex = 0; faceIndex < faceCount; faceIndex += 1) {
+ preparedFaceLightingSignature(
+ currentSignature,
+ canonicalPointIndices,
+ (state.geometryStateIndex * faceCount + faceIndex) * 3,
+ vertexColors,
+ );
+ const selectedOffset = faceIndex * signatureStride;
+ let changed = stateIndex === 0;
+ for (let channel = 0; channel < signatureStride && !changed; channel += 1) {
+ changed = selectedSignatures[selectedOffset + channel] !== currentSignature[channel];
+ }
+ if (!changed) continue;
+ selectedSignatures.set(currentSignature, selectedOffset);
+ updatedFaceIndices.push(faceIndex);
+ }
+ }
+ updateOffsets[cycle.stateCount] = updatedFaceIndices.length;
+ if (updateOffsets[1] !== faceCount || updatedFaceIndices.length < faceCount) {
+ throw new Error("cssFlower sparse lighting schedule did not bind the complete retained target");
+ }
+ const indices = new Uint16Array(updatedFaceIndices);
+ const indexBytes = Buffer.from(indices.buffer, indices.byteOffset, indices.byteLength);
+ const counts = Array.from(
+ { length: cycle.stateCount },
+ (_, stateIndex) => updateOffsets[stateIndex + 1] - updateOffsets[stateIndex],
+ );
+ const sortedCounts = [...counts].sort((left, right) => left - right);
+ return Object.freeze({
+ schema: "cssflower-prepared-exact-sparse-lighting-address-schedule@1",
+ stateCount: cycle.stateCount,
+ faceCount,
+ selectionDomain: "prepared-source-vertex-lighting-rgb8",
+ comparison: "exact-three-canonical-point-rgb8-signature-per-retained-triangle",
+ threshold: 0,
+ cycleBoundaryPolicy: "force-all-faces-to-state-zero-on-each-360-state-wrap",
+ updateCount: indices.length,
+ meanUpdatesPerState: indices.length / cycle.stateCount,
+ p95UpdatesPerState: sortedCounts[Math.ceil(sortedCounts.length * 0.95) - 1],
+ maximumUpdatesPerState: sortedCounts.at(-1),
+ offsets: Object.freeze(Array.from(updateOffsets)),
+ faceIndicesEncoding: "base64-u16le-state-major-updated-face-indices",
+ faceIndicesByteLength: indexBytes.length,
+ faceIndicesSha256: sha256(indexBytes),
+ faceIndicesBase64: indexBytes.toString("base64"),
+ runtimeSelection: "prepared-state-range-only-no-lighting-or-geometry-calculation",
+ visualEquivalence: "source-rgb8-exact-with-bounded-location-dependent-lossy-atlas-raster-drift",
+ });
+}
+
+function preparedFaceLightingSignature(
+ output,
+ canonicalPointIndices,
+ canonicalPointOffset,
+ vertexColors,
+) {
+ if (!(output instanceof Uint8Array) || output.length !== 9 ||
+ !(vertexColors instanceof Float64Array)) {
+ throw new TypeError("cssFlower prepared lighting signature buffers are invalid");
+ }
+ for (let vertex = 0; vertex < 3; vertex += 1) {
+ const pointIndex = canonicalPointIndices[canonicalPointOffset + vertex];
+ for (let channel = 0; channel < 3; channel += 1) {
+ output[vertex * 3 + channel] = clampByte(vertexColors[pointIndex * 3 + channel]);
+ }
+ }
+ return output;
+}
+
+function assertPreparedLightingPageDescriptor(actual, expected) {
+ for (const [key, value] of Object.entries(expected)) {
+ if (actual?.[key] !== value) {
+ throw new Error(`cssFlower cached lighting page ${expected.index} drifted at ${key}`);
+ }
+ }
+ if (!Number.isSafeInteger(actual.byteLength) || actual.byteLength < 1 ||
+ !/^[a-f0-9]{64}$/u.test(actual.sha256 ?? "") || "bytes" in actual) {
+ throw new Error(`cssFlower cached lighting page ${expected.index} has an invalid identity`);
+ }
+}
+
+function lightingPageAssetUrl(pageIndex) {
+ return pageIndex === 0
+ ? "/cssflower/assets/flower-box-space-texels.png"
+ : `/cssflower/assets/flower-box-space-texels-page-${String(pageIndex).padStart(3, "0")}.png`;
+}
+
+function selectPreparedRasterFaces(topology, cycle, siblingSeamPlan, seamEdgesByMask) {
+ const faces = topology.triangles.map((triangle) => ({
+ sourceOrder: triangle.index,
+ triangleId: triangle.id,
+ seamEdgeMask: siblingSeamPlan.edgeMasks[triangle.index],
+ seamBleed: siblingSeamPlan.boundaryAdjacentTriangles[triangle.index]
+ ? CSSFLOWER_BOUNDARY_SEAM_BLEED
+ : CSSFLOWER_SEAM_BLEED,
+ boundaryAdjacent: siblingSeamPlan.boundaryAdjacentTriangles[triangle.index],
+ leafWidth: MINIMUM_RASTER_LEAF_SIZE,
+ leafHeight: MINIMUM_RASTER_LEAF_SIZE,
+ }));
+ for (const geometryState of cycle.geometryStates) {
+ const positions = deformCubePoints(topology, geometryState.sf);
+ for (const triangle of topology.triangles) {
+ const polygon = trianglePolygon(topology, triangle, positions);
+ const face = faces[triangle.index];
+ const seamEdges = seamEdgesByMask[siblingSeamPlan.edgeMasks[triangle.index]];
+ const plan = computeTextureAtlasPlanPublic(polygon, triangle.index, {
+ seamBleed: face.seamBleed,
+ seamEdges,
+ });
+ if (!plan || plan.bleedRatio !== face.seamBleed ||
+ !sameEdgeSet(plan.seamBleedEdges, seamEdges) || plan.projectiveMatrix !== null) {
+ throw new Error(`cssFlower raster state ${geometryState.index} triangle ${triangle.index} lost its prepared seam-bleed affine atlas plan`);
+ }
+ const box = resolveAtlasLeafBox(plan, 1, "raster", SOLID_TRIANGLE_CANONICAL_SIZE);
+ if (box.sizing !== "raster" || !Number.isFinite(box.width) || !Number.isFinite(box.height)) {
+ throw new Error(`cssFlower raster state ${geometryState.index} triangle ${triangle.index} has no PolyCSS raster leaf box`);
+ }
+ face.leafWidth = Math.max(face.leafWidth, Math.ceil(box.width));
+ face.leafHeight = Math.max(face.leafHeight, Math.ceil(box.height));
+ }
+ }
+ return Object.freeze(faces.map((face) => Object.freeze(face)));
+}
+
+function buildSeamEdgesByMask() {
+ return Object.freeze(Array.from({ length: 8 }, (_, mask) => new Set(
+ [0, 1, 2].filter((edgeIndex) => (mask & (1 << edgeIndex)) !== 0),
+ )));
+}
+
+function sameEdgeSet(actual, expected) {
+ return actual?.size === expected.size && [...expected].every((edgeIndex) => actual.has(edgeIndex));
+}
+
+function fitCanonicalTransformToRasterLeaf(values, leafWidth, leafHeight) {
+ const fitted = [...values];
+ const xScale = SOLID_TRIANGLE_CANONICAL_SIZE / leafWidth;
+ const yScale = SOLID_TRIANGLE_CANONICAL_SIZE / leafHeight;
+ for (const index of [0, 1, 2]) fitted[index] *= xScale;
+ for (const index of [4, 5, 6]) fitted[index] *= yScale;
+ return fitted;
+}
+
+function topologyEvidenceFor(topology) {
+ return Object.freeze({
+ schema: topology.schema,
+ subdivision: topology.subdivision,
+ sideCount: topology.sideCount,
+ sideLocalPointCount: topology.sideLocalPointCount,
+ triangleCount: topology.triangleCount,
+ topology: topology.topology,
+ merge: topology.merge,
+ pointIdsSha256: sha256(Buffer.from(topology.points.map((point) => point.id).join("\n"))),
+ triangleIdsSha256: sha256(Buffer.from(topology.triangles.map((triangle) => triangle.id).join("\n"))),
+ trianglePointIndicesSha256: sha256(Buffer.from(new Uint16Array(topology.triangles.flatMap((triangle) => triangle.pointIndices)).buffer)),
+ materials: Object.freeze([...new Map(topology.triangles.map((triangle) => [
+ triangle.material.id,
+ Object.freeze({ id: triangle.material.id, color: triangle.material.color }),
+ ])).values()]),
+ });
+}
+
+export function computePreparedVertexLighting(topology, positions, normals, state) {
+ const unquantized = computePreparedVertexLightingUnquantized(topology, positions, normals, state);
+ return Uint8ClampedArray.from(unquantized, clampByte);
+}
+
+export function computePreparedVertexLightingUnquantized(topology, positions, normals, state) {
+ if (!state || ![state.rotationXDegrees, state.rotationYDegrees, state.rotationZDegrees].every(Number.isFinite)) {
+ throw new TypeError("Prepared cssFlower lighting requires one source rotation state");
+ }
+ const out = new Float64Array(topology.points.length * 3);
+ const light = CSSFLOWER_SOURCE_PROFILE.light;
+ const rotation = sourceModelRotation(state);
+ for (const point of topology.points) {
+ const offset = point.index * 3;
+ const position = rotateSourceVector(rotation, [positions[offset], positions[offset + 1], positions[offset + 2]]);
+ const normal = normalize(rotateSourceVector(rotation, [normals[offset], normals[offset + 1], normals[offset + 2]]));
+ const toLight = normalize([light.position[0] - position[0], light.position[1] - position[1], light.position[2] - position[2]]);
+ const halfway = normalize([toLight[0], toLight[1], toLight[2] + 1]);
+ const diffuse = Math.max(0, dot(normal, toLight));
+ const specular = diffuse > 0 ? light.specular[0] * Math.pow(Math.max(0, dot(normal, halfway)), light.shininess) : 0;
+ const base = topology.triangles[point.side * 200].material.rgb;
+ for (let channel = 0; channel < 3; channel += 1) {
+ const ambient = 255 * light.globalAmbient[channel] * light.materialAmbient[channel];
+ out[offset + channel] = Math.max(0, Math.min(255,
+ ambient + base[channel] * diffuse + 255 * specular,
+ ));
+ }
+ }
+ return out;
+}
+
+function sourceModelRotation(state) {
+ const radians = Math.PI / 180;
+ const physicalRootState = Number.isInteger(state.rootStateIndex) ? state.rootStateIndex : null;
+ const x = (physicalRootState === null
+ ? state.rotationXDegrees
+ : physicalRootState * CSSFLOWER_SOURCE_PROFILE.rotation.xDegreesPerUpdate) * radians;
+ const y = (physicalRootState === null
+ ? state.rotationYDegrees
+ : physicalRootState * CSSFLOWER_SOURCE_PROFILE.rotation.yDegreesPerUpdate) * radians;
+ const z = state.rotationZDegrees * radians;
+ return Object.freeze({
+ cx: Math.cos(x), sx: Math.sin(x),
+ cy: Math.cos(y), sy: Math.sin(y),
+ cz: Math.cos(z), sz: Math.sin(z),
+ });
+}
+
+function rotateSourceVector(rotation, value) {
+ const xAfterZ = rotation.cz * value[0] - rotation.sz * value[1];
+ const yAfterZ = rotation.sz * value[0] + rotation.cz * value[1];
+ const xAfterY = rotation.cy * xAfterZ + rotation.sy * value[2];
+ const zAfterY = -rotation.sy * xAfterZ + rotation.cy * value[2];
+ return [
+ xAfterY,
+ rotation.cx * yAfterZ - rotation.sx * zAfterY,
+ rotation.sx * yAfterZ + rotation.cx * zAfterY,
+ ];
+}
+
+function parseMatrix3d(value) {
+ const match = /^matrix3d\(([^)]+)\)$/u.exec(value);
+ if (!match) throw new Error(`Invalid prepared cssFlower transform ${value}`);
+ const values = match[1].split(",").map(Number);
+ if (values.length !== MATRIX_COMPONENTS || values.some((component) => !Number.isFinite(component))) {
+ throw new Error(`Invalid prepared cssFlower matrix component count in ${value}`);
+ }
+ return values;
+}
+
+function typedArrayBytes(value) {
+ return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
+}
+
+function sha256(bytes) {
+ return createHash("sha256").update(bytes).digest("hex");
+}
+
+function normalize(value) {
+ const length = Math.hypot(value[0], value[1], value[2]) || 1;
+ return [value[0] / length, value[1] / length, value[2] / length];
+}
+
+function dot(left, right) {
+ return left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
+}
+
+function clampByte(value) {
+ return Math.max(0, Math.min(255, Math.round(value)));
+}
+
+function assertLittleEndian() {
+ const bytes = new Uint8Array(new Uint32Array([0x01020304]).buffer);
+ if (bytes[0] !== 0x04) throw new Error("cssFlower transform asset requires little-endian Float32");
+}
diff --git a/src/adapters/flowerbox/src/prepare/cssflower/cubeTopology.mjs b/src/adapters/flowerbox/src/prepare/cssflower/cubeTopology.mjs
new file mode 100644
index 0000000..eea2a20
--- /dev/null
+++ b/src/adapters/flowerbox/src/prepare/cssflower/cubeTopology.mjs
@@ -0,0 +1,267 @@
+import {
+ CSSFLOWER_SIDE_MATERIALS,
+ CSSFLOWER_SOURCE_PROFILE,
+ FLOAT,
+ sourceToPolyCss,
+} from "./sourceProfile.mjs";
+
+const SOURCE_CUBE_PLANES = Object.freeze([
+ Object.freeze({ id: "front", base: [-0.5, -0.5, 0.5], xAxis: [1, 0, 0], yAxis: [0, 1, 0] }),
+ Object.freeze({ id: "back", base: [0.5, -0.5, -0.5], xAxis: [-1, 0, 0], yAxis: [0, 1, 0] }),
+ Object.freeze({ id: "top", base: [0.5, 0.5, -0.5], xAxis: [-1, 0, 0], yAxis: [0, 0, 1] }),
+ Object.freeze({ id: "bottom", base: [-0.5, -0.5, -0.5], xAxis: [1, 0, 0], yAxis: [0, 0, 1] }),
+ Object.freeze({ id: "right", base: [0.5, -0.5, -0.5], xAxis: [0, 1, 0], yAxis: [0, 0, 1] }),
+ Object.freeze({ id: "left", base: [-0.5, 0.5, -0.5], xAxis: [0, -1, 0], yAxis: [0, 0, 1] }),
+]);
+
+export function buildCubeTopology(subdivision = CSSFLOWER_SOURCE_PROFILE.subdivision) {
+ if (subdivision !== 10) throw new RangeError("The first cssFlower slice requires subdivision 10");
+ const points = [];
+ const triangles = [];
+ const strips = [];
+ const sideStride = (subdivision + 1) ** 2;
+
+ for (let side = 0; side < SOURCE_CUBE_PLANES.length; side += 1) {
+ const plane = SOURCE_CUBE_PLANES[side];
+ for (let sourceX = 0; sourceX <= subdivision; sourceX += 1) {
+ const x = FLOAT(FLOAT(sourceX) / FLOAT(subdivision));
+ for (let sourceY = 0; sourceY <= subdivision; sourceY += 1) {
+ const y = FLOAT(FLOAT(sourceY) / FLOAT(subdivision));
+ const source = mapToSourcePlane(plane, x, y);
+ const scaledDistance = multiplyFloat(sourceLength(source), 2);
+ const radialCoefficient = divideFloat(subtractFloat(1, scaledDistance), scaledDistance);
+ const pointIndex = points.length;
+ points.push(Object.freeze({
+ id: `side-${String(side).padStart(2, "0")}-point-${String(sourceX * (subdivision + 1) + sourceY).padStart(3, "0")}`,
+ index: pointIndex,
+ side,
+ sideId: plane.id,
+ row: sourceX,
+ column: sourceY,
+ source: Object.freeze(source),
+ radialCoefficient,
+ }));
+ }
+ }
+
+ const index = (sourceX, sourceY) => side * sideStride + sourceX * (subdivision + 1) + sourceY;
+ for (let strip = 0; strip < subdivision; strip += 1) {
+ const stripPointIndices = [];
+ for (let sourceY = 0; sourceY <= subdivision; sourceY += 1) {
+ stripPointIndices.push(index(strip, sourceY), index(strip + 1, sourceY));
+ }
+ strips.push(Object.freeze({ side, strip, pointIndices: Object.freeze(stripPointIndices) }));
+ for (let stripIndex = 2; stripIndex < stripPointIndices.length; stripIndex += 1) {
+ const pointIndices = (stripIndex & 1) === 0
+ ? [stripPointIndices[stripIndex - 2], stripPointIndices[stripIndex - 1], stripPointIndices[stripIndex]]
+ : [stripPointIndices[stripIndex - 1], stripPointIndices[stripIndex - 2], stripPointIndices[stripIndex]];
+ const stripTriangle = stripIndex - 2;
+ const triangleIndex = triangles.length;
+ triangles.push(Object.freeze({
+ id: `side-${String(side).padStart(2, "0")}-strip-${String(strip).padStart(2, "0")}-triangle-${String(stripTriangle).padStart(2, "0")}`,
+ index: triangleIndex,
+ side,
+ sideId: plane.id,
+ strip,
+ column: Math.floor(stripTriangle / 2),
+ stripTriangle,
+ cellTriangle: stripTriangle & 1,
+ material: CSSFLOWER_SIDE_MATERIALS[side],
+ pointIndices: Object.freeze(pointIndices),
+ }));
+ }
+ }
+ }
+
+ if (points.length !== 726 || triangles.length !== 1200) {
+ throw new Error(`cssFlower cube topology drifted (${points.length} points, ${triangles.length} triangles)`);
+ }
+ return Object.freeze({
+ schema: "cssflower-cube-topology@1",
+ subdivision,
+ sideCount: SOURCE_CUBE_PLANES.length,
+ sideLocalPointCount: points.length,
+ triangleCount: triangles.length,
+ topology: "six-side-local-ordered-triangle-strips",
+ merge: false,
+ points: Object.freeze(points),
+ strips: Object.freeze(strips),
+ triangles: Object.freeze(triangles),
+ });
+}
+
+export function buildSideSiblingSeamPlan(topology) {
+ if (topology?.triangleCount !== 1200 || topology?.sideCount !== 6) {
+ throw new TypeError("cssFlower sibling seams require the retained default-cube topology");
+ }
+ const edgeOwners = new Map();
+ const edgeMasks = new Array(topology.triangleCount).fill(0);
+ for (const triangle of topology.triangles) {
+ for (let edgeIndex = 0; edgeIndex < 3; edgeIndex += 1) {
+ const first = triangle.pointIndices[edgeIndex];
+ const second = triangle.pointIndices[(edgeIndex + 1) % 3];
+ const low = Math.min(first, second);
+ const high = Math.max(first, second);
+ const key = `${triangle.side}:${low}:${high}`;
+ const record = edgeOwners.get(key) ?? { pointIndices: Object.freeze([low, high]), owners: [] };
+ record.owners.push(Object.freeze({ triangleIndex: triangle.index, edgeIndex }));
+ edgeOwners.set(key, record);
+ }
+ }
+
+ const boundaryPointIndices = new Set();
+ for (const { pointIndices, owners } of edgeOwners.values()) {
+ if (owners.length !== 1) continue;
+ boundaryPointIndices.add(pointIndices[0]);
+ boundaryPointIndices.add(pointIndices[1]);
+ }
+
+ let sharedEdgeCount = 0;
+ let boundaryEdgeCount = 0;
+ for (const { owners } of edgeOwners.values()) {
+ if (owners.length === 1) {
+ boundaryEdgeCount += 1;
+ continue;
+ }
+ if (owners.length !== 2) {
+ throw new Error(`cssFlower side-local edge has ${owners.length} owners`);
+ }
+ sharedEdgeCount += 1;
+ for (const owner of owners) {
+ edgeMasks[owner.triangleIndex] |= 1 << owner.edgeIndex;
+ }
+ }
+ const sharedEdgeIncidenceCount = sharedEdgeCount * 2;
+ const boundaryEdgeIncidenceCount = boundaryEdgeCount;
+ const boundaryAdjacentTriangles = topology.triangles.map((triangle) => (
+ triangle.pointIndices.some((pointIndex) => boundaryPointIndices.has(pointIndex))
+ ));
+ const boundaryAdjacentTriangleCount = boundaryAdjacentTriangles.filter(Boolean).length;
+ if (sharedEdgeCount !== 1680 || boundaryEdgeCount !== 240 ||
+ sharedEdgeIncidenceCount !== 3360 || boundaryEdgeIncidenceCount !== 240 ||
+ boundaryPointIndices.size !== 240 || boundaryAdjacentTriangleCount !== 432 ||
+ edgeMasks.reduce((sum, mask) => sum + bitCount3(mask), 0) !== sharedEdgeIncidenceCount ||
+ edgeMasks.some((mask) => mask < 1 || mask > 7)) {
+ throw new Error("cssFlower side-local sibling seam inventory drifted");
+ }
+ return Object.freeze({
+ schema: "cssflower-side-sibling-seam-plan@1",
+ policy: "side-local-shared-edges-boundary-ring-damped",
+ boundaryVertexCount: boundaryPointIndices.size,
+ boundaryAdjacentTriangles: Object.freeze(boundaryAdjacentTriangles),
+ boundaryAdjacentTriangleCount,
+ edgeMasks: Object.freeze(edgeMasks),
+ sharedEdgeCount,
+ sharedEdgeIncidenceCount,
+ boundaryEdgeCount,
+ boundaryEdgeIncidenceCount,
+ });
+}
+
+export function deformCubePoints(topology, sf) {
+ const positions = new Float32Array(topology.points.length * 3);
+ for (const point of topology.points) {
+ const scale = addFloat(multiplyFloat(point.radialCoefficient, FLOAT(sf)), 1);
+ const offset = point.index * 3;
+ positions[offset] = multiplyFloat(point.source[0], scale);
+ positions[offset + 1] = multiplyFloat(point.source[1], scale);
+ positions[offset + 2] = multiplyFloat(point.source[2], scale);
+ }
+ return positions;
+}
+
+export function computeSmoothPointNormals(topology, positions) {
+ const sums = new Float32Array(topology.points.length * 3);
+ for (const strip of topology.strips) {
+ let index1 = strip.pointIndices[0];
+ let index2 = strip.pointIndices[1];
+ for (let stripTriangle = 0; stripTriangle < strip.pointIndices.length - 2; stripTriangle += 1) {
+ const index3 = strip.pointIndices[stripTriangle + 2];
+ const a = index1 * 3;
+ const b = index2 * 3;
+ const c = index3 * 3;
+ const v1 = [
+ subtractFloat(positions[c], positions[a]),
+ subtractFloat(positions[c + 1], positions[a + 1]),
+ subtractFloat(positions[c + 2], positions[a + 2]),
+ ];
+ const v2 = [
+ subtractFloat(positions[b], positions[a]),
+ subtractFloat(positions[b + 1], positions[a + 1]),
+ subtractFloat(positions[b + 2], positions[a + 2]),
+ ];
+ const normal = [
+ subtractFloat(multiplyFloat(v1[1], v2[2]), multiplyFloat(v2[1], v1[2])),
+ subtractFloat(multiplyFloat(v1[2], v2[0]), multiplyFloat(v2[2], v1[0])),
+ subtractFloat(multiplyFloat(v1[0], v2[1]), multiplyFloat(v2[0], v1[1])),
+ ];
+ if ((stripTriangle & 1) === 0) {
+ normal[0] = FLOAT(-normal[0]);
+ normal[1] = FLOAT(-normal[1]);
+ normal[2] = FLOAT(-normal[2]);
+ }
+ for (const pointIndex of [index1, index2, index3]) {
+ const offset = pointIndex * 3;
+ sums[offset] = addFloat(sums[offset], normal[0]);
+ sums[offset + 1] = addFloat(sums[offset + 1], normal[1]);
+ sums[offset + 2] = addFloat(sums[offset + 2], normal[2]);
+ }
+ index1 = index2;
+ index2 = index3;
+ }
+ }
+ return sums;
+}
+
+export function trianglePolygon(topology, triangle, positions) {
+ return {
+ vertices: triangle.pointIndices.map((pointIndex) => {
+ const offset = pointIndex * 3;
+ return sourceToPolyCss([positions[offset], positions[offset + 1], positions[offset + 2]]);
+ }),
+ color: triangle.material.color,
+ data: {
+ "cssflower-triangle": triangle.id,
+ "cssflower-leaf-index": triangle.index,
+ "cssflower-side": triangle.side,
+ "cssflower-side-id": triangle.sideId,
+ "cssflower-strip": triangle.strip,
+ "cssflower-strip-triangle": triangle.stripTriangle,
+ "cssflower-material": triangle.material.id,
+ "cssflower-seam-bleed": 0,
+ },
+ };
+}
+
+function mapToSourcePlane(plane, x, y) {
+ return [0, 1, 2].map((component) => addFloat(
+ addFloat(multiplyFloat(x, plane.xAxis[component]), multiplyFloat(y, plane.yAxis[component])),
+ plane.base[component],
+ ));
+}
+
+function sourceLength(value) {
+ const xy = addFloat(multiplyFloat(value[0], value[0]), multiplyFloat(value[1], value[1]));
+ const xyz = addFloat(xy, multiplyFloat(value[2], value[2]));
+ return FLOAT(Math.sqrt(xyz));
+}
+
+function addFloat(left, right) {
+ return FLOAT(FLOAT(left) + FLOAT(right));
+}
+
+function subtractFloat(left, right) {
+ return FLOAT(FLOAT(left) - FLOAT(right));
+}
+
+function multiplyFloat(left, right) {
+ return FLOAT(FLOAT(left) * FLOAT(right));
+}
+
+function divideFloat(left, right) {
+ return FLOAT(FLOAT(left) / FLOAT(right));
+}
+
+function bitCount3(mask) {
+ return (mask & 1) + ((mask >> 1) & 1) + ((mask >> 2) & 1);
+}
diff --git a/src/adapters/flowerbox/src/prepare/cssflower/dataSource.mjs b/src/adapters/flowerbox/src/prepare/cssflower/dataSource.mjs
new file mode 100644
index 0000000..87b1f57
--- /dev/null
+++ b/src/adapters/flowerbox/src/prepare/cssflower/dataSource.mjs
@@ -0,0 +1,78 @@
+import { existsSync, readFileSync, statSync } from "node:fs";
+import { resolve } from "node:path";
+
+export const NATIVE_ROOT_ENV = "CSSFLOWER_NATIVE_ROOT";
+const EXPECTED_SOURCE_COMMIT = "9478d25a677f70dbe4fc0ed317cc5a5e5050ef8b";
+const REPO_ROOT = resolve(import.meta.dirname, "../../../../../..");
+const RETAINED_NATIVE_COMPARISON = resolve(
+ REPO_ROOT,
+ ".local/oracle/cssflower/native/state/native-cssflower-compare.json",
+);
+
+export async function resolveCssflowerDataSource(options = {}) {
+ const rawRoot = options.nativeRoot ?? process.env[NATIVE_ROOT_ENV] ?? "";
+ const qualification = retainedNativeQualification();
+ if (rawRoot) {
+ const root = resolve(String(rawRoot));
+ if (!existsSync(root) || !statSync(root).isDirectory()) {
+ throw new Error(NATIVE_ROOT_ENV + " does not point to a readable directory: " + root);
+ }
+ return {
+ kind: "user-supplied-native-authority",
+ env: NATIVE_ROOT_ENV,
+ root,
+ publicLabel: NATIVE_ROOT_ENV,
+ legalLabel: "owned-local-authority-not-redistributed",
+ nativeAuthorityStatus: qualification ? "available-qualified" : "unqualified",
+ nativeQualification: qualification,
+ };
+ }
+
+ if (qualification) {
+ return {
+ kind: "identity-bound-local-native-authority",
+ env: null,
+ root: null,
+ publicLabel: "ignored local identity-bound native oracle",
+ legalLabel: "local-oracle-evidence-not-redistributed",
+ nativeAuthorityStatus: "available-qualified",
+ nativeQualification: qualification,
+ };
+ }
+
+ return {
+ kind: "documented-source-behavior",
+ env: null,
+ root: null,
+ publicLabel: "src/adapters/flowerbox/README.md",
+ legalLabel: "independently-authored-results-only",
+ nativeAuthorityStatus: "missing",
+ nativeQualification: null,
+ };
+}
+
+function retainedNativeQualification() {
+ if (!existsSync(RETAINED_NATIVE_COMPARISON)) return null;
+ try {
+ const report = JSON.parse(readFileSync(RETAINED_NATIVE_COMPARISON, "utf8"));
+ if (report?.status !== "pass" ||
+ report?.nativeBinding?.sourceCommit !== EXPECTED_SOURCE_COMMIT ||
+ report?.nativeStateWasNotUsedAsCandidateInput !== true ||
+ report?.firstDivergence !== null ||
+ report?.comparedTickCount !== 9_331) {
+ return null;
+ }
+ return Object.freeze({
+ schema: report.schema,
+ status: report.status,
+ sourceCommit: report.nativeBinding.sourceCommit,
+ executableSha256: report.nativeBinding.executable.sha256,
+ compilerSha256: report.nativeBinding.compiler.sha256,
+ comparedTickCount: report.comparedTickCount,
+ firstDivergence: report.firstDivergence,
+ candidateIndependence: report.engineIndependence.status,
+ });
+ } catch {
+ return null;
+ }
+}
diff --git a/src/adapters/flowerbox/src/prepare/cssflower/leafRasterLighting.mjs b/src/adapters/flowerbox/src/prepare/cssflower/leafRasterLighting.mjs
new file mode 100644
index 0000000..41bec9d
--- /dev/null
+++ b/src/adapters/flowerbox/src/prepare/cssflower/leafRasterLighting.mjs
@@ -0,0 +1,430 @@
+import {
+ CSSFLOWER_LIGHTING_ATLAS_WIDTH,
+ CSSFLOWER_LIGHTING_GUTTER,
+ CSSFLOWER_LIGHTING_MICRO_CONTENT_SIZE,
+ CSSFLOWER_LIGHTING_PAGE_ROWS,
+ CSSFLOWER_LIGHTING_SAMPLING,
+} from "../../cssflower/renderContract.mjs";
+
+export const CSSFLOWER_LEAF_RASTER_GUTTER = 1;
+export const CSSFLOWER_LEAF_RASTER_ATLAS_WIDTH = 768;
+export const CSSFLOWER_LEAF_RASTER_PAGE_ROWS = CSSFLOWER_LIGHTING_PAGE_ROWS;
+export const CSSFLOWER_LEAF_RASTER_MAX_TEXTURE_DIMENSION = 8_192;
+
+const lightingWeightCache = new Map();
+const affineMicroWeightCache = new Map();
+
+export function buildPreparedLeafRasterLayout(faces, options = {}) {
+ if (!Array.isArray(faces) || faces.length < 1) {
+ throw new TypeError("Prepared cssFlower leaf raster layout requires faces");
+ }
+ const atlasWidth = options.atlasWidth ?? CSSFLOWER_LEAF_RASTER_ATLAS_WIDTH;
+ const pageRowCount = options.pageRowCount ?? CSSFLOWER_LEAF_RASTER_PAGE_ROWS;
+ const gutter = options.gutter ?? CSSFLOWER_LEAF_RASTER_GUTTER;
+ const maximumTextureDimension = options.maximumTextureDimension ??
+ CSSFLOWER_LEAF_RASTER_MAX_TEXTURE_DIMENSION;
+ if (!Number.isSafeInteger(atlasWidth) || atlasWidth < 1 || atlasWidth > maximumTextureDimension ||
+ !Number.isSafeInteger(pageRowCount) || pageRowCount < 1 ||
+ !Number.isSafeInteger(gutter) || gutter < 1 ||
+ !Number.isSafeInteger(maximumTextureDimension) || maximumTextureDimension < 1) {
+ throw new RangeError("Prepared cssFlower leaf raster layout options are invalid");
+ }
+
+ const tiles = faces.map((face, faceIndex) => {
+ const sourceOrder = Number(face?.sourceOrder);
+ const width = Number(face?.leafWidth);
+ const height = Number(face?.leafHeight);
+ if (!Number.isSafeInteger(sourceOrder) || sourceOrder !== faceIndex ||
+ !Number.isSafeInteger(width) || width < 2 ||
+ !Number.isSafeInteger(height) || height < 2) {
+ throw new TypeError(`Prepared cssFlower leaf raster face ${faceIndex} is invalid`);
+ }
+ const slotWidth = width + gutter * 2;
+ const slotHeight = height + gutter * 2;
+ if (slotWidth > atlasWidth) {
+ throw new RangeError(`Prepared cssFlower leaf raster face ${faceIndex} exceeds the atlas width`);
+ }
+ return { sourceOrder, width, height, slotWidth, slotHeight };
+ }).sort((left, right) => (
+ right.slotHeight - left.slotHeight ||
+ right.slotWidth - left.slotWidth ||
+ left.sourceOrder - right.sourceOrder
+ ));
+
+ const placements = new Array(faces.length);
+ let x = 0;
+ let y = 0;
+ let shelfHeight = 0;
+ let shelfCount = 1;
+ for (const tile of tiles) {
+ if (x > 0 && x + tile.slotWidth > atlasWidth) {
+ y += shelfHeight;
+ x = 0;
+ shelfHeight = 0;
+ shelfCount += 1;
+ }
+ placements[tile.sourceOrder] = Object.freeze({
+ sourceOrder: tile.sourceOrder,
+ slotX: x,
+ slotY: y,
+ slotWidth: tile.slotWidth,
+ slotHeight: tile.slotHeight,
+ contentX: x + gutter,
+ contentY: y + gutter,
+ width: tile.width,
+ height: tile.height,
+ });
+ x += tile.slotWidth;
+ shelfHeight = Math.max(shelfHeight, tile.slotHeight);
+ }
+ const stateSliceHeight = y + shelfHeight;
+ const atlasHeight = stateSliceHeight * pageRowCount;
+ if (atlasHeight > maximumTextureDimension) {
+ throw new RangeError(
+ `Prepared cssFlower leaf raster page ${atlasWidth}x${atlasHeight} exceeds ${maximumTextureDimension}`,
+ );
+ }
+ const occupiedPixelsPerState = placements.reduce((sum, placement) => (
+ sum + placement.slotWidth * placement.slotHeight
+ ), 0);
+ const decodedBytesPerFullPage = atlasWidth * atlasHeight * 4;
+ return Object.freeze({
+ schema: "cssflower-prepared-leaf-raster-layout@1",
+ packing: "height-descending-deterministic-shelves",
+ sampling: "endpoint-aligned-pixel-centers",
+ gutterPolicy: "one-pixel-clamped-edge-duplication",
+ atlasWidth,
+ atlasHeight,
+ pageRowCount,
+ stateSliceHeight,
+ gutter,
+ shelfCount,
+ maximumTextureDimension,
+ occupiedPixelsPerState,
+ packedPixelsPerState: atlasWidth * stateSliceHeight,
+ packingEfficiency: occupiedPixelsPerState / (atlasWidth * stateSliceHeight),
+ decodedBytesPerFullPage,
+ placements: Object.freeze(placements),
+ });
+}
+
+export function buildPreparedAffineMicroRasterLayout(faces, options = {}) {
+ if (!Array.isArray(faces) || faces.length < 1) {
+ throw new TypeError("Prepared cssFlower affine micro-raster layout requires faces");
+ }
+ const atlasWidth = options.atlasWidth ?? CSSFLOWER_LIGHTING_ATLAS_WIDTH;
+ const pageRowCount = options.pageRowCount ?? CSSFLOWER_LIGHTING_PAGE_ROWS;
+ const contentSize = options.contentSize ?? CSSFLOWER_LIGHTING_MICRO_CONTENT_SIZE;
+ const gutter = options.gutter ?? CSSFLOWER_LIGHTING_GUTTER;
+ const maximumTextureDimension = options.maximumTextureDimension ??
+ CSSFLOWER_LEAF_RASTER_MAX_TEXTURE_DIMENSION;
+ const slotSize = contentSize + gutter * 2;
+ if (!Number.isSafeInteger(atlasWidth) || atlasWidth < slotSize || atlasWidth > maximumTextureDimension ||
+ !Number.isSafeInteger(pageRowCount) || pageRowCount < 1 ||
+ !Number.isSafeInteger(contentSize) || contentSize < 2 ||
+ !Number.isSafeInteger(gutter) || gutter < 1 ||
+ !Number.isSafeInteger(maximumTextureDimension) || maximumTextureDimension < 1) {
+ throw new RangeError("Prepared cssFlower affine micro-raster layout options are invalid");
+ }
+ for (let index = 0; index < faces.length; index += 1) {
+ const face = faces[index];
+ if (face?.sourceOrder !== index || !Number.isSafeInteger(face.leafWidth) || face.leafWidth < 2 ||
+ !Number.isSafeInteger(face.leafHeight) || face.leafHeight < 2) {
+ throw new TypeError(`Prepared cssFlower affine micro-raster face ${index} is invalid`);
+ }
+ }
+ const tilesPerShelf = Math.floor(atlasWidth / slotSize);
+ const shelfCount = Math.ceil(faces.length / tilesPerShelf);
+ const stateSliceHeight = shelfCount * slotSize;
+ const atlasHeight = stateSliceHeight * pageRowCount;
+ if (atlasHeight > maximumTextureDimension) {
+ throw new RangeError(
+ `Prepared cssFlower affine micro-raster page ${atlasWidth}x${atlasHeight} exceeds ${maximumTextureDimension}`,
+ );
+ }
+ const placements = Object.freeze(faces.map((face, index) => {
+ const slotX = (index % tilesPerShelf) * slotSize;
+ const slotY = Math.floor(index / tilesPerShelf) * slotSize;
+ return Object.freeze({
+ sourceOrder: face.sourceOrder,
+ slotX,
+ slotY,
+ slotWidth: slotSize,
+ slotHeight: slotSize,
+ contentX: slotX + gutter,
+ contentY: slotY + gutter,
+ width: contentSize,
+ height: contentSize,
+ leafWidth: face.leafWidth,
+ leafHeight: face.leafHeight,
+ });
+ }));
+ const occupiedPixelsPerState = faces.length * slotSize * slotSize;
+ const packedPixelsPerState = atlasWidth * stateSliceHeight;
+ return Object.freeze({
+ schema: "cssflower-prepared-affine-micro-raster-layout@1",
+ packing: "source-order-fixed-square-micro-texel-shelves",
+ sampling: CSSFLOWER_LIGHTING_SAMPLING,
+ gutterPolicy: "one-affine-extrapolated-sample-plus-outer-clamped-padding",
+ displayExpansion: "per-leaf-static-background-size",
+ atlasWidth,
+ atlasHeight,
+ pageRowCount,
+ stateSliceHeight,
+ contentSize,
+ gutter,
+ slotSize,
+ tilesPerShelf,
+ shelfCount,
+ maximumTextureDimension,
+ occupiedPixelsPerState,
+ contentPixelsPerState: faces.length * contentSize * contentSize,
+ packedPixelsPerState,
+ packingEfficiency: occupiedPixelsPerState / packedPixelsPerState,
+ decodedBytesPerFullPage: atlasWidth * atlasHeight * 4,
+ placements,
+ });
+}
+
+export function writePreparedAffineMicroLightingTile({
+ atlasData,
+ atlasPixels,
+ atlasWidth,
+ rowIndex,
+ faceIndex,
+ layout,
+ canonicalPointIndices,
+ canonicalPointOffset = 0,
+ vertexColors,
+}) {
+ if (!(atlasData instanceof Uint8Array)) {
+ throw new TypeError("Prepared cssFlower affine micro-raster atlas data must be bytes");
+ }
+ const placement = layout?.placements?.[faceIndex];
+ const validPointIndices = Array.isArray(canonicalPointIndices) ||
+ canonicalPointIndices instanceof Uint16Array ||
+ canonicalPointIndices instanceof Uint32Array;
+ const validVertexColors = vertexColors instanceof Uint8Array ||
+ vertexColors instanceof Uint8ClampedArray ||
+ vertexColors instanceof Float32Array ||
+ vertexColors instanceof Float64Array;
+ const pixels = atlasPixels ?? (
+ atlasData.byteOffset % Uint32Array.BYTES_PER_ELEMENT === 0
+ ? new Uint32Array(
+ atlasData.buffer,
+ atlasData.byteOffset,
+ atlasData.byteLength / Uint32Array.BYTES_PER_ELEMENT,
+ )
+ : null
+ );
+ if (!pixels || pixels.length * Uint32Array.BYTES_PER_ELEMENT !== atlasData.byteLength ||
+ layout?.schema !== "cssflower-prepared-affine-micro-raster-layout@1" ||
+ !placement || atlasWidth !== layout.atlasWidth ||
+ !Number.isSafeInteger(rowIndex) || rowIndex < 0 || rowIndex >= layout.pageRowCount ||
+ !validPointIndices || !Number.isSafeInteger(canonicalPointOffset) || canonicalPointOffset < 0 ||
+ canonicalPointOffset + 3 > canonicalPointIndices.length || !validVertexColors) {
+ throw new TypeError("Prepared cssFlower affine micro-raster tile inputs are invalid");
+ }
+ const point0 = canonicalPointIndices[canonicalPointOffset];
+ const point1 = canonicalPointIndices[canonicalPointOffset + 1];
+ const point2 = canonicalPointIndices[canonicalPointOffset + 2];
+ const weights = preparedAffineMicroWeights(layout.contentSize, layout.gutter);
+ const originX = placement.contentX - layout.gutter;
+ const originY = rowIndex * layout.stateSliceHeight + placement.contentY - layout.gutter;
+ let weightOffset = 0;
+ for (let y = 0; y < layout.slotSize; y += 1) {
+ for (let x = 0; x < layout.slotSize; x += 1) {
+ const apex = weights[weightOffset];
+ const left = weights[weightOffset + 1];
+ const right = weights[weightOffset + 2];
+ weightOffset += 3;
+ const red = clampByte(
+ vertexColors[point0 * 3] * apex +
+ vertexColors[point1 * 3] * left +
+ vertexColors[point2 * 3] * right,
+ );
+ const green = clampByte(
+ vertexColors[point0 * 3 + 1] * apex +
+ vertexColors[point1 * 3 + 1] * left +
+ vertexColors[point2 * 3 + 1] * right,
+ );
+ const blue = clampByte(
+ vertexColors[point0 * 3 + 2] * apex +
+ vertexColors[point1 * 3 + 2] * left +
+ vertexColors[point2 * 3 + 2] * right,
+ );
+ pixels[(originY + y) * atlasWidth + originX + x] =
+ red | (green << 8) | (blue << 16) | (255 << 24);
+ }
+ }
+}
+
+export function writePreparedLeafRasterLightingTile({
+ atlasData,
+ atlasPixels,
+ atlasWidth,
+ rowIndex,
+ faceIndex,
+ layout,
+ canonicalPointIndices,
+ canonicalPointOffset = 0,
+ vertexColors,
+}) {
+ if (!(atlasData instanceof Uint8Array)) {
+ throw new TypeError("Prepared cssFlower leaf raster atlas data must be bytes");
+ }
+ const placement = layout?.placements?.[faceIndex];
+ const validPointIndices = Array.isArray(canonicalPointIndices) ||
+ canonicalPointIndices instanceof Uint16Array ||
+ canonicalPointIndices instanceof Uint32Array;
+ const validVertexColors = vertexColors instanceof Uint8Array ||
+ vertexColors instanceof Uint8ClampedArray ||
+ vertexColors instanceof Float32Array ||
+ vertexColors instanceof Float64Array;
+ const pixels = atlasPixels ?? (
+ atlasData.byteOffset % Uint32Array.BYTES_PER_ELEMENT === 0
+ ? new Uint32Array(
+ atlasData.buffer,
+ atlasData.byteOffset,
+ atlasData.byteLength / Uint32Array.BYTES_PER_ELEMENT,
+ )
+ : null
+ );
+ if (!pixels || pixels.length * Uint32Array.BYTES_PER_ELEMENT !== atlasData.byteLength ||
+ !placement || atlasWidth !== layout.atlasWidth ||
+ !Number.isSafeInteger(rowIndex) || rowIndex < 0 || rowIndex >= layout.pageRowCount ||
+ !validPointIndices ||
+ !Number.isSafeInteger(canonicalPointOffset) || canonicalPointOffset < 0 ||
+ canonicalPointOffset + 3 > canonicalPointIndices.length ||
+ !validVertexColors) {
+ throw new TypeError("Prepared cssFlower leaf raster tile inputs are invalid");
+ }
+ const point0 = canonicalPointIndices[canonicalPointOffset];
+ const point1 = canonicalPointIndices[canonicalPointOffset + 1];
+ const point2 = canonicalPointIndices[canonicalPointOffset + 2];
+ const originX = placement.contentX;
+ const originY = rowIndex * layout.stateSliceHeight + placement.contentY;
+ const weights = preparedLightingWeights(placement.width, placement.height);
+ let weightOffset = 0;
+ for (let y = 0; y < placement.height; y += 1) {
+ let firstPixel = 0;
+ let lastPixel = 0;
+ for (let x = 0; x < placement.width; x += 1) {
+ const apex = weights[weightOffset];
+ const left = weights[weightOffset + 1];
+ const right = weights[weightOffset + 2];
+ weightOffset += 3;
+ const red = clampByte(
+ vertexColors[point0 * 3] * apex +
+ vertexColors[point1 * 3] * left +
+ vertexColors[point2 * 3] * right,
+ );
+ const green = clampByte(
+ vertexColors[point0 * 3 + 1] * apex +
+ vertexColors[point1 * 3 + 1] * left +
+ vertexColors[point2 * 3 + 1] * right,
+ );
+ const blue = clampByte(
+ vertexColors[point0 * 3 + 2] * apex +
+ vertexColors[point1 * 3 + 2] * left +
+ vertexColors[point2 * 3 + 2] * right,
+ );
+ const pixel = red | (green << 8) | (blue << 16) | (255 << 24);
+ pixels[(originY + y) * atlasWidth + originX + x] = pixel;
+ if (x === 0) firstPixel = pixel;
+ if (x === placement.width - 1) lastPixel = pixel;
+ }
+ const rowOffset = (originY + y) * atlasWidth;
+ for (let gutterX = 1; gutterX <= layout.gutter; gutterX += 1) {
+ pixels[rowOffset + originX - gutterX] = firstPixel;
+ pixels[rowOffset + originX + placement.width - 1 + gutterX] = lastPixel;
+ }
+ }
+ const slotX = originX - layout.gutter;
+ const slotWidth = placement.width + layout.gutter * 2;
+ const firstRowOffset = originY * atlasWidth + slotX;
+ const lastRowOffset = (originY + placement.height - 1) * atlasWidth + slotX;
+ for (let gutterY = 1; gutterY <= layout.gutter; gutterY += 1) {
+ pixels.copyWithin(
+ (originY - gutterY) * atlasWidth + slotX,
+ firstRowOffset,
+ firstRowOffset + slotWidth,
+ );
+ pixels.copyWithin(
+ (originY + placement.height - 1 + gutterY) * atlasWidth + slotX,
+ lastRowOffset,
+ lastRowOffset + slotWidth,
+ );
+ }
+}
+
+function preparedLightingWeights(width, height) {
+ const key = `${width}x${height}`;
+ const cached = lightingWeightCache.get(key);
+ if (cached) return cached;
+ const weights = new Float64Array(width * height * 3);
+ let offset = 0;
+ for (let y = 0; y < height; y += 1) {
+ const v = y / (height - 1);
+ for (let x = 0; x < width; x += 1) {
+ const u = x / (width - 1);
+ const apex = 1 - v;
+ const right = u - 0.5 * apex;
+ const left = 1 - apex - right;
+ weights[offset] = apex;
+ weights[offset + 1] = left;
+ weights[offset + 2] = right;
+ offset += 3;
+ }
+ }
+ lightingWeightCache.set(key, weights);
+ return weights;
+}
+
+function preparedAffineMicroWeights(contentSize, gutter) {
+ const key = `${contentSize}:${gutter}`;
+ const cached = affineMicroWeightCache.get(key);
+ if (cached) return cached;
+ const slotSize = contentSize + gutter * 2;
+ const weights = new Float64Array(slotSize * slotSize * 3);
+ const minimumSample = -0.5 / contentSize;
+ const maximumSample = 1 + 0.5 / contentSize;
+ let offset = 0;
+ for (let localY = -gutter; localY < contentSize + gutter; localY += 1) {
+ const v = clamp((localY + 0.5) / contentSize, minimumSample, maximumSample);
+ for (let localX = -gutter; localX < contentSize + gutter; localX += 1) {
+ const u = clamp((localX + 0.5) / contentSize, minimumSample, maximumSample);
+ const apex = 1 - v;
+ const right = u - 0.5 * apex;
+ const left = 1 - apex - right;
+ weights[offset] = apex;
+ weights[offset + 1] = left;
+ weights[offset + 2] = right;
+ offset += 3;
+ }
+ }
+ affineMicroWeightCache.set(key, weights);
+ return weights;
+}
+
+export function leafRasterBackgroundBinding(layout, faceIndex, rowIndex) {
+ const placement = layout?.placements?.[faceIndex];
+ if (!placement || !Number.isSafeInteger(rowIndex) || rowIndex < 0 || rowIndex >= layout.pageRowCount) {
+ throw new RangeError("Prepared cssFlower leaf raster background binding is invalid");
+ }
+ return Object.freeze({
+ backgroundSize: `${layout.atlasWidth}px ${layout.atlasHeight}px`,
+ backgroundPositionX: `${-placement.contentX}px`,
+ backgroundPositionY: `${-(rowIndex * layout.stateSliceHeight + placement.contentY)}px`,
+ temporalOffsetY: `${-rowIndex * layout.stateSliceHeight}px`,
+ faceOffsetY: `${-placement.contentY}px`,
+ });
+}
+
+function clampByte(value) {
+ return Math.max(0, Math.min(255, Math.round(value)));
+}
+
+function clamp(value, minimum, maximum) {
+ return Math.max(minimum, Math.min(maximum, value));
+}
diff --git a/src/adapters/flowerbox/src/prepare/cssflower/paths.mjs b/src/adapters/flowerbox/src/prepare/cssflower/paths.mjs
new file mode 100644
index 0000000..1bdc928
--- /dev/null
+++ b/src/adapters/flowerbox/src/prepare/cssflower/paths.mjs
@@ -0,0 +1,95 @@
+import { fileURLToPath } from "node:url";
+import { join, resolve } from "node:path";
+import {
+ CSSFLOWER_LIGHTING_ATLAS_EXTENSION,
+ CSSFLOWER_PROJECTED_ATLAS_EXTENSION,
+} from "../../cssflower/renderContract.mjs";
+
+export const repoRoot = resolve(fileURLToPath(new URL("../../../../../../", import.meta.url)));
+export const adapterRoot = resolve(fileURLToPath(new URL("../../../", import.meta.url)));
+export const localRoot = join(repoRoot, ".local", "cssflower");
+export const generatedRoot = resolve(
+ process.env.CSSFLOWER_GENERATED_ROOT ?? join(repoRoot, "build", "generated"),
+);
+export const generatedPublicRoot = join(generatedRoot, "public", "cssflower");
+export const generatedSceneDir = join(generatedPublicRoot, "scenes");
+export const generatedAssetDir = join(generatedPublicRoot, "assets");
+export const generatedTransformAssetDir = join(generatedAssetDir, "transforms");
+export const generatedLightingAssetDir = join(generatedAssetDir, "lighting");
+export const generatedProjectedAssetDir = join(generatedAssetDir, "projected");
+export const manifestPath = join(generatedPublicRoot, "manifest.json");
+export const generatedTransformsPath = join(generatedAssetDir, "flower-box-transforms.f32");
+export const generatedLightingPath = join(generatedAssetDir, "flower-box-space-texels.png");
+export const generatedStateEvidencePath = join(generatedAssetDir, "flower-box-state-evidence.json");
+export const localPreparedTransformsPath = join(localRoot, "prepared", "flower-box-transforms.f32");
+
+export function generatedScenePath(sceneId) {
+ return join(generatedSceneDir, sceneId + ".json");
+}
+
+export function generatedSceneUrl(sceneId) {
+ return "/cssflower/scenes/" + sceneId + ".json";
+}
+
+export function generatedLightingPagePath(pageIndex) {
+ if (!Number.isSafeInteger(pageIndex) || pageIndex < 0) {
+ throw new RangeError("cssFlower lighting page index must be a non-negative integer");
+ }
+ return pageIndex === 0
+ ? generatedLightingPath
+ : join(generatedAssetDir, `flower-box-space-texels-page-${String(pageIndex).padStart(3, "0")}.png`);
+}
+
+export function generatedTransformBlockPath(sha256) {
+ assertSha256(sha256);
+ return join(generatedTransformAssetDir, `block-${sha256}.matrix3d.pack`);
+}
+
+export function generatedTransformBlockUrl(sha256) {
+ assertSha256(sha256);
+ return `/cssflower/assets/transforms/block-${sha256}.matrix3d.pack`;
+}
+
+export function generatedPreparedLightingPath(sha256) {
+ assertSha256(sha256);
+ return join(generatedLightingAssetDir, `grid-${sha256}.${CSSFLOWER_LIGHTING_ATLAS_EXTENSION}`);
+}
+
+export function generatedPreparedLightingUrl(sha256) {
+ assertSha256(sha256);
+ return `/cssflower/assets/lighting/grid-${sha256}.${CSSFLOWER_LIGHTING_ATLAS_EXTENSION}`;
+}
+
+export function generatedProjectedAtlasPath(sha256) {
+ assertSha256(sha256);
+ return join(generatedProjectedAssetDir, `atlas-${sha256}.${CSSFLOWER_PROJECTED_ATLAS_EXTENSION}`);
+}
+
+export function generatedProjectedAtlasUrl(sha256) {
+ assertSha256(sha256);
+ return `/cssflower/assets/projected/atlas-${sha256}.${CSSFLOWER_PROJECTED_ATLAS_EXTENSION}`;
+}
+
+export function generatedProjectedLayoutPath(sha256) {
+ assertSha256(sha256);
+ return join(generatedProjectedAssetDir, `layout-${sha256}.i16`);
+}
+
+export function generatedProjectedLayoutUrl(sha256) {
+ assertSha256(sha256);
+ return `/cssflower/assets/projected/layout-${sha256}.i16`;
+}
+
+export function generatedSharedLayoutBlockPath(sha256) {
+ assertSha256(sha256);
+ return join(generatedProjectedAssetDir, `layout-block-${sha256}.i16pack`);
+}
+
+export function generatedSharedLayoutBlockUrl(sha256) {
+ assertSha256(sha256);
+ return `/cssflower/assets/projected/layout-block-${sha256}.i16pack`;
+}
+
+function assertSha256(value) {
+ if (!/^[a-f0-9]{64}$/u.test(value ?? "")) throw new TypeError("cssFlower content-addressed asset hash is invalid");
+}
diff --git a/src/adapters/flowerbox/src/prepare/cssflower/prepare.mjs b/src/adapters/flowerbox/src/prepare/cssflower/prepare.mjs
new file mode 100644
index 0000000..d2e1705
--- /dev/null
+++ b/src/adapters/flowerbox/src/prepare/cssflower/prepare.mjs
@@ -0,0 +1,43 @@
+import {
+ buildCssflowerFirstSliceScene,
+} from "./sceneBuilder.mjs";
+import { compilePreparedCssflowerCycle } from "./compilePreparedCycle.mjs";
+import {
+ resolveCssflowerDataSource,
+} from "./dataSource.mjs";
+import {
+ writeCssflowerPreparedOutput,
+} from "./writeManifest.mjs";
+import {
+ writeCssflowerPreparedAssets,
+ createCssflowerPreparedLightingPageStore,
+} from "./writePreparedAssets.mjs";
+
+export async function prepareCssflower(options = {}) {
+ const dataSource = await resolveCssflowerDataSource({
+ nativeRoot: options.nativeRoot,
+ });
+ const sceneId = options.scene ?? "default-cube";
+ const lightingPageStore = await createCssflowerPreparedLightingPageStore();
+ const compiled = await compilePreparedCssflowerCycle({
+ nativeAuthorityStatus: dataSource?.nativeAuthorityStatus ?? "missing",
+ readLightingPage: lightingPageStore.read,
+ writeLightingPage: lightingPageStore.write,
+ });
+ const assets = await writeCssflowerPreparedAssets(compiled, { lightingPageStore });
+ const { scene } = await buildCssflowerFirstSliceScene({
+ compiled,
+ dataSource,
+ preparedAssets: assets,
+ sceneId,
+ });
+ const output = await writeCssflowerPreparedOutput({
+ scenes: [scene],
+ defaultSceneId: scene.id,
+ });
+ return Object.freeze({
+ ...output,
+ scene,
+ assets: Object.freeze({ ...assets, lightingPageCache: lightingPageStore.stats() }),
+ });
+}
diff --git a/src/adapters/flowerbox/src/prepare/cssflower/projectedPixels.mjs b/src/adapters/flowerbox/src/prepare/cssflower/projectedPixels.mjs
new file mode 100644
index 0000000..d4d25e9
--- /dev/null
+++ b/src/adapters/flowerbox/src/prepare/cssflower/projectedPixels.mjs
@@ -0,0 +1,1024 @@
+import { createHash } from "node:crypto";
+import { PNG } from "pngjs";
+import {
+ buildPreparedFullRotationCycle,
+ buildPreparedRoundedProductCycle,
+} from "./bloomCycle.mjs";
+import {
+ buildCubeTopology,
+ computeSmoothPointNormals,
+ deformCubePoints,
+} from "./cubeTopology.mjs";
+import { computePreparedVertexLightingUnquantized } from "./compilePreparedCycle.mjs";
+import { CSSFLOWER_SOURCE_PROFILE } from "./sourceProfile.mjs";
+
+const EDGE = CSSFLOWER_SOURCE_PROFILE.camera.stagePixels;
+const HALF_EDGE = EDGE / 2;
+const CAMERA = CSSFLOWER_SOURCE_PROFILE.camera;
+const FOCAL_PIXELS = HALF_EDGE / Math.tan(CAMERA.fovDegrees * Math.PI / 360);
+const ATLAS_WIDTH = 2_048;
+const SPACE_TIME_ATLAS_WIDTH = 8_192;
+const DEPTH_MAX = (1 << 16) - 1;
+const EPSILON = 1e-10;
+const topology = buildCubeTopology();
+const cycle = buildPreparedFullRotationCycle();
+const roundedProductOwnedPixelCounts = new Map();
+const roundedProductTriangleAdjacency = new Map();
+
+export function buildCssflowerPreparedInverseRootTransforms() {
+ return Object.freeze(Array.from({ length: cycle.rootStateCount }, (_, rootStateIndex) => (
+ preparedInverseRootTransform({ rootStateIndex })
+ )));
+}
+
+export function buildCssflowerPreparedFrontFacingSchedule() {
+ return buildFrontFacingSchedule(cycle);
+}
+
+export function buildCssflowerPreparedRoundedFrontFacingSchedule(dilationTicks = 0) {
+ if (!Number.isSafeInteger(dilationTicks) || dilationTicks < 0 || dilationTicks > 4) {
+ throw new RangeError("Prepared cssFlower front-face dilation must be an integer from zero through four");
+ }
+ const schedule = buildFrontFacingSchedule(buildPreparedRoundedProductCycle());
+ if (dilationTicks === 0) return schedule;
+ return Object.freeze(schedule.map((_, stateIndex) => {
+ const selected = new Uint8Array(topology.triangleCount);
+ for (let offset = -dilationTicks; offset <= dilationTicks; offset += 1) {
+ const neighbor = (stateIndex + offset + schedule.length) % schedule.length;
+ for (const leafIndex of schedule[neighbor]) selected[leafIndex] = 1;
+ }
+ return Object.freeze(Array.from(selected, (value, leafIndex) => value === 1 ? leafIndex : -1)
+ .filter((leafIndex) => leafIndex >= 0));
+ }));
+}
+
+export function buildCssflowerPreparedRoundedOcclusionSchedule(options = {}) {
+ const {
+ adjacency = "edge",
+ adjacencyRings = 1,
+ minimumOwnedPixels = 1,
+ sampleGrid = 1,
+ temporalDilationTicks = 1,
+ } = options;
+ if (!["edge", "vertex"].includes(adjacency) ||
+ !Number.isSafeInteger(adjacencyRings) || adjacencyRings < 0 || adjacencyRings > 4 ||
+ !Number.isSafeInteger(minimumOwnedPixels) || minimumOwnedPixels < 1 || minimumOwnedPixels > EDGE * EDGE ||
+ ![1, 2, 4].includes(sampleGrid) ||
+ !Number.isSafeInteger(temporalDilationTicks) || temporalDilationTicks < 0 || temporalDilationTicks > 4) {
+ throw new RangeError("Prepared cssFlower occlusion schedule options are invalid");
+ }
+ const ownedPixelCounts = getRoundedProductOwnedPixelCounts(sampleGrid);
+ const adjacencyTable = adjacencyRings > 0 ? triangleAdjacency(adjacency) : null;
+ const spatialSchedule = ownedPixelCounts.map((counts) => {
+ const selected = new Uint8Array(topology.triangleCount);
+ for (let leafIndex = 0; leafIndex < counts.length; leafIndex += 1) {
+ if (counts[leafIndex] >= minimumOwnedPixels) selected[leafIndex] = 1;
+ }
+ for (let ring = 0; ring < adjacencyRings; ring += 1) {
+ const previous = selected.slice();
+ for (let leafIndex = 0; leafIndex < previous.length; leafIndex += 1) {
+ if (previous[leafIndex] !== 1) continue;
+ for (const neighbor of adjacencyTable[leafIndex]) selected[neighbor] = 1;
+ }
+ }
+ return selected;
+ });
+ return Object.freeze(spatialSchedule.map((_, stateIndex) => {
+ const selected = new Uint8Array(topology.triangleCount);
+ for (let offset = -temporalDilationTicks; offset <= temporalDilationTicks; offset += 1) {
+ const neighbor = (stateIndex + offset + spatialSchedule.length) % spatialSchedule.length;
+ for (let leafIndex = 0; leafIndex < topology.triangleCount; leafIndex += 1) {
+ selected[leafIndex] |= spatialSchedule[neighbor][leafIndex];
+ }
+ }
+ return Object.freeze(Array.from(selected, (value, leafIndex) => value === 1 ? leafIndex : -1)
+ .filter((leafIndex) => leafIndex >= 0));
+ }));
+}
+
+function getRoundedProductOwnedPixelCounts(sampleGrid) {
+ const cached = roundedProductOwnedPixelCounts.get(sampleGrid);
+ if (cached) return cached;
+ const productCycle = buildPreparedRoundedProductCycle();
+ const sampleCount = sampleGrid * sampleGrid;
+ const owner = new Int16Array(EDGE * EDGE * sampleCount);
+ const depth = new Uint16Array(EDGE * EDGE * sampleCount);
+ const counts = new Uint32Array(topology.triangleCount);
+ const result = Object.freeze(productCycle.states.map((state) => {
+ owner.fill(-1);
+ depth.fill(DEPTH_MAX);
+ counts.fill(0);
+ const positions = deformCubePoints(topology, state.sf);
+ const rotation = sourceModelRotation(state);
+ const projectedPoints = topology.points.map((point) => {
+ const pointOffset = point.index * 3;
+ const world = rotateSourceVector(rotation, [
+ positions[pointOffset],
+ positions[pointOffset + 1],
+ positions[pointOffset + 2],
+ ]);
+ const eyeDistance = CAMERA.eye[2] - world[2];
+ if (eyeDistance < CAMERA.near || eyeDistance > CAMERA.far) {
+ throw new Error(`cssFlower prepared occlusion point ${point.index} left the source clip interval`);
+ }
+ return {
+ x: HALF_EDGE + FOCAL_PIXELS * world[0] / eyeDistance,
+ y: HALF_EDGE - FOCAL_PIXELS * world[1] / eyeDistance,
+ windowDepth: windowDepthForEyeDistance(eyeDistance),
+ };
+ });
+ for (const triangle of topology.triangles) {
+ const first = projectedPoints[triangle.pointIndices[0]];
+ const second = projectedPoints[triangle.pointIndices[1]];
+ const third = projectedPoints[triangle.pointIndices[2]];
+ const area = orient(first, second, third);
+ if (area >= -EPSILON) continue;
+ const inverseArea = 1 / area;
+ const minX = Math.max(0, Math.floor(Math.min(first.x, second.x, third.x)));
+ const maxX = Math.min(EDGE - 1, Math.floor(Math.max(first.x, second.x, third.x)));
+ const minY = Math.max(0, Math.floor(Math.min(first.y, second.y, third.y)));
+ const maxY = Math.min(EDGE - 1, Math.floor(Math.max(first.y, second.y, third.y)));
+ for (let y = minY; y <= maxY; y += 1) {
+ for (let x = minX; x <= maxX; x += 1) {
+ const pixelIndex = y * EDGE + x;
+ for (let sampleY = 0; sampleY < sampleGrid; sampleY += 1) {
+ for (let sampleX = 0; sampleX < sampleGrid; sampleX += 1) {
+ const sample = {
+ x: x + (sampleX + 0.5) / sampleGrid,
+ y: y + (sampleY + 0.5) / sampleGrid,
+ };
+ const weight0 = orient(second, third, sample) * inverseArea;
+ const weight1 = orient(third, first, sample) * inverseArea;
+ const weight2 = orient(first, second, sample) * inverseArea;
+ if (weight0 < -EPSILON || weight1 < -EPSILON || weight2 < -EPSILON) continue;
+ const windowDepth = weight0 * first.windowDepth +
+ weight1 * second.windowDepth + weight2 * third.windowDepth;
+ const depth16 = Math.max(0, Math.min(DEPTH_MAX, Math.round(windowDepth * DEPTH_MAX)));
+ const sampleIndex = pixelIndex * sampleCount + sampleY * sampleGrid + sampleX;
+ if (depth16 >= depth[sampleIndex]) continue;
+ const previousOwner = owner[sampleIndex];
+ if (previousOwner >= 0) counts[previousOwner] -= 1;
+ owner[sampleIndex] = triangle.index;
+ depth[sampleIndex] = depth16;
+ counts[triangle.index] += 1;
+ }
+ }
+ }
+ }
+ }
+ const result = Object.freeze(Array.from(counts));
+ if (!result.some((count) => count > 0)) {
+ throw new Error(`Prepared cssFlower occlusion schedule is empty at tick ${state.tick}`);
+ }
+ return result;
+ }));
+ roundedProductOwnedPixelCounts.set(sampleGrid, result);
+ return result;
+}
+
+function triangleAdjacency(kind) {
+ const cached = roundedProductTriangleAdjacency.get(kind);
+ if (cached) return cached;
+ const owners = new Map();
+ for (const triangle of topology.triangles) {
+ const pointKeys = triangle.pointIndices.map((pointIndex) => topology.points[pointIndex].source.join(","));
+ const keys = kind === "edge"
+ ? [
+ [pointKeys[0], pointKeys[1]].sort().join("|"),
+ [pointKeys[1], pointKeys[2]].sort().join("|"),
+ [pointKeys[2], pointKeys[0]].sort().join("|"),
+ ]
+ : pointKeys;
+ for (const key of keys) {
+ const indices = owners.get(key) ?? [];
+ indices.push(triangle.index);
+ owners.set(key, indices);
+ }
+ }
+ const neighbors = Array.from({ length: topology.triangleCount }, () => new Set());
+ for (const indices of owners.values()) {
+ for (const left of indices) {
+ for (const right of indices) {
+ if (left !== right) neighbors[left].add(right);
+ }
+ }
+ }
+ const result = Object.freeze(neighbors.map((values) => Object.freeze([...values].sort((a, b) => a - b))));
+ roundedProductTriangleAdjacency.set(kind, result);
+ return result;
+}
+
+function buildFrontFacingSchedule(selectedCycle) {
+ return Object.freeze(selectedCycle.states.map((state) => {
+ const positions = deformCubePoints(topology, state.sf);
+ const rotation = sourceModelRotation(state);
+ const projectedPoints = topology.points.map((point) => {
+ const offset = point.index * 3;
+ const world = rotateSourceVector(rotation, [
+ positions[offset],
+ positions[offset + 1],
+ positions[offset + 2],
+ ]);
+ const eyeDistance = CAMERA.eye[2] - world[2];
+ return Object.freeze({
+ x: HALF_EDGE + FOCAL_PIXELS * world[0] / eyeDistance,
+ y: HALF_EDGE - FOCAL_PIXELS * world[1] / eyeDistance,
+ });
+ });
+ const leafIndices = topology.triangles
+ .filter((triangle) => {
+ const vertices = triangle.pointIndices.map((pointIndex) => projectedPoints[pointIndex]);
+ return orient(vertices[0], vertices[1], vertices[2]) < -EPSILON;
+ })
+ .map((triangle) => triangle.index);
+ if (leafIndices.length < 1 || leafIndices.length >= topology.triangleCount) {
+ throw new Error(`Prepared cssFlower front-face count ${leafIndices.length} drifted at tick ${state.tick}`);
+ }
+ return Object.freeze(leafIndices);
+ }));
+}
+
+export function scanCssflowerProjectedLeafBounds() {
+ const leaves = Array.from({ length: topology.triangleCount }, (_, index) => ({
+ index,
+ maxWidth: 0,
+ maxHeight: 0,
+ maxArea: 0,
+ maxAreaTick: -1,
+ visibleStateCount: 0,
+ }));
+ const states = [];
+ for (const state of cycle.states) {
+ const positions = deformCubePoints(topology, state.sf);
+ const rotation = sourceModelRotation(state);
+ const projectedPoints = topology.points.map((point) => {
+ const offset = point.index * 3;
+ const world = rotateSourceVector(rotation, [
+ positions[offset],
+ positions[offset + 1],
+ positions[offset + 2],
+ ]);
+ const eyeDistance = CAMERA.eye[2] - world[2];
+ return Object.freeze({
+ x: HALF_EDGE + FOCAL_PIXELS * world[0] / eyeDistance,
+ y: HALF_EDGE - FOCAL_PIXELS * world[1] / eyeDistance,
+ });
+ });
+ let frontFacingTriangleCount = 0;
+ let boundingArea = 0;
+ let maxLeafArea = 0;
+ let maxLeafWidth = 0;
+ let maxLeafHeight = 0;
+ for (const triangle of topology.triangles) {
+ const vertices = triangle.pointIndices.map((pointIndex) => projectedPoints[pointIndex]);
+ if (orient(vertices[0], vertices[1], vertices[2]) >= -EPSILON) continue;
+ frontFacingTriangleCount += 1;
+ const minX = Math.max(0, Math.ceil(Math.min(...vertices.map((value) => value.x)) - 0.5));
+ const maxX = Math.min(EDGE - 1, Math.floor(Math.max(...vertices.map((value) => value.x)) - 0.5));
+ const minY = Math.max(0, Math.ceil(Math.min(...vertices.map((value) => value.y)) - 0.5));
+ const maxY = Math.min(EDGE - 1, Math.floor(Math.max(...vertices.map((value) => value.y)) - 0.5));
+ const width = Math.max(0, maxX - minX + 1);
+ const height = Math.max(0, maxY - minY + 1);
+ const area = width * height;
+ const leaf = leaves[triangle.index];
+ leaf.visibleStateCount += 1;
+ leaf.maxWidth = Math.max(leaf.maxWidth, width);
+ leaf.maxHeight = Math.max(leaf.maxHeight, height);
+ if (area > leaf.maxArea) {
+ leaf.maxArea = area;
+ leaf.maxAreaTick = state.tick;
+ }
+ boundingArea += area;
+ maxLeafArea = Math.max(maxLeafArea, area);
+ maxLeafWidth = Math.max(maxLeafWidth, width);
+ maxLeafHeight = Math.max(maxLeafHeight, height);
+ }
+ states.push(Object.freeze({
+ tick: state.tick,
+ sf: state.sf,
+ sfHex: state.sfHex,
+ geometryStateIndex: state.geometryStateIndex,
+ rootStateIndex: state.rootStateIndex,
+ frontFacingTriangleCount,
+ boundingArea,
+ maxLeafArea,
+ maxLeafWidth,
+ maxLeafHeight,
+ }));
+ }
+ const fixedSlotArea = leaves.reduce((sum, leaf) => sum + leaf.maxWidth * leaf.maxHeight, 0);
+ return Object.freeze({
+ schema: "cssflower-projected-leaf-bound-scan@1",
+ stateCount: states.length,
+ retainedLeafCount: leaves.length,
+ fixedSlotArea,
+ fixedSlotDecodedBytesPerFrame: fixedSlotArea * 4,
+ maximumLeafWidth: Math.max(...leaves.map((leaf) => leaf.maxWidth)),
+ maximumLeafHeight: Math.max(...leaves.map((leaf) => leaf.maxHeight)),
+ maximumLeafArea: Math.max(...leaves.map((leaf) => leaf.maxArea)),
+ leaves: Object.freeze(leaves.map((leaf) => Object.freeze(leaf))),
+ states: Object.freeze(states),
+ });
+}
+
+export function prepareCssflowerProjectedPixelSpaceTimeBank(ticks) {
+ if (!Array.isArray(ticks) || ticks.length < 1 ||
+ ticks.some((tick) => !Number.isSafeInteger(tick) || tick < 0) ||
+ new Set(ticks).size !== ticks.length) {
+ throw new TypeError("Prepared projected space-time bank requires unique non-negative safe ticks");
+ }
+ const states = ticks.map((tick) => rasterizeCssflowerProjectedPixels(tick));
+ const patchesByState = states.map((state) => state.leaves);
+ const slots = Array.from({ length: topology.triangleCount }, (_, index) => {
+ const visiblePatches = patchesByState
+ .map((patchesForState) => patchesForState[index])
+ .filter((patch) => patch.pixelCount > 0);
+ const left = visiblePatches.length ? Math.min(...visiblePatches.map((patch) => patch.left)) : EDGE;
+ const top = visiblePatches.length ? Math.min(...visiblePatches.map((patch) => patch.top)) : EDGE;
+ const right = visiblePatches.length ? Math.max(...visiblePatches.map((patch) => patch.right)) : -1;
+ const bottom = visiblePatches.length ? Math.max(...visiblePatches.map((patch) => patch.bottom)) : -1;
+ return {
+ index,
+ pixelCount: visiblePatches.reduce((sum, patch) => sum + patch.pixelCount, 0),
+ left,
+ top,
+ right,
+ bottom,
+ width: Math.max(0, right - left + 1),
+ height: Math.max(0, bottom - top + 1),
+ atlasX: 0,
+ atlasY: 0,
+ };
+ });
+ const atlasHeight = packSpaceTimeLeafStrips(slots, states.length);
+ const atlas = new PNG({ width: SPACE_TIME_ATLAS_WIDTH, height: atlasHeight, colorType: 6 });
+ for (let stateIndex = 0; stateIndex < states.length; stateIndex += 1) {
+ const state = states[stateIndex];
+ const frame = state.frameImage;
+ const patches = patchesByState[stateIndex];
+ for (let pixelIndex = 0; pixelIndex < state.owner.length; pixelIndex += 1) {
+ const triangleIndex = state.owner[pixelIndex];
+ if (triangleIndex < 0) continue;
+ const slot = slots[triangleIndex];
+ const x = pixelIndex % EDGE;
+ const y = Math.floor(pixelIndex / EDGE);
+ const atlasX = slot.atlasX + x - slot.left;
+ const atlasY = slot.atlasY + stateIndex * slot.height + y - slot.top;
+ const sourceOffset = pixelIndex * 4;
+ const atlasOffset = (atlasY * atlas.width + atlasX) * 4;
+ atlas.data[atlasOffset] = frame.data[sourceOffset];
+ atlas.data[atlasOffset + 1] = frame.data[sourceOffset + 1];
+ atlas.data[atlasOffset + 2] = frame.data[sourceOffset + 2];
+ atlas.data[atlasOffset + 3] = 255;
+ }
+ }
+ const atlasBytes = PNG.sync.write(atlas, { colorType: 6, inputColorType: 6 });
+ const leafCss = slots.map((slot) => slot.pixelCount === 0
+ ? hiddenBankLeafCss()
+ : spaceTimeBankLeafCss(slot, atlas.width, atlas.height));
+ const packets = states.map((state, stateIndex) => {
+ const sourceState = cycle.states[state.timelineStateIndex];
+ return Object.freeze({
+ tick: state.tick,
+ sf: state.state.sf,
+ rootTransform: state.rootTransform,
+ meshTransform: preparedInverseRootTransform(sourceState),
+ frameIndex: stateIndex,
+ visibleLeafCount: state.topology.visibleLeafCount,
+ });
+ });
+ return Object.freeze({
+ schema: "cssflower-prepared-projected-pixel-space-time-bank@1",
+ layout: "source-order-fixed-union-leaf-strips-by-consecutive-source-frame-rows",
+ ticks: Object.freeze([...ticks]),
+ retainedLeafCount: topology.triangleCount,
+ activeUnionLeafCount: slots.filter((slot) => slot.pixelCount > 0).length,
+ frameCount: states.length,
+ slots: Object.freeze(slots.map((slot) => Object.freeze({ ...slot }))),
+ leafCss: Object.freeze(leafCss),
+ packets: Object.freeze(packets),
+ atlas: Object.freeze({
+ width: atlas.width,
+ height: atlas.height,
+ bytes: atlasBytes,
+ sha256: sha256(atlasBytes),
+ dataUrl: `data:image/png;base64,${atlasBytes.toString("base64")}`,
+ }),
+ states: Object.freeze(states.map(projectedStateDescriptor)),
+ authority: Object.freeze({
+ precedent: "cssgraphics-super-mario-64-source-order-face-columns-by-source-frame-rows",
+ input: "independently-prepared-cssflower-source-state",
+ nativeStateIngestion: false,
+ nativePixelIngestion: false,
+ runtimeProjection: false,
+ runtimeRasterization: false,
+ runtimeGeometryConstruction: false,
+ }),
+ });
+}
+
+export function prepareCssflowerProjectedPixelBank(ticks) {
+ if (!Array.isArray(ticks) || ticks.length < 1 ||
+ ticks.some((tick) => !Number.isSafeInteger(tick) || tick < 0) ||
+ new Set(ticks).size !== ticks.length) {
+ throw new TypeError("Prepared projected-pixel bank requires unique non-negative safe ticks");
+ }
+ const states = ticks.map((tick) => prepareCssflowerProjectedPixels(tick));
+ const patchesByState = states.map((state) => buildLeafPatches(state.owner));
+ const slots = Array.from({ length: topology.triangleCount }, (_, index) => {
+ const patches = patchesByState.map((patchesForState) => patchesForState[index]);
+ return {
+ index,
+ pixelCount: patches.reduce((sum, patch) => sum + patch.pixelCount, 0),
+ width: Math.max(0, ...patches.map((patch) => patch.width)),
+ height: Math.max(0, ...patches.map((patch) => patch.height)),
+ atlasX: 0,
+ atlasY: 0,
+ };
+ });
+ const stateStride = packLeafPatches(slots);
+ const atlas = new PNG({ width: ATLAS_WIDTH, height: stateStride * states.length, colorType: 6 });
+ for (let stateIndex = 0; stateIndex < states.length; stateIndex += 1) {
+ const state = states[stateIndex];
+ const frame = PNG.sync.read(state.frame.bytes);
+ const patches = patchesByState[stateIndex];
+ const stateY = stateIndex * stateStride;
+ for (let pixelIndex = 0; pixelIndex < state.owner.length; pixelIndex += 1) {
+ const triangleIndex = state.owner[pixelIndex];
+ if (triangleIndex < 0) continue;
+ const slot = slots[triangleIndex];
+ const patch = patches[triangleIndex];
+ const x = pixelIndex % EDGE;
+ const y = Math.floor(pixelIndex / EDGE);
+ const atlasX = slot.atlasX + x - patch.left;
+ const atlasY = stateY + slot.atlasY + y - patch.top;
+ const sourceOffset = pixelIndex * 4;
+ const atlasOffset = (atlasY * atlas.width + atlasX) * 4;
+ atlas.data[atlasOffset] = frame.data[sourceOffset];
+ atlas.data[atlasOffset + 1] = frame.data[sourceOffset + 1];
+ atlas.data[atlasOffset + 2] = frame.data[sourceOffset + 2];
+ atlas.data[atlasOffset + 3] = 255;
+ }
+ }
+ const atlasBytes = PNG.sync.write(atlas, { colorType: 6, inputColorType: 6 });
+ const leafCss = slots.map((slot) => slot.pixelCount === 0
+ ? hiddenBankLeafCss()
+ : bankLeafCss(slot, atlas.width, atlas.height));
+ const packets = states.map((state, stateIndex) => {
+ const inverseRoot = inverseCssRootRotation(cycle.states[state.timelineStateIndex]);
+ const patches = patchesByState[stateIndex];
+ return Object.freeze({
+ tick: state.tick,
+ sf: state.state.sf,
+ rootTransform: state.rootTransform,
+ atlasY: -stateIndex * stateStride,
+ visibleLeafCount: state.topology.visibleLeafCount,
+ transforms: Object.freeze(patches.map((patch, triangleIndex) => {
+ if (slots[triangleIndex].pixelCount === 0) return "none";
+ return patch.pixelCount === 0
+ ? projectedLeafTransform({ left: -10_000, top: -10_000 }, inverseRoot)
+ : projectedLeafTransform(patch, inverseRoot);
+ })),
+ });
+ });
+ return Object.freeze({
+ schema: "cssflower-prepared-projected-pixel-bank@1",
+ ticks: Object.freeze([...ticks]),
+ retainedLeafCount: topology.triangleCount,
+ activeUnionLeafCount: slots.filter((slot) => slot.pixelCount > 0).length,
+ stateStride,
+ leafCss: Object.freeze(leafCss),
+ packets: Object.freeze(packets),
+ atlas: Object.freeze({
+ width: atlas.width,
+ height: atlas.height,
+ bytes: atlasBytes,
+ sha256: sha256(atlasBytes),
+ dataUrl: `data:image/png;base64,${atlasBytes.toString("base64")}`,
+ }),
+ states: Object.freeze(states),
+ authority: Object.freeze({
+ input: "independently-prepared-cssflower-source-state",
+ nativeStateIngestion: false,
+ nativePixelIngestion: false,
+ runtimeProjection: false,
+ runtimeRasterization: false,
+ runtimeGeometryConstruction: false,
+ }),
+ });
+}
+
+function rasterizeCssflowerProjectedPixels(tick) {
+ if (!Number.isSafeInteger(tick) || tick < 0) {
+ throw new RangeError("Prepared projected pixels require a non-negative safe tick");
+ }
+ const timelineStateIndex = tick < cycle.stateCount
+ ? tick
+ : cycle.cycleStartState + ((tick - cycle.cycleStartState) % cycle.cycleLength);
+ const state = cycle.states[timelineStateIndex];
+ const positions = deformCubePoints(topology, state.sf);
+ const normals = computeSmoothPointNormals(topology, positions);
+ const colors = computePreparedVertexLightingUnquantized(topology, positions, normals, state);
+ const rotation = sourceModelRotation(state);
+ const projectedPoints = topology.points.map((point) => {
+ const offset = point.index * 3;
+ const world = rotateSourceVector(rotation, [
+ positions[offset],
+ positions[offset + 1],
+ positions[offset + 2],
+ ]);
+ const eyeDistance = CAMERA.eye[2] - world[2];
+ if (eyeDistance < CAMERA.near || eyeDistance > CAMERA.far) {
+ throw new Error(`cssFlower projected point ${point.index} left the qualified source clip interval`);
+ }
+ return Object.freeze({
+ x: HALF_EDGE + FOCAL_PIXELS * world[0] / eyeDistance,
+ y: HALF_EDGE - FOCAL_PIXELS * world[1] / eyeDistance,
+ inverseEyeDistance: 1 / eyeDistance,
+ windowDepth: windowDepthForEyeDistance(eyeDistance),
+ colorOffset: offset,
+ });
+ });
+
+ const frame = new PNG({ width: EDGE, height: EDGE, colorType: 6 });
+ const owner = new Int16Array(EDGE * EDGE);
+ const depth = new Uint16Array(EDGE * EDGE);
+ owner.fill(-1);
+ depth.fill(DEPTH_MAX);
+ for (let offset = 0; offset < frame.data.length; offset += 4) frame.data[offset + 3] = 255;
+
+ let frontFacingTriangleCount = 0;
+ let candidateFragmentCount = 0;
+ let depthAcceptedFragmentCount = 0;
+ for (const triangle of topology.triangles) {
+ const vertices = triangle.pointIndices.map((pointIndex) => projectedPoints[pointIndex]);
+ const area = orient(vertices[0], vertices[1], vertices[2]);
+ // Source GL uses the default CCW front face. The stored image has its Y axis
+ // flipped relative to OpenGL window coordinates, so front faces are negative.
+ if (area >= -EPSILON) continue;
+ frontFacingTriangleCount += 1;
+ const minX = Math.max(0, Math.ceil(Math.min(...vertices.map((value) => value.x)) - 0.5));
+ const maxX = Math.min(EDGE - 1, Math.floor(Math.max(...vertices.map((value) => value.x)) - 0.5));
+ const minY = Math.max(0, Math.ceil(Math.min(...vertices.map((value) => value.y)) - 0.5));
+ const maxY = Math.min(EDGE - 1, Math.floor(Math.max(...vertices.map((value) => value.y)) - 0.5));
+ for (let y = minY; y <= maxY; y += 1) {
+ for (let x = minX; x <= maxX; x += 1) {
+ const sample = { x: x + 0.5, y: y + 0.5 };
+ const barycentric = [
+ orient(vertices[1], vertices[2], sample) / area,
+ orient(vertices[2], vertices[0], sample) / area,
+ orient(vertices[0], vertices[1], sample) / area,
+ ];
+ if (barycentric.some((value) => value < -EPSILON)) continue;
+ candidateFragmentCount += 1;
+ const inverseEyeDistance = barycentric.reduce((sum, weight, index) => (
+ sum + weight * vertices[index].inverseEyeDistance
+ ), 0);
+ const windowDepth = barycentric.reduce((sum, weight, index) => (
+ sum + weight * vertices[index].windowDepth
+ ), 0);
+ const depth16 = Math.max(0, Math.min(DEPTH_MAX, Math.round(windowDepth * DEPTH_MAX)));
+ const pixelIndex = y * EDGE + x;
+ if (depth16 >= depth[pixelIndex]) continue;
+ depthAcceptedFragmentCount += 1;
+ depth[pixelIndex] = depth16;
+ owner[pixelIndex] = triangle.index;
+ const frameOffset = pixelIndex * 4;
+ for (let channel = 0; channel < 3; channel += 1) {
+ const numerator = barycentric.reduce((sum, weight, index) => (
+ sum + weight * colors[vertices[index].colorOffset + channel] * vertices[index].inverseEyeDistance
+ ), 0);
+ frame.data[frameOffset + channel] = clampByte(numerator / inverseEyeDistance);
+ }
+ }
+ }
+ }
+
+ const leaves = buildLeafPatches(owner);
+ const rootTransform = cycle.rootTransforms[state.rootStateIndex];
+ const ownedPixelCount = leaves.reduce((sum, leaf) => sum + leaf.pixelCount, 0);
+ return Object.freeze({
+ tick,
+ timelineStateIndex,
+ state: Object.freeze({
+ sf: state.sf,
+ sfHex: state.sfHex,
+ sfi: state.sfi,
+ sfiHex: state.sfiHex,
+ geometryStateIndex: state.geometryStateIndex,
+ rootStateIndex: state.rootStateIndex,
+ rotationDegrees: Object.freeze([
+ state.rotationXDegrees,
+ state.rotationYDegrees,
+ state.rotationZDegrees,
+ ]),
+ }),
+ viewport: Object.freeze({ width: EDGE, height: EDGE, deviceScaleFactor: 1 }),
+ projection: Object.freeze({
+ fovDegrees: CAMERA.fovDegrees,
+ eye: Object.freeze([...CAMERA.eye]),
+ near: CAMERA.near,
+ far: CAMERA.far,
+ focalPixels: FOCAL_PIXELS,
+ rasterSample: "integer-pixel-center",
+ cull: "source-default-CCW-front",
+ depth: "source-depth16-less",
+ interpolation: "perspective-correct-smooth-vertex-lighting",
+ }),
+ topology: Object.freeze({
+ pointCount: topology.points.length,
+ triangleCount: topology.triangleCount,
+ retainedLeafCount: topology.triangleCount,
+ frontFacingTriangleCount,
+ visibleLeafCount: leaves.filter((leaf) => leaf.pixelCount > 0).length,
+ ownedPixelCount,
+ candidateFragmentCount,
+ depthAcceptedFragmentCount,
+ }),
+ rootTransform,
+ frameImage: frame,
+ owner,
+ leaves,
+ authority: Object.freeze({
+ input: "independently-prepared-cssflower-source-state",
+ nativeStateIngestion: false,
+ nativePixelIngestion: false,
+ runtimeProjection: false,
+ runtimeRasterization: false,
+ runtimeGeometryConstruction: false,
+ }),
+ });
+}
+
+export function prepareCssflowerProjectedPixels(tick) {
+ const raster = rasterizeCssflowerProjectedPixels(tick);
+ const leaves = raster.leaves;
+ const atlasHeight = packLeafPatches(leaves);
+ const atlas = new PNG({ width: ATLAS_WIDTH, height: atlasHeight, colorType: 6 });
+ for (let pixelIndex = 0; pixelIndex < raster.owner.length; pixelIndex += 1) {
+ const triangleIndex = raster.owner[pixelIndex];
+ if (triangleIndex < 0) continue;
+ const leaf = leaves[triangleIndex];
+ const x = pixelIndex % EDGE;
+ const y = Math.floor(pixelIndex / EDGE);
+ const atlasX = leaf.atlasX + x - leaf.left;
+ const atlasY = leaf.atlasY + y - leaf.top;
+ const sourceOffset = pixelIndex * 4;
+ const atlasOffset = (atlasY * atlas.width + atlasX) * 4;
+ atlas.data[atlasOffset] = raster.frameImage.data[sourceOffset];
+ atlas.data[atlasOffset + 1] = raster.frameImage.data[sourceOffset + 1];
+ atlas.data[atlasOffset + 2] = raster.frameImage.data[sourceOffset + 2];
+ atlas.data[atlasOffset + 3] = 255;
+ }
+ const frameBytes = PNG.sync.write(raster.frameImage, { colorType: 2, inputColorType: 6 });
+ const atlasBytes = PNG.sync.write(atlas, { colorType: 6, inputColorType: 6 });
+ const sourceState = cycle.states[raster.timelineStateIndex];
+ const inverseRoot = inverseCssRootRotation(sourceState);
+ const leafCss = leaves.map((leaf) => leaf.pixelCount === 0
+ ? hiddenLeafCss()
+ : visibleLeafCss(leaf, inverseRoot, atlas.width, atlas.height));
+ return Object.freeze({
+ ...projectedStateDescriptor(raster),
+ schema: "cssflower-prepared-projected-pixels@1",
+ leafCss: Object.freeze(leafCss),
+ atlas: Object.freeze({
+ width: atlas.width,
+ height: atlas.height,
+ bytes: atlasBytes,
+ sha256: sha256(atlasBytes),
+ dataUrl: `data:image/png;base64,${atlasBytes.toString("base64")}`,
+ }),
+ frame: Object.freeze({
+ width: raster.frameImage.width,
+ height: raster.frameImage.height,
+ bytes: frameBytes,
+ sha256: sha256(frameBytes),
+ }),
+ owner: raster.owner,
+ });
+}
+
+export function prepareCssflowerProjectedFrame(tick) {
+ const raster = rasterizeCssflowerProjectedPixels(tick);
+ return Object.freeze({
+ ...projectedStateDescriptor(raster),
+ schema: "cssflower-prepared-projected-frame@1",
+ frameImage: raster.frameImage,
+ owner: raster.owner,
+ leaves: raster.leaves,
+ });
+}
+
+function projectedStateDescriptor(raster) {
+ return Object.freeze({
+ tick: raster.tick,
+ timelineStateIndex: raster.timelineStateIndex,
+ state: raster.state,
+ viewport: raster.viewport,
+ projection: raster.projection,
+ topology: raster.topology,
+ rootTransform: raster.rootTransform,
+ authority: raster.authority,
+ });
+}
+
+function buildLeafPatches(owner) {
+ const leaves = Array.from({ length: topology.triangleCount }, (_, index) => ({
+ index,
+ pixelCount: 0,
+ left: EDGE,
+ top: EDGE,
+ right: -1,
+ bottom: -1,
+ width: 0,
+ height: 0,
+ atlasX: 0,
+ atlasY: 0,
+ }));
+ for (let pixelIndex = 0; pixelIndex < owner.length; pixelIndex += 1) {
+ const triangleIndex = owner[pixelIndex];
+ if (triangleIndex < 0) continue;
+ const x = pixelIndex % EDGE;
+ const y = Math.floor(pixelIndex / EDGE);
+ const leaf = leaves[triangleIndex];
+ leaf.pixelCount += 1;
+ leaf.left = Math.min(leaf.left, x);
+ leaf.top = Math.min(leaf.top, y);
+ leaf.right = Math.max(leaf.right, x);
+ leaf.bottom = Math.max(leaf.bottom, y);
+ }
+ for (const leaf of leaves) {
+ if (leaf.pixelCount === 0) continue;
+ leaf.width = leaf.right - leaf.left + 1;
+ leaf.height = leaf.bottom - leaf.top + 1;
+ }
+ return leaves;
+}
+
+function packLeafPatches(leaves) {
+ let x = 1;
+ let y = 1;
+ let rowHeight = 0;
+ for (const leaf of leaves) {
+ if (leaf.pixelCount === 0) continue;
+ if (leaf.width + 2 > ATLAS_WIDTH) {
+ throw new Error(`Projected leaf ${leaf.index} exceeds the prepared atlas width`);
+ }
+ if (x + leaf.width + 1 > ATLAS_WIDTH) {
+ x = 1;
+ y += rowHeight + 2;
+ rowHeight = 0;
+ }
+ leaf.atlasX = x;
+ leaf.atlasY = y;
+ x += leaf.width + 2;
+ rowHeight = Math.max(rowHeight, leaf.height);
+ }
+ return Math.max(1, y + rowHeight + 1);
+}
+
+function packSpaceTimeLeafStrips(slots, stateCount) {
+ let x = 1;
+ let y = 1;
+ let rowHeight = 0;
+ for (const slot of slots) {
+ if (slot.pixelCount === 0) continue;
+ const stripHeight = slot.height * stateCount;
+ if (slot.width + 2 > SPACE_TIME_ATLAS_WIDTH || stripHeight + 2 > 8_192) {
+ throw new Error(`Projected space-time leaf ${slot.index} exceeds the 8192px page bound`);
+ }
+ if (x + slot.width + 1 > SPACE_TIME_ATLAS_WIDTH) {
+ x = 1;
+ y += rowHeight + 2;
+ rowHeight = 0;
+ }
+ slot.atlasX = x;
+ slot.atlasY = y;
+ x += slot.width + 2;
+ rowHeight = Math.max(rowHeight, stripHeight);
+ }
+ const height = Math.max(1, y + rowHeight + 1);
+ if (height > 8_192) throw new Error(`Projected space-time page height ${height} exceeds 8192px`);
+ return height;
+}
+
+function visibleLeafCss(leaf, inverseRoot, atlasWidth, atlasHeight) {
+ return [
+ "position:absolute",
+ "display:block",
+ "left:0",
+ "top:0",
+ `width:${leaf.width}px`,
+ `height:${leaf.height}px`,
+ "box-sizing:content-box",
+ "margin:0",
+ "padding:0",
+ "border:0",
+ "border-radius:0",
+ "corner-top-left-shape:initial",
+ "corner-top-right-shape:initial",
+ "corner-bottom-right-shape:initial",
+ "corner-bottom-left-shape:initial",
+ "transform-origin:0 0",
+ "transform-style:preserve-3d",
+ "backface-visibility:visible",
+ `transform:${projectedLeafTransform(leaf, inverseRoot)}`,
+ "background-image:var(--cssflower-projected-atlas)",
+ "background-color:transparent",
+ "background-repeat:no-repeat",
+ `background-position:${-leaf.atlasX}px ${-leaf.atlasY}px`,
+ `background-size:${atlasWidth}px ${atlasHeight}px`,
+ "image-rendering:auto",
+ "color:transparent",
+ "line-height:0",
+ "text-decoration:none",
+ ].join(";");
+}
+
+function bankLeafCss(slot, atlasWidth, atlasHeight) {
+ return [
+ "position:absolute",
+ "display:block",
+ "left:0",
+ "top:0",
+ `width:${slot.width}px`,
+ `height:${slot.height}px`,
+ "box-sizing:content-box",
+ "margin:0",
+ "padding:0",
+ "border:0",
+ "border-radius:0",
+ "corner-top-left-shape:initial",
+ "corner-top-right-shape:initial",
+ "corner-bottom-right-shape:initial",
+ "corner-bottom-left-shape:initial",
+ "transform-origin:0 0",
+ "transform-style:preserve-3d",
+ "backface-visibility:visible",
+ "background-image:var(--cssflower-projected-atlas)",
+ "background-color:transparent",
+ "background-repeat:no-repeat",
+ `background-position:${-slot.atlasX}px calc(var(--cssflower-projected-y) - ${slot.atlasY}px)`,
+ `background-size:${atlasWidth}px ${atlasHeight}px`,
+ "image-rendering:pixelated",
+ "color:transparent",
+ "line-height:0",
+ "text-decoration:none",
+ ].join(";");
+}
+
+function spaceTimeBankLeafCss(slot, atlasWidth, atlasHeight) {
+ return [
+ "position:absolute",
+ "display:block",
+ "left:0",
+ "top:0",
+ `width:${slot.width}px`,
+ `height:${slot.height}px`,
+ "box-sizing:content-box",
+ "margin:0",
+ "padding:0",
+ "border:0",
+ "border-radius:0",
+ "corner-top-left-shape:initial",
+ "corner-top-right-shape:initial",
+ "corner-bottom-right-shape:initial",
+ "corner-bottom-left-shape:initial",
+ "transform-origin:0 0",
+ "transform-style:preserve-3d",
+ "backface-visibility:visible",
+ `transform:${projectedLeafScreenTranslation(slot)}`,
+ "background-image:var(--cssflower-projected-atlas)",
+ "background-color:transparent",
+ "background-repeat:no-repeat",
+ `background-position:${-slot.atlasX}px calc(${-slot.atlasY}px - var(--cssflower-projected-frame) * ${slot.height}px)`,
+ `background-size:${atlasWidth}px ${atlasHeight}px`,
+ "image-rendering:pixelated",
+ "color:transparent",
+ "line-height:0",
+ "text-decoration:none",
+ ].join(";");
+}
+
+function hiddenBankLeafCss() {
+ return "position:absolute;display:none;left:0;top:0;width:0;height:0;transform:none;background:none;border:0";
+}
+
+function projectedLeafTransform(leaf, inverseRoot) {
+ const targetTranslation = [leaf.left - HALF_EDGE, leaf.top - HALF_EDGE, 0];
+ const localTranslation = multiplyRotationVector(inverseRoot, targetTranslation);
+ const matrix = [
+ inverseRoot[0][0], inverseRoot[1][0], inverseRoot[2][0], 0,
+ inverseRoot[0][1], inverseRoot[1][1], inverseRoot[2][1], 0,
+ inverseRoot[0][2], inverseRoot[1][2], inverseRoot[2][2], 0,
+ localTranslation[0], localTranslation[1], localTranslation[2], 1,
+ ].map(formatCssNumber).join(",");
+ return `matrix3d(${matrix})`;
+}
+
+function projectedLeafFactoredTransform(leaf, state) {
+ const x = normalizeDegrees(state.rootStateIndex * CSSFLOWER_SOURCE_PROFILE.rotation.xDegreesPerUpdate);
+ const y = normalizeDegrees(state.rootStateIndex * CSSFLOWER_SOURCE_PROFILE.rotation.yDegreesPerUpdate);
+ const dx = leaf.left - HALF_EDGE;
+ const dy = leaf.top - HALF_EDGE;
+ return `rotateY(${-y}deg) rotateX(${x}deg) translate3d(${dx}px,${dy}px,0px)`;
+}
+
+function preparedInverseRootTransform(state) {
+ const x = normalizeDegrees(state.rootStateIndex * CSSFLOWER_SOURCE_PROFILE.rotation.xDegreesPerUpdate);
+ const y = normalizeDegrees(state.rootStateIndex * CSSFLOWER_SOURCE_PROFILE.rotation.yDegreesPerUpdate);
+ return `rotateY(${-y}deg) rotateX(${x}deg)`;
+}
+
+function projectedLeafScreenTranslation(leaf) {
+ return `translate3d(${leaf.left - HALF_EDGE}px,${leaf.top - HALF_EDGE}px,0px)`;
+}
+
+function hiddenLeafCss() {
+ return "position:absolute;display:none;left:0;top:0;width:0;height:0;transform:none;background:none;border:0";
+}
+
+function inverseCssRootRotation(state) {
+ const x = -normalizeDegrees(state.rootStateIndex * CSSFLOWER_SOURCE_PROFILE.rotation.xDegreesPerUpdate) * Math.PI / 180;
+ const y = normalizeDegrees(state.rootStateIndex * CSSFLOWER_SOURCE_PROFILE.rotation.yDegreesPerUpdate) * Math.PI / 180;
+ const rx = [
+ [1, 0, 0],
+ [0, Math.cos(x), -Math.sin(x)],
+ [0, Math.sin(x), Math.cos(x)],
+ ];
+ const ry = [
+ [Math.cos(y), 0, Math.sin(y)],
+ [0, 1, 0],
+ [-Math.sin(y), 0, Math.cos(y)],
+ ];
+ const root = multiplyRotation(rx, ry);
+ return transposeRotation(root);
+}
+
+function multiplyRotation(left, right) {
+ return Array.from({ length: 3 }, (_, row) => Array.from({ length: 3 }, (_, column) => (
+ left[row][0] * right[0][column] +
+ left[row][1] * right[1][column] +
+ left[row][2] * right[2][column]
+ )));
+}
+
+function transposeRotation(value) {
+ return Array.from({ length: 3 }, (_, row) => Array.from({ length: 3 }, (_, column) => value[column][row]));
+}
+
+function multiplyRotationVector(rotation, value) {
+ return rotation.map((row) => row[0] * value[0] + row[1] * value[1] + row[2] * value[2]);
+}
+
+function sourceModelRotation(state) {
+ const radians = Math.PI / 180;
+ const x = state.rootStateIndex * CSSFLOWER_SOURCE_PROFILE.rotation.xDegreesPerUpdate * radians;
+ const y = state.rootStateIndex * CSSFLOWER_SOURCE_PROFILE.rotation.yDegreesPerUpdate * radians;
+ const z = state.rotationZDegrees * radians;
+ return Object.freeze({
+ cx: Math.cos(x), sx: Math.sin(x),
+ cy: Math.cos(y), sy: Math.sin(y),
+ cz: Math.cos(z), sz: Math.sin(z),
+ });
+}
+
+function rotateSourceVector(rotation, value) {
+ const xAfterZ = rotation.cz * value[0] - rotation.sz * value[1];
+ const yAfterZ = rotation.sz * value[0] + rotation.cz * value[1];
+ const xAfterY = rotation.cy * xAfterZ + rotation.sy * value[2];
+ const zAfterY = -rotation.sy * xAfterZ + rotation.cy * value[2];
+ return [
+ xAfterY,
+ rotation.cx * yAfterZ - rotation.sx * zAfterY,
+ rotation.sx * yAfterZ + rotation.cx * zAfterY,
+ ];
+}
+
+function windowDepthForEyeDistance(eyeDistance) {
+ const ndc = (CAMERA.far + CAMERA.near) / (CAMERA.far - CAMERA.near) -
+ (2 * CAMERA.far * CAMERA.near) / ((CAMERA.far - CAMERA.near) * eyeDistance);
+ return (ndc + 1) / 2;
+}
+
+function orient(a, b, c) {
+ return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
+}
+
+function clampByte(value) {
+ return Math.max(0, Math.min(255, Math.round(value)));
+}
+
+function normalizeDegrees(value) {
+ const normalized = value % 360;
+ return normalized < 0 ? normalized + 360 : normalized;
+}
+
+function formatCssNumber(value) {
+ const rounded = Math.round(value * 1_000_000_000) / 1_000_000_000;
+ return String(Object.is(rounded, -0) ? 0 : rounded);
+}
+
+function sha256(bytes) {
+ return createHash("sha256").update(bytes).digest("hex");
+}
diff --git a/src/adapters/flowerbox/src/prepare/cssflower/provenance.mjs b/src/adapters/flowerbox/src/prepare/cssflower/provenance.mjs
new file mode 100644
index 0000000..8bd74d2
--- /dev/null
+++ b/src/adapters/flowerbox/src/prepare/cssflower/provenance.mjs
@@ -0,0 +1,29 @@
+export function sourceProvenanceFor(dataSource) {
+ return {
+ project: "cssFlower — Microsoft Flower Box",
+ dataKind: dataSource.kind,
+ behaviorAuthority: "src/adapters/flowerbox/README.md",
+ sourceRevision: "DigitalMars/dmc@9478d25a677f70dbe4fc0ed317cc5a5e5050ef8b",
+ sourceFiles: [
+ { id: "GEOM.C", sha256: "7689779fd3e37a06245f38a0190b863a8ee4c4ed0364d8799206c37f549a69ff" },
+ { id: "GEOM.H", sha256: "6d8d179530db1bc26ed2f30430f3280cafe8cb598aa671aa57a4121bed3edadf" },
+ { id: "SSFLWBOX.C", sha256: "5322ab133840f5fbd7a4de2d1d701f7c5f333295442c8d728409869bec95d168" },
+ { id: "SSFLWBOX.H", sha256: "dfcb99b876480ee5e29b0f72ac9650fb8e2790e8efa023154f974c12cd2ad822" },
+ ],
+ localAuthorityLabel: dataSource.publicLabel,
+ nativeAuthorityStatus: dataSource.nativeAuthorityStatus,
+ nativeQualification: dataSource.nativeQualification ?? null,
+ legal: dataSource.legalLabel ?? "user-supplied-data-not-redistributed",
+ acquisition: dataSource.nativeQualification
+ ? "explicit-user-authorized-local-git-fetch-ignored-not-redistributed"
+ : "none",
+ redistributableUpstreamBytes: false,
+ };
+}
+
+export function assertNoBrowserPathLeaks(value) {
+ const text = JSON.stringify(value);
+ if (/\/Users\/|\\\\Users\\\\|file:\/\//.test(text)) {
+ throw new Error("Generated browser JSON contains a local absolute path.");
+ }
+}
diff --git a/src/adapters/flowerbox/src/prepare/cssflower/quadMergeAudit.mjs b/src/adapters/flowerbox/src/prepare/cssflower/quadMergeAudit.mjs
new file mode 100644
index 0000000..ddc38eb
--- /dev/null
+++ b/src/adapters/flowerbox/src/prepare/cssflower/quadMergeAudit.mjs
@@ -0,0 +1,109 @@
+import { deformCubePoints } from "./cubeTopology.mjs";
+
+export function auditPreparedQuadMergeEligibility(topology, cycle) {
+ if (topology.triangleCount % 2 !== 0) throw new Error("cssFlower triangle bank cannot be paired into source cells");
+ const sourceCellCount = topology.triangleCount / 2;
+ const nonCoplanarCells = new Set();
+ let testedStateCellPairs = 0;
+ let exactCoplanarStateCellPairs = 0;
+ let exactNonCoplanarStateCellPairs = 0;
+ let minimumPositivePlaneDistance = Number.POSITIVE_INFINITY;
+ let maximumPlaneDistance = 0;
+ let firstNonCoplanar = null;
+ let worstNonCoplanar = null;
+
+ for (const geometryState of cycle.geometryStates) {
+ const positions = deformCubePoints(topology, geometryState.sf);
+ for (let cellIndex = 0; cellIndex < sourceCellCount; cellIndex += 1) {
+ const first = topology.triangles[cellIndex * 2];
+ const second = topology.triangles[cellIndex * 2 + 1];
+ if (first.side !== second.side || first.strip !== second.strip ||
+ first.column !== second.column || first.material.id !== second.material.id) {
+ throw new Error(`cssFlower source cell pairing drifted at ${cellIndex}`);
+ }
+ const pointIndices = [...new Set([...first.pointIndices, ...second.pointIndices])];
+ if (pointIndices.length !== 4) throw new Error(`cssFlower source cell ${cellIndex} does not have four corners`);
+ const points = pointIndices.map((pointIndex) => pointAt(positions, pointIndex));
+ const ab = subtract(points[1], points[0]);
+ const ac = subtract(points[2], points[0]);
+ const ad = subtract(points[3], points[0]);
+ const normal = cross(ab, ac);
+ const signedVolume6 = dot(normal, ad);
+ const normalLength = Math.hypot(normal[0], normal[1], normal[2]);
+ const planeDistance = normalLength > 0 ? Math.abs(signedVolume6) / normalLength : Number.POSITIVE_INFINITY;
+ testedStateCellPairs += 1;
+ if (signedVolume6 === 0) {
+ exactCoplanarStateCellPairs += 1;
+ continue;
+ }
+ exactNonCoplanarStateCellPairs += 1;
+ nonCoplanarCells.add(cellIndex);
+ minimumPositivePlaneDistance = Math.min(minimumPositivePlaneDistance, planeDistance);
+ const coordinate = {
+ geometryStateIndex: geometryState.index,
+ firstTick: geometryState.firstTick,
+ sf: geometryState.sf,
+ sfHex: geometryState.sfHex,
+ cellIndex,
+ side: first.side,
+ strip: first.strip,
+ column: first.column,
+ triangleIds: [first.id, second.id],
+ pointIndices,
+ signedVolume6,
+ planeDistance,
+ };
+ if (!firstNonCoplanar) firstNonCoplanar = Object.freeze(coordinate);
+ if (planeDistance > maximumPlaneDistance) {
+ maximumPlaneDistance = planeDistance;
+ worstNonCoplanar = Object.freeze(coordinate);
+ }
+ }
+ }
+
+ const acrossAllStatesEligibleCellCount = sourceCellCount - nonCoplanarCells.size;
+ if (acrossAllStatesEligibleCellCount !== 0 || exactNonCoplanarStateCellPairs === 0) {
+ throw new Error("cssFlower quad merge audit unexpectedly found an across-cycle eligible source cell");
+ }
+ return Object.freeze({
+ schema: "cssflower-quad-merge-audit@1",
+ primitiveConsidered: Object.freeze({ tag: "b", kind: "polycss-solid-quad" }),
+ status: "rejected-noncoplanar-across-prepared-cycle",
+ comparison: "exact-signed-volume-on-float32-prepared-positions",
+ sourceCellCount,
+ geometryStateCount: cycle.geometryStateCount,
+ testedStateCellPairs,
+ exactCoplanarStateCellPairs,
+ exactNonCoplanarStateCellPairs,
+ cellsNonCoplanarInAtLeastOneState: nonCoplanarCells.size,
+ acrossAllStatesEligibleCellCount,
+ minimumPositivePlaneDistance,
+ maximumPlaneDistance,
+ firstNonCoplanar,
+ worstNonCoplanar,
+ geometryEquivalence: false,
+ lightingEquivalence: "not-evaluated-after-geometry-prerequisite-failed",
+ decision: "retain-two-stable-triangle-leaves-per-source-cell",
+ });
+}
+
+function pointAt(positions, index) {
+ const offset = index * 3;
+ return [positions[offset], positions[offset + 1], positions[offset + 2]];
+}
+
+function subtract(left, right) {
+ return [left[0] - right[0], left[1] - right[1], left[2] - right[2]];
+}
+
+function cross(left, right) {
+ return [
+ left[1] * right[2] - left[2] * right[1],
+ left[2] * right[0] - left[0] * right[2],
+ left[0] * right[1] - left[1] * right[0],
+ ];
+}
+
+function dot(left, right) {
+ return left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
+}
diff --git a/src/adapters/flowerbox/src/prepare/cssflower/sceneBuilder.mjs b/src/adapters/flowerbox/src/prepare/cssflower/sceneBuilder.mjs
new file mode 100644
index 0000000..6e6e329
--- /dev/null
+++ b/src/adapters/flowerbox/src/prepare/cssflower/sceneBuilder.mjs
@@ -0,0 +1,363 @@
+import { createHash } from "node:crypto";
+import { sourceProvenanceFor } from "./provenance.mjs";
+import { cssflowerSlicePlan } from "./slicePlan.mjs";
+import { CSSFLOWER_CAMERA, CSSFLOWER_SIDE_MATERIALS, CSSFLOWER_SOURCE_PROFILE } from "./sourceProfile.mjs";
+import { buildCssflowerPreparedRoundedOcclusionSchedule } from "./projectedPixels.mjs";
+import {
+ CSSFLOWER_BOUNDARY_SEAM_BLEED,
+ CSSFLOWER_FRONT_FACE_DILATION_TICKS,
+ CSSFLOWER_FRONT_FACE_SCHEDULE_ENCODING,
+ CSSFLOWER_FRONT_FACE_SCHEDULE_SCHEMA,
+ CSSFLOWER_LIGHTING_ATLAS_ENCODING,
+ CSSFLOWER_LIGHTING_ATLAS_MIME_TYPE,
+ CSSFLOWER_LIGHTING_ATLAS_QUALITY,
+ CSSFLOWER_LIGHTING_GRID_COLUMNS,
+ CSSFLOWER_LIGHTING_GRID_HEIGHT,
+ CSSFLOWER_LIGHTING_GRID_ROWS,
+ CSSFLOWER_LIGHTING_GRID_WIDTH,
+ CSSFLOWER_SEAM_BLEED,
+ CSSFLOWER_TRANSFORM_BLOCK_GEOMETRY_STATES,
+ CSSFLOWER_VISIBILITY_POLICY,
+} from "../../cssflower/renderContract.mjs";
+
+export async function buildCssflowerFirstSliceScene({
+ compiled,
+ dataSource,
+ preparedAssets,
+ sceneId = "default-cube",
+} = {}) {
+ if (sceneId !== "default-cube") {
+ throw new RangeError(`Unknown prepared cssFlower scene ${sceneId}`);
+ }
+ if (compiled?.topology?.triangleCount !== 1_200 ||
+ preparedAssets?.transforms?.triangleCount !== compiled.topology.triangleCount ||
+ preparedAssets.transforms.geometryStateCount !== compiled.cycle.geometryStateCount ||
+ preparedAssets?.lighting?.pageCount !== compiled.lighting.pageCount) {
+ throw new Error("Complete source-bound PolyCSS Morph preparation is required");
+ }
+ const playbackStates = preparePlaybackAssetSchedule(compiled.cycle);
+ const frontFacingSchedule = prepareFrontFacingTransformSchedule(
+ buildCssflowerPreparedRoundedOcclusionSchedule({
+ adjacency: CSSFLOWER_VISIBILITY_POLICY.adjacency,
+ adjacencyRings: CSSFLOWER_VISIBILITY_POLICY.adjacencyRings,
+ minimumOwnedPixels: CSSFLOWER_VISIBILITY_POLICY.minimumOwnedPixels,
+ sampleGrid: CSSFLOWER_VISIBILITY_POLICY.sampleGrid,
+ temporalDilationTicks: CSSFLOWER_VISIBILITY_POLICY.temporalDilationTicks,
+ }),
+ compiled.topology.triangleCount,
+ );
+ const scene = Object.freeze({
+ schema: "cssflower-prepared-scene@1",
+ id: "default-cube",
+ label: "Microsoft Flower Box — default cube",
+ mode: cssflowerSlicePlan.mode,
+ artifactMode: cssflowerSlicePlan.artifactMode,
+ source: sourceProvenanceFor(dataSource),
+ sourceProfile: CSSFLOWER_SOURCE_PROFILE,
+ renderer: Object.freeze({
+ package: "@layoutit/polycss",
+ version: "0.2.11",
+ morphPackage: "@layoutit/polycss-morph",
+ morphVersion: "0.2.11",
+ morphTarget: "createPolyMorphPreparedDomTarget",
+ representation: "stable-source-triangle-leaves-with-prepared-owned-pixel-occlusion-matrix3d-blocks-and-exact-sparse-leaf-lighting-addresses",
+ textureBackend: "atlas",
+ textureLeafSizing: "raster",
+ stableDom: true,
+ seamBleed: CSSFLOWER_SEAM_BLEED,
+ boundarySeamBleed: CSSFLOWER_BOUNDARY_SEAM_BLEED,
+ seamBleedPolicy: compiled.siblingSeamPlan.policy,
+ seamBleedSharedEdgeCount: compiled.siblingSeamPlan.sharedEdgeCount,
+ seamBleedBoundaryEdgeCount: compiled.siblingSeamPlan.boundaryEdgeCount,
+ seamBleedBoundaryVertexCount: compiled.siblingSeamPlan.boundaryVertexCount,
+ seamBleedBoundaryAdjacentTriangleCount: compiled.siblingSeamPlan.boundaryAdjacentTriangleCount,
+ merge: false,
+ quadPrimitiveAudit: compiled.quadMergeAudit,
+ meshResolution: "lossless",
+ runtimeGeometryConstruction: false,
+ runtimeRadialProjection: false,
+ runtimeNormalCalculation: false,
+ runtimeLightingCalculation: false,
+ runtimeDomGrowth: false,
+ }),
+ camera: CSSFLOWER_CAMERA,
+ controls: "none",
+ background: "#000000",
+ textureLighting: "baked",
+ textureQuality: 1,
+ materials: CSSFLOWER_SIDE_MATERIALS.map(({ id, color }) => ({ id, color })),
+ lighting: publicLightingContract(compiled.lighting, preparedAssets.lighting),
+ playback: Object.freeze({
+ schema: "cssflower-prepared-playback@1",
+ target: "createPolyMorphPreparedDomTarget",
+ scope: "rounded-product-cycle-spike-phase-omitted",
+ sourceTicksPerSecond: CSSFLOWER_SOURCE_PROFILE.presentationTicksPerSecond,
+ transformAsset: preparedAssets.transforms,
+ frontFacingSchedule,
+ stateEvidenceUrl: "/cssflower/assets/flower-box-state-evidence.json",
+ cycle: Object.freeze({
+ schema: compiled.cycle.schema,
+ initialState: compiled.cycle.initialState,
+ stateCount: compiled.cycle.stateCount,
+ cycleStartState: compiled.cycle.cycleStartState,
+ cycleLength: compiled.cycle.cycleLength,
+ bloomTraceStateCount: compiled.cycle.bloomTraceStateCount,
+ bloomCycleStartState: compiled.cycle.bloomCycleStartState,
+ bloomCycleLength: compiled.cycle.bloomCycleLength,
+ bloomPeakGeometryStateIndex: compiled.cycle.bloomPeakGeometryStateIndex,
+ bloomPeakSf: compiled.cycle.bloomPeakSf,
+ bloomPeakSfHex: compiled.cycle.bloomPeakSfHex,
+ bloomPeakSfNominal: compiled.cycle.bloomPeakSfNominal,
+ omittedSourceSfAtOrAbove: compiled.cycle.omittedSourceSfAtOrAbove,
+ geometryStateCount: compiled.cycle.geometryStateCount,
+ rootStateCount: compiled.cycle.rootStateCount,
+ rootTransforms: compiled.cycle.rootTransforms,
+ states: playbackStates,
+ }),
+ }),
+ meshes: Object.freeze([Object.freeze({
+ id: "flower-box-default-cube",
+ kind: "flower-box-cube",
+ sourceId: "FLWBOX:cube:subdivision-10",
+ stableDom: true,
+ excludeFromAutoCenter: true,
+ transform: Object.freeze({}),
+ polygons: Object.freeze(compiled.initialPolygons.map((polygon, index) => Object.freeze({
+ ...polygon,
+ data: Object.freeze({
+ ...polygon.data,
+ "cssflower-seam-bleed": compiled.lighting.faces[index].seamBleed,
+ "cssflower-seam-edge-mask": compiled.siblingSeamPlan.edgeMasks[index],
+ }),
+ }))),
+ })]),
+ metrics: Object.freeze({
+ meshCount: 1,
+ sourceSideCount: compiled.topology.sideCount,
+ sourceSideLocalPointCount: compiled.topology.sideLocalPointCount,
+ sourceTriangleCount: compiled.topology.triangleCount,
+ preparedPolygonCount: compiled.topology.triangleCount,
+ preparedLeafCount: compiled.topology.triangleCount,
+ preparedRootCount: 1,
+ preparedGeometryStateCount: compiled.cycle.geometryStateCount,
+ preparedTimelineStateCount: compiled.cycle.stateCount,
+ preparedCombinedCycleLength: compiled.cycle.cycleLength,
+ preparedBloomCycleLength: compiled.cycle.bloomCycleLength,
+ preparedRootStateCount: compiled.cycle.rootStateCount,
+ retainedSourceOracleTimelineStateCount: compiled.sourceCycle.stateCount,
+ retainedSourceOracleGeometryStateCount: compiled.sourceCycle.geometryStateCount,
+ retainedSourceOracleCombinedCycleLength: compiled.sourceCycle.cycleLength,
+ preparedTransformBlockCount: preparedAssets.transforms.blockCount,
+ preparedTransformEncodedBytes: preparedAssets.transforms.byteLength,
+ preparedTransformDecodedBytes: preparedAssets.transforms.decodedByteLength,
+ preparedFrontFacingDilationTicks: frontFacingSchedule.dilationTicks,
+ preparedVisibilitySelectionDomain: frontFacingSchedule.selectionDomain,
+ preparedVisibilityMinimumOwnedPixels: frontFacingSchedule.minimumOwnedPixels,
+ preparedVisibilitySampleGrid: frontFacingSchedule.sampleGrid,
+ preparedVisibilityAdjacencyRings: frontFacingSchedule.adjacencyRings,
+ preparedFrontFacingSelectedCount: frontFacingSchedule.selectedFaceCount,
+ preparedFrontFacingSuppressedCount: frontFacingSchedule.suppressedFaceCount,
+ preparedFrontFacingMeanSelectedPerState: frontFacingSchedule.meanSelectedFacesPerState,
+ preparedFrontFacingMinimumSelectedPerState: frontFacingSchedule.minimumSelectedFacesPerState,
+ preparedFrontFacingMaximumSelectedPerState: frontFacingSchedule.maximumSelectedFacesPerState,
+ preparedFrontFacingVisibilityChangeCount: frontFacingSchedule.visibilityChangeCount,
+ preparedLightingPageCount: preparedAssets.lighting.pageCount,
+ preparedLightingAssetCount: preparedAssets.lighting.assetCount,
+ preparedLightingEncodedBytes: preparedAssets.lighting.contentAddressedBytes,
+ preparedLightingFieldCount: compiled.cycle.stateCount * compiled.topology.triangleCount,
+ preparedLightingAddressUpdateCount: compiled.lighting.addressSchedule.updateCount,
+ preparedLightingMeanAddressUpdatesPerState: compiled.lighting.addressSchedule.meanUpdatesPerState,
+ preparedLightingP95AddressUpdatesPerState: compiled.lighting.addressSchedule.p95UpdatesPerState,
+ preparedLightingMaximumAddressUpdatesPerState: compiled.lighting.addressSchedule.maximumUpdatesPerState,
+ preparedSeamSharedEdgeCount: compiled.siblingSeamPlan.sharedEdgeCount,
+ preparedSeamSharedEdgeIncidenceCount: compiled.siblingSeamPlan.sharedEdgeIncidenceCount,
+ preparedSeamBoundaryEdgeCount: compiled.siblingSeamPlan.boundaryEdgeCount,
+ preparedSeamBoundaryEdgeIncidenceCount: compiled.siblingSeamPlan.boundaryEdgeIncidenceCount,
+ preparedSeamBoundaryVertexCount: compiled.siblingSeamPlan.boundaryVertexCount,
+ preparedSeamBoundaryAdjacentTriangleCount: compiled.siblingSeamPlan.boundaryAdjacentTriangleCount,
+ mergedCellCount: 0,
+ mergeCandidateCount: compiled.quadMergeAudit.sourceCellCount,
+ mergeEligibleCellCount: compiled.quadMergeAudit.acrossAllStatesEligibleCellCount,
+ runtimePolygonConstructionCount: 0,
+ runtimeRadialProjectionCount: 0,
+ runtimeNormalCalculationCount: 0,
+ runtimeLightingCalculationCount: 0,
+ runtimeDomGrowth: false,
+ }),
+ oracle: Object.freeze({
+ stateEvidenceSchema: compiled.evidence.schema,
+ stateEvidenceUrl: "/cssflower/assets/flower-box-state-evidence.json",
+ implementationEngineIndependence: compiled.evidence.engineIndependence,
+ nativeAuthorityStatus: dataSource?.nativeAuthorityStatus ?? "missing",
+ nativeStateComparison: dataSource?.nativeQualification?.status === "pass"
+ ? "exact-pass-9331-ticks"
+ : "pending-owned-byte-identified-authority",
+ visualComparison: dataSource?.nativeQualification?.status === "pass"
+ ? "rounded-product-common-prefix-only-full-source-visual-report-retained-as-historical-evidence"
+ : "pending-state-correctness-and-native-authority",
+ }),
+ warnings: Object.freeze([
+ ...(dataSource?.nativeQualification?.status === "pass" ? [
+ "The independently generated topology, binary32 bloom state, rotations, positions, normals, materials, camera, and light configuration pass the retained exact 9,331-tick identity-bound native comparison.",
+ "Native/browser visual parity is not claimed: calibrated pixelmatch still reports measured rasterization and lighting divergence.",
+ ] : [
+ "The radial coefficient formula, exact material channels, normal averaging order, and fixed-function lighting realization are independently authored candidates pending identity-bound native differential validation.",
+ "Native state and visual parity are not claimed because no owned source or binary authority is present under ignored local storage.",
+ ]),
+ "The source update cadence is not yet authority-bound; 30 ticks per second is a deterministic browser presentation cadence, not a native timing claim.",
+ "The product deliberately omits source states at sf >= 2.5 and uses a prepared 90-state rounded bloom inside one 360-state rotation. The independent 9,331-tick source/native oracle remains complete; product/native state parity is claimed only through the shared tick-0-to-45 prefix.",
+ "The product uses the owner-accepted prepare-time depth16 owned-pixel visibility schedule with an eight-pixel minimum and one-tick cyclic dilation. Its sparse browser edge-coverage drift is accepted bounded product behavior, not native or prior-browser pixel parity.",
+ `Prepared leaf-resolution lighting is encoded as ${CSSFLOWER_LIGHTING_ATLAS_ENCODING}; exact state and topology are retained, but decoded browser pixels are bounded-lossy and require explicit visual acceptance.`,
+ "Runtime lighting selection uses an exact prepared RGB8 per-triangle change schedule. Reusing a prior atlas address is state-exact in that selection domain; the lossy AVIF grid can still produce small location-dependent raster differences when an equivalent source field is addressed from a different prepared state.",
+ "PolyCSS quads were evaluated for all 600 source cells across all 414 prepared states; every cell is noncoplanar in at least one state, so exact geometry equivalence fails and the 1,200 stable triangle leaves are retained.",
+ ]),
+ });
+ return Object.freeze({ scene, compiled });
+}
+
+function preparePlaybackAssetSchedule(cycle) {
+ const states = cycle?.states;
+ if (!Array.isArray(states) || states.length !== cycle.stateCount ||
+ !Number.isSafeInteger(cycle.cycleStartState) || cycle.cycleStartState < 0 ||
+ cycle.cycleStartState >= states.length) {
+ throw new Error("Prepared cssFlower playback schedule is invalid");
+ }
+ return Object.freeze(states.map((state, timelineStateIndex) => {
+ const transformBlockIndex = Math.floor(
+ state.geometryStateIndex / CSSFLOWER_TRANSFORM_BLOCK_GEOMETRY_STATES,
+ );
+ const nextTransformState = nextDistinctPreparedState(
+ states,
+ timelineStateIndex,
+ cycle.cycleStartState,
+ (candidate) => Math.floor(
+ candidate.geometryStateIndex / CSSFLOWER_TRANSFORM_BLOCK_GEOMETRY_STATES,
+ ),
+ );
+ const nextLightingState = nextDistinctPreparedState(
+ states,
+ timelineStateIndex,
+ cycle.cycleStartState,
+ (candidate) => candidate.lightingPageIndex,
+ );
+ return Object.freeze({
+ ...state,
+ transformBlockIndex,
+ nextTransformBlockGeometryStateIndex: nextTransformState.geometryStateIndex,
+ nextLightingPageIndex: nextLightingState.lightingPageIndex,
+ });
+ }));
+}
+
+function prepareFrontFacingTransformSchedule(schedule, faceCount) {
+ if (!Array.isArray(schedule) || schedule.length !== 360 || faceCount !== 1_200 ||
+ schedule.some((indices) => !Array.isArray(indices) || indices.length < 1 ||
+ indices.some((faceIndex) => !Number.isSafeInteger(faceIndex) || faceIndex < 0 || faceIndex >= faceCount) ||
+ new Set(indices).size !== indices.length)) {
+ throw new Error("Prepared cssFlower front-face transform schedule is invalid");
+ }
+ const bytesPerState = Math.ceil(faceCount / 8);
+ const bytes = Buffer.alloc(schedule.length * bytesPerState);
+ const selectedByState = schedule.map((indices, stateIndex) => {
+ for (const faceIndex of indices) {
+ bytes[stateIndex * bytesPerState + (faceIndex >> 3)] |= 1 << (faceIndex & 7);
+ }
+ return indices.length;
+ });
+ const selectedFaceCount = selectedByState.reduce((sum, count) => sum + count, 0);
+ let visibilityChangeCount = 0;
+ for (let stateIndex = 0; stateIndex < schedule.length; stateIndex += 1) {
+ const previousStateIndex = (stateIndex + schedule.length - 1) % schedule.length;
+ for (let byteIndex = 0; byteIndex < bytesPerState; byteIndex += 1) {
+ visibilityChangeCount += popcount8(
+ bytes[stateIndex * bytesPerState + byteIndex] ^
+ bytes[previousStateIndex * bytesPerState + byteIndex],
+ );
+ }
+ }
+ const fieldCount = schedule.length * faceCount;
+ return Object.freeze({
+ schema: CSSFLOWER_FRONT_FACE_SCHEDULE_SCHEMA,
+ stateCount: schedule.length,
+ faceCount,
+ dilationTicks: CSSFLOWER_FRONT_FACE_DILATION_TICKS,
+ selectionDomain: CSSFLOWER_VISIBILITY_POLICY.selectionDomain,
+ depthComparison: CSSFLOWER_VISIBILITY_POLICY.depthComparison,
+ minimumOwnedPixels: CSSFLOWER_VISIBILITY_POLICY.minimumOwnedPixels,
+ sampleGrid: CSSFLOWER_VISIBILITY_POLICY.sampleGrid,
+ adjacency: CSSFLOWER_VISIBILITY_POLICY.adjacency,
+ adjacencyRings: CSSFLOWER_VISIBILITY_POLICY.adjacencyRings,
+ dilationPolicy: CSSFLOWER_VISIBILITY_POLICY.dilationPolicy,
+ encoding: CSSFLOWER_FRONT_FACE_SCHEDULE_ENCODING,
+ bytesPerState,
+ byteLength: bytes.length,
+ selectedFaceCount,
+ suppressedFaceCount: fieldCount - selectedFaceCount,
+ meanSelectedFacesPerState: selectedFaceCount / schedule.length,
+ minimumSelectedFacesPerState: Math.min(...selectedByState),
+ maximumSelectedFacesPerState: Math.max(...selectedByState),
+ initialVisibilitySelectionCount: faceCount,
+ visibilityChangeCount,
+ dataSha256: createHash("sha256").update(bytes).digest("hex"),
+ dataBase64: bytes.toString("base64"),
+ runtimeSelection: "prepared-bit-test-only-no-geometry-projection-normal-or-lighting-calculation",
+ });
+}
+
+function popcount8(value) {
+ let count = 0;
+ for (let bits = value & 255; bits !== 0; bits &= bits - 1) count += 1;
+ return count;
+}
+
+function nextDistinctPreparedState(states, stateIndex, cycleStartState, keyFor) {
+ const currentKey = keyFor(states[stateIndex]);
+ let cursor = stateIndex + 1 < states.length ? stateIndex + 1 : cycleStartState;
+ for (let checked = 0; checked < states.length; checked += 1) {
+ const candidate = states[cursor];
+ if (keyFor(candidate) !== currentKey) return candidate;
+ cursor = cursor + 1 < states.length ? cursor + 1 : cycleStartState;
+ }
+ throw new Error(`Prepared cssFlower schedule never leaves asset key ${currentKey}`);
+}
+
+export function createCssflowerSceneContract(value = {}) {
+ return value;
+}
+
+function publicLightingContract(lighting, assets) {
+ if (assets?.encoding !== CSSFLOWER_LIGHTING_ATLAS_ENCODING ||
+ assets.mimeType !== CSSFLOWER_LIGHTING_ATLAS_MIME_TYPE ||
+ assets.quality !== CSSFLOWER_LIGHTING_ATLAS_QUALITY ||
+ assets.assetCount !== 1 ||
+ assets.grid?.columns !== CSSFLOWER_LIGHTING_GRID_COLUMNS ||
+ assets.grid?.rows !== CSSFLOWER_LIGHTING_GRID_ROWS ||
+ assets.grid?.width !== CSSFLOWER_LIGHTING_GRID_WIDTH ||
+ assets.grid?.height !== CSSFLOWER_LIGHTING_GRID_HEIGHT ||
+ assets.pages?.length !== lighting.pages.length) {
+ throw new Error("Prepared cssFlower public lighting asset contract is incomplete");
+ }
+ return Object.freeze({
+ ...lighting,
+ distribution: assets.distribution,
+ assetUrl: assets.grid.assetUrl,
+ assetSha256: assets.grid.sha256,
+ grid: assets.grid,
+ pages: assets.pages,
+ totalEncodedBytes: assets.encodedGridBytes,
+ contentAddressedBytes: assets.contentAddressedBytes,
+ assetCount: assets.assetCount,
+ visualEncoding: Object.freeze({
+ codec: "AVIF",
+ encoding: assets.encoding,
+ mimeType: assets.mimeType,
+ quality: assets.quality,
+ chromaSubsampling: "4:4:4",
+ speed: 6,
+ policy: "bounded-lossy-prepared-leaf-lighting-only",
+ exactGeometry: true,
+ exactPreparedPixels: false,
+ }),
+ encoder: assets.encoder,
+ });
+}
diff --git a/src/adapters/flowerbox/src/prepare/cssflower/slicePlan.mjs b/src/adapters/flowerbox/src/prepare/cssflower/slicePlan.mjs
new file mode 100644
index 0000000..0a6bcbf
--- /dev/null
+++ b/src/adapters/flowerbox/src/prepare/cssflower/slicePlan.mjs
@@ -0,0 +1,21 @@
+export const cssflowerSlicePlan = Object.freeze({
+ schema: "polycss-port-slice-plan@1",
+ title: "cssFlower — Microsoft Flower Box",
+ slug: "cssflower",
+ mode: "model-viewer",
+ artifactMode: "prepared-polycss-snapshot",
+ referenceHarness: "source-dump",
+ defaultSceneId: "default-cube",
+ firstSlice: Object.freeze({
+ kind: "fixed-topology-deforming-model",
+ label: "Microsoft Flower Box default cube, subdivision 10",
+ routeContract: "/",
+ expectedInputs: Object.freeze(["documented source-behavior profile", "optional owned native authority"]),
+ cameraIntent: "source 45-degree square perspective from eye (0,0,3.5)",
+ }),
+ formatHints: Object.freeze(["procedural-cube", "prepared-float32-matrix-cycle", "prepared-space-texel-atlas"]),
+});
+
+export function describeFirstSlice() {
+ return `${cssflowerSlicePlan.firstSlice.label} (${cssflowerSlicePlan.artifactMode})`;
+}
diff --git a/src/adapters/flowerbox/src/prepare/cssflower/sourceProfile.mjs b/src/adapters/flowerbox/src/prepare/cssflower/sourceProfile.mjs
new file mode 100644
index 0000000..8559f21
--- /dev/null
+++ b/src/adapters/flowerbox/src/prepare/cssflower/sourceProfile.mjs
@@ -0,0 +1,116 @@
+export const FLOAT = Math.fround;
+const POLYCSS_SOURCE_UNIT_PIXELS = 50;
+
+export const CSSFLOWER_SOURCE_PROFILE = Object.freeze({
+ schema: "cssflower-source-profile@1",
+ id: "microsoft-flower-box-default-cube-subdiv10",
+ authority: "src/adapters/flowerbox/README.md",
+ authorityStatus: "pinned-source-native-state-validated-locally-not-packaged",
+ geometry: "cube",
+ subdivision: 10,
+ sideCount: 6,
+ sideLocalPointCount: 726,
+ triangleCount: 1200,
+ smoothShading: true,
+ bloom: Object.freeze({
+ resetSf: 0,
+ minSf: -1.1,
+ maxSf: 5.1,
+ sfIncrement: 0.05,
+ radialFormula: "d = float32(length(basePosition) * 2); vlen = float32((1 - d) / d); position = float32(basePosition * float32(vlen * sf + 1))",
+ radialFormulaStatus: "pinned-source-operation-order",
+ crossingRule: "reverse-sfi-after-crossing-bound-without-clamp",
+ arithmetic: "ieee754-binary32-stepwise",
+ }),
+ rotation: Object.freeze({ xDegreesPerUpdate: 3, yDegreesPerUpdate: 2, zDegreesPerUpdate: 0 }),
+ colorCycling: false,
+ camera: Object.freeze({
+ fovDegrees: 45,
+ eye: Object.freeze([0, 0, 3.5]),
+ target: Object.freeze([0, 0, 0]),
+ up: Object.freeze([0, 1, 0]),
+ near: 2,
+ far: 5,
+ aspect: 1,
+ stagePixels: 720,
+ }),
+ light: Object.freeze({
+ position: Object.freeze([2, 2, 10, 1]),
+ specular: Object.freeze([0.8, 0.8, 0.8, 1]),
+ shininess: 30,
+ globalAmbient: Object.freeze([0.2, 0.2, 0.2, 1]),
+ materialAmbient: Object.freeze([0.2, 0.2, 0.2, 1]),
+ lightAmbient: Object.freeze([0, 0, 0, 1]),
+ lightDiffuse: Object.freeze([1, 1, 1, 1]),
+ lightSpecular: Object.freeze([1, 1, 1, 1]),
+ normalizeNormals: true,
+ localViewer: false,
+ positionSetUnderIdentityModelview: true,
+ cameraTransformMatrix: "projection",
+ }),
+ presentationTicksPerSecond: 30,
+});
+
+export const CSSFLOWER_SIDE_MATERIALS = Object.freeze([
+ Object.freeze({ id: "front-red", color: "#ff0000", rgb: Object.freeze([255, 0, 0]) }),
+ Object.freeze({ id: "back-green", color: "#00ff00", rgb: Object.freeze([0, 255, 0]) }),
+ Object.freeze({ id: "top-blue", color: "#0000ff", rgb: Object.freeze([0, 0, 255]) }),
+ Object.freeze({ id: "bottom-magenta", color: "#ff00ff", rgb: Object.freeze([255, 0, 255]) }),
+ Object.freeze({ id: "right-cyan", color: "#00ffff", rgb: Object.freeze([0, 255, 255]) }),
+ Object.freeze({ id: "left-yellow", color: "#ffff00", rgb: Object.freeze([255, 255, 0]) }),
+]);
+
+export const CSSFLOWER_CAMERA = Object.freeze(buildPolyCssCamera());
+
+export function buildPolyCssCamera() {
+ const source = CSSFLOWER_SOURCE_PROFILE.camera;
+ const perspective = source.stagePixels / 2 / Math.tan(source.fovDegrees * Math.PI / 360);
+ return {
+ projection: "perspective",
+ perspective,
+ zoom: POLYCSS_SOURCE_UNIT_PIXELS,
+ rotX: 0,
+ rotY: 0,
+ target: [0, 0, 0],
+ distance: POLYCSS_SOURCE_UNIT_PIXELS * source.eye[2] - perspective,
+ calibration: {
+ sourceUnitPixels: POLYCSS_SOURCE_UNIT_PIXELS,
+ equation: "zoom*p/(p+distance-sourceUnitPixels*z) = p/(eyeZ-z)",
+ depthAware: true,
+ },
+ sourceViewport: { width: source.stagePixels, height: source.stagePixels },
+ source: {
+ fovDegrees: source.fovDegrees,
+ eye: [...source.eye],
+ target: [...source.target],
+ up: [...source.up],
+ near: source.near,
+ far: source.far,
+ aspect: source.aspect,
+ },
+ };
+}
+
+export function sourceToPolyCss([x, y, z]) {
+ return [FLOAT(-y), FLOAT(x), FLOAT(z)];
+}
+
+export function preparedRootTransform(xDegrees, yDegrees) {
+ const x = normalizeDegrees(xDegrees);
+ const y = normalizeDegrees(yDegrees);
+ return `rotateX(${-x}deg) rotateY(${y}deg)`;
+}
+
+export function normalizeDegrees(value) {
+ const normalized = value % 360;
+ return Object.is(normalized, -0) ? 0 : normalized < 0 ? normalized + 360 : normalized;
+}
+
+export function floatBits(value) {
+ const floats = new Float32Array([FLOAT(value)]);
+ return new Uint32Array(floats.buffer)[0];
+}
+
+export function floatHex(value) {
+ return floatBits(value).toString(16).padStart(8, "0");
+}
diff --git a/src/adapters/flowerbox/src/prepare/cssflower/writeManifest.mjs b/src/adapters/flowerbox/src/prepare/cssflower/writeManifest.mjs
new file mode 100644
index 0000000..9f8b48f
--- /dev/null
+++ b/src/adapters/flowerbox/src/prepare/cssflower/writeManifest.mjs
@@ -0,0 +1,87 @@
+import { mkdir, rename, writeFile } from "node:fs/promises";
+import { dirname } from "node:path";
+import {
+ generatedPublicRoot,
+ generatedScenePath,
+ generatedSceneUrl,
+ manifestPath,
+} from "./paths.mjs";
+import { assertNoBrowserPathLeaks } from "./provenance.mjs";
+
+export async function writeCssflowerPreparedOutput({
+ title = "cssFlower — Microsoft Flower Box",
+ scenes,
+ defaultSceneId,
+ warnings = [],
+ debugApi = "window.__cssFlowerDebug",
+} = {}) {
+ if (!Array.isArray(scenes) || scenes.length === 0) {
+ throw new Error("writeCssflowerPreparedOutput requires at least one prepared scene.");
+ }
+ await mkdir(generatedPublicRoot, { recursive: true });
+ const manifestScenes = [];
+ for (const scene of scenes) {
+ await writeJsonAtomic(generatedScenePath(scene.id), scene);
+ const snapshot = snapshotEntryForScene(scene);
+ manifestScenes.push({
+ id: scene.id,
+ label: scene.label,
+ sceneUrl: generatedSceneUrl(scene.id),
+ ...snapshot,
+ metrics: scene.metrics ?? {},
+ warnings: scene.warnings ?? [],
+ });
+ }
+ const defaultId = defaultSceneId ?? scenes[0].id;
+ const manifest = {
+ schema: "cssflower-manifest@1",
+ status: "ready",
+ title,
+ artifactMode: "prepared-polycss-snapshot",
+ scaffoldMode: "model-viewer",
+ generatedAssetRoot: "/cssflower/",
+ defaultScene: { id: defaultId },
+ scenes: manifestScenes,
+ source: scenes[0].source,
+ sourceProfileId: scenes[0].sourceProfile?.id,
+ renderer: scenes[0].renderer,
+ metrics: scenes[0].metrics,
+ oracle: scenes[0].oracle,
+ assets: {
+ transforms: scenes[0].playback?.transformAsset,
+ lighting: scenes[0].lighting,
+ stateEvidenceUrl: scenes[0].playback?.stateEvidenceUrl,
+ },
+ warnings,
+ runtime: {
+ debugApi,
+ routeContract: "?scene=",
+ },
+ };
+ await writeJsonAtomic(manifestPath, manifest);
+ return { manifestPath, manifest };
+}
+
+function snapshotEntryForScene(scene) {
+ if (scene.snapshotUrl) {
+ return {
+ snapshotUrl: scene.snapshotUrl,
+ snapshotKind: scene.snapshotKind ?? "polycss-exported-html",
+ artifactKind: "prepared-polycss-snapshot",
+ };
+ }
+ if ("prepared-polycss-snapshot" !== "prepared-polycss-snapshot") return {};
+ return {
+ snapshotUrl: "/cssflower/scenes/" + scene.id + ".polycss.html",
+ snapshotKind: "polycss-exported-html",
+ artifactKind: "prepared-polycss-snapshot",
+ };
+}
+
+async function writeJsonAtomic(path, value) {
+ assertNoBrowserPathLeaks(value);
+ await mkdir(dirname(path), { recursive: true });
+ const tmp = path + ".tmp";
+ await writeFile(tmp, JSON.stringify(value, null, 2) + "\n");
+ await rename(tmp, path);
+}
diff --git a/src/adapters/flowerbox/src/prepare/cssflower/writePreparedAssets.mjs b/src/adapters/flowerbox/src/prepare/cssflower/writePreparedAssets.mjs
new file mode 100644
index 0000000..4da31b6
--- /dev/null
+++ b/src/adapters/flowerbox/src/prepare/cssflower/writePreparedAssets.mjs
@@ -0,0 +1,479 @@
+import { createHash } from "node:crypto";
+import { execFile } from "node:child_process";
+import { copyFile, link, mkdir, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
+import { gzipSync } from "node:zlib";
+import { promisify } from "node:util";
+import { dirname, join } from "node:path";
+import {
+ CSSFLOWER_LIGHTING_ATLAS_ENCODING,
+ CSSFLOWER_LIGHTING_ATLAS_MIME_TYPE,
+ CSSFLOWER_LIGHTING_ATLAS_QUALITY,
+ CSSFLOWER_LIGHTING_GRID_COLUMNS,
+ CSSFLOWER_LIGHTING_GRID_DECODED_BYTES,
+ CSSFLOWER_LIGHTING_GRID_HEIGHT,
+ CSSFLOWER_LIGHTING_GRID_ROWS,
+ CSSFLOWER_LIGHTING_GRID_WIDTH,
+ CSSFLOWER_TRANSFORM_BLOCK_GEOMETRY_STATES,
+ CSSFLOWER_TRANSFORM_BLOCK_SCHEMA,
+} from "../../cssflower/renderContract.mjs";
+import {
+ generatedLightingPagePath,
+ generatedAssetDir,
+ generatedLightingAssetDir,
+ generatedProjectedAssetDir,
+ generatedPreparedLightingPath,
+ generatedPreparedLightingUrl,
+ generatedStateEvidencePath,
+ generatedTransformAssetDir,
+ generatedTransformBlockPath,
+ generatedTransformBlockUrl,
+ generatedTransformsPath,
+ localRoot,
+ localPreparedTransformsPath,
+ repoRoot,
+} from "./paths.mjs";
+import { assertNoBrowserPathLeaks } from "./provenance.mjs";
+
+const execFileAsync = promisify(execFile);
+const DEFAULT_AVIFENC = "/opt/homebrew/bin/avifenc";
+
+export async function writeCssflowerPreparedAssets(compiled, {
+ avifenc = process.env.CSSFLOWER_AVIFENC || DEFAULT_AVIFENC,
+ lightingPageStore,
+} = {}) {
+ if (!lightingPageStore?.pathFor) {
+ throw new TypeError("Prepared cssFlower lighting page store is required");
+ }
+ await writeAtomic(localPreparedTransformsPath, compiled.transformBytes);
+ await unlinkIfPresent(generatedTransformsPath);
+ await rm(generatedProjectedAssetDir, { recursive: true, force: true });
+ await pruneLegacyLightingPages();
+ await prunePublicOracleProofAssets();
+ if (!Array.isArray(compiled.lightingPages) || compiled.lightingPages.length !== compiled.lighting.pageCount) {
+ throw new Error("Prepared cssFlower lighting pages are incomplete");
+ }
+ assertNoBrowserPathLeaks(compiled.evidence);
+ await writeAtomic(generatedStateEvidencePath, Buffer.from(JSON.stringify(compiled.evidence, null, 2) + "\n"));
+ const [transforms, lighting] = await Promise.all([
+ materializeTransformBlocks(compiled),
+ materializePreparedLightingPages(compiled, lightingPageStore, avifenc),
+ ]);
+ return Object.freeze({
+ transformBytes: compiled.transformBytes.length,
+ transformSha256: compiled.transformSha256,
+ lightingBytes: lighting.contentAddressedBytes,
+ lightingSha256: lighting.grid.sha256,
+ lightingPageCount: compiled.lightingPages.length,
+ decodedLightingGridBytes: CSSFLOWER_LIGHTING_GRID_DECODED_BYTES,
+ transforms,
+ lighting,
+ });
+}
+
+async function materializeTransformBlocks(compiled) {
+ const triangleCount = compiled.topology.triangleCount;
+ const geometryStateCount = compiled.cycle.geometryStateCount;
+ const componentCount = 16;
+ const values = new Float32Array(
+ compiled.transformBytes.buffer,
+ compiled.transformBytes.byteOffset,
+ compiled.transformBytes.byteLength / Float32Array.BYTES_PER_ELEMENT,
+ );
+ if (triangleCount !== 1_200 || values.length !== geometryStateCount * triangleCount * componentCount) {
+ throw new Error("Prepared cssFlower matrix3d source is incomplete");
+ }
+ const blocks = [];
+ const keep = new Set();
+ for (let startGeometryStateIndex = 0; startGeometryStateIndex < geometryStateCount;
+ startGeometryStateIndex += CSSFLOWER_TRANSFORM_BLOCK_GEOMETRY_STATES) {
+ const blockGeometryStateCount = Math.min(
+ CSSFLOWER_TRANSFORM_BLOCK_GEOMETRY_STATES,
+ geometryStateCount - startGeometryStateIndex,
+ );
+ const transformCount = blockGeometryStateCount * triangleCount;
+ const lines = new Array(transformCount);
+ for (let transformIndex = 0; transformIndex < transformCount; transformIndex += 1) {
+ const componentOffset = (startGeometryStateIndex * triangleCount + transformIndex) * componentCount;
+ const components = new Array(componentCount);
+ for (let component = 0; component < componentCount; component += 1) {
+ components[component] = formatCssNumber(values[componentOffset + component]);
+ }
+ lines[transformIndex] = `matrix3d(${components.join(",")})`;
+ }
+ const decoded = Buffer.from(`${lines.join("\n")}\n`);
+ const encoded = gzipSync(decoded, { level: 9, mtime: 0 });
+ const encodedSha256 = sha256(encoded);
+ const decodedSha256 = sha256(decoded);
+ const assetPath = generatedTransformBlockPath(encodedSha256);
+ await writeAtomic(assetPath, encoded);
+ keep.add(assetPath);
+ blocks.push(Object.freeze({
+ index: blocks.length,
+ startGeometryStateIndex,
+ geometryStateCount: blockGeometryStateCount,
+ triangleCount,
+ transformCount,
+ assetUrl: generatedTransformBlockUrl(encodedSha256),
+ byteLength: encoded.length,
+ sha256: encodedSha256,
+ decodedByteLength: decoded.length,
+ decodedSha256,
+ }));
+ }
+ await pruneAssetDirectory(generatedTransformAssetDir, keep);
+ return Object.freeze({
+ schema: CSSFLOWER_TRANSFORM_BLOCK_SCHEMA,
+ distribution: "public-independent-prepared-transform-blocks",
+ encoding: "gzip-newline-utf8-geometry-state-major-triangle-major-matrix3d",
+ componentCount,
+ triangleCount,
+ geometryStateCount,
+ blockGeometryStateCount: CSSFLOWER_TRANSFORM_BLOCK_GEOMETRY_STATES,
+ blockCount: blocks.length,
+ byteLength: blocks.reduce((sum, block) => sum + block.byteLength, 0),
+ decodedByteLength: blocks.reduce((sum, block) => sum + block.decodedByteLength, 0),
+ sourceFloat32: Object.freeze({
+ distribution: "ignored-local-preparation-evidence",
+ sha256: compiled.transformSha256,
+ byteLength: compiled.transformBytes.length,
+ }),
+ blocks: Object.freeze(blocks),
+ });
+}
+
+async function materializePreparedLightingPages(compiled, lightingPageStore, avifenc) {
+ const encoder = await executableIdentity(avifenc, ["--version"]);
+ const binding = sha256(Buffer.from(JSON.stringify({
+ encoding: CSSFLOWER_LIGHTING_ATLAS_ENCODING,
+ quality: CSSFLOWER_LIGHTING_ATLAS_QUALITY,
+ gridColumns: CSSFLOWER_LIGHTING_GRID_COLUMNS,
+ gridRows: CSSFLOWER_LIGHTING_GRID_ROWS,
+ encoder,
+ })));
+ if (compiled.lightingPages.length !== CSSFLOWER_LIGHTING_GRID_COLUMNS) {
+ throw new Error("Prepared cssFlower horizontal lighting grid source count drifted");
+ }
+ const sourcePaths = compiled.lightingPages.map((page) => lightingPageStore.pathFor(page.index));
+ const sourceBinding = sha256(Buffer.from(compiled.lightingPages.map((page) => page.sha256).join("\n")));
+ const cacheRoot = join(localRoot, "cache", "prepared-leaf-lighting-avif-grid", binding);
+ const encodedCachePath = join(cacheRoot, `${sourceBinding}.avif`);
+ let encoded;
+ try {
+ encoded = await readFile(encodedCachePath);
+ if (encoded.length < 1) throw Object.assign(new Error("empty encoded lighting grid cache"), { code: "ENOENT" });
+ } catch (error) {
+ if (error?.code !== "ENOENT") throw error;
+ await mkdir(dirname(encodedCachePath), { recursive: true });
+ const temporary = `${encodedCachePath}.tmp-${process.pid}.avif`;
+ await execFileAsync(avifenc, [
+ "--qcolor", String(CSSFLOWER_LIGHTING_ATLAS_QUALITY),
+ "--speed", "6",
+ "--jobs", "2",
+ "--yuv", "444",
+ "--ignore-exif",
+ "--ignore-xmp",
+ "--ignore-icc",
+ "--grid", `${CSSFLOWER_LIGHTING_GRID_COLUMNS}x${CSSFLOWER_LIGHTING_GRID_ROWS}`,
+ ...sourcePaths,
+ temporary,
+ ], { maxBuffer: 8 * 1024 * 1024 });
+ await rename(temporary, encodedCachePath);
+ encoded = await readFile(encodedCachePath);
+ }
+ const encodedSha256 = sha256(encoded);
+ const grid = Object.freeze({
+ schema: "cssflower-prepared-leaf-lighting-grid@1",
+ assetUrl: generatedPreparedLightingUrl(encodedSha256),
+ encoding: CSSFLOWER_LIGHTING_ATLAS_ENCODING,
+ mimeType: CSSFLOWER_LIGHTING_ATLAS_MIME_TYPE,
+ quality: CSSFLOWER_LIGHTING_ATLAS_QUALITY,
+ chromaSubsampling: "4:4:4",
+ exactPreparedPixels: false,
+ columns: CSSFLOWER_LIGHTING_GRID_COLUMNS,
+ rows: CSSFLOWER_LIGHTING_GRID_ROWS,
+ cellWidth: compiled.lighting.atlasWidth,
+ cellHeight: compiled.lighting.atlasHeight,
+ width: CSSFLOWER_LIGHTING_GRID_WIDTH,
+ height: CSSFLOWER_LIGHTING_GRID_HEIGHT,
+ decodedBytes: CSSFLOWER_LIGHTING_GRID_DECODED_BYTES,
+ byteLength: encoded.length,
+ sha256: encodedSha256,
+ });
+ const pages = Object.freeze(compiled.lightingPages.map((sourcePage) => Object.freeze({
+ index: sourcePage.index,
+ startStateIndex: sourcePage.startStateIndex,
+ usedRowCount: sourcePage.usedRowCount,
+ rowCount: sourcePage.rowCount,
+ role: sourcePage.role,
+ width: sourcePage.width,
+ height: sourcePage.height,
+ decodedBytes: sourcePage.decodedBytes,
+ gridColumn: sourcePage.index,
+ gridRow: 0,
+ gridOffsetX: sourcePage.index * sourcePage.width,
+ gridOffsetY: 0,
+ sourceEncoding: sourcePage.encoding,
+ sourcePngByteLength: sourcePage.byteLength,
+ sourcePngSha256: sourcePage.sha256,
+ })));
+ const asset = {
+ source: encodedCachePath,
+ target: generatedPreparedLightingPath(encodedSha256),
+ };
+ await pruneAssetDirectory(generatedLightingAssetDir, new Set([asset.target]));
+ await materializeContentAddressedAssets([asset], 1);
+ return Object.freeze({
+ schema: "cssflower-prepared-leaf-lighting-assets@2",
+ distribution: "public-independent-prepared-leaf-lighting-horizontal-grid",
+ encoding: CSSFLOWER_LIGHTING_ATLAS_ENCODING,
+ mimeType: CSSFLOWER_LIGHTING_ATLAS_MIME_TYPE,
+ quality: CSSFLOWER_LIGHTING_ATLAS_QUALITY,
+ pageCount: pages.length,
+ assetCount: 1,
+ encodedGridBytes: encoded.length,
+ contentAddressedBytes: encoded.length,
+ encoder,
+ grid,
+ pages,
+ });
+}
+
+async function pruneAssetDirectory(root, keepPaths) {
+ await mkdir(root, { recursive: true });
+ const entries = await readdir(root, { withFileTypes: true });
+ await Promise.all(entries.map(async (entry) => {
+ if (!entry.isFile()) return;
+ const path = join(root, entry.name);
+ if (!keepPaths.has(path)) await unlink(path);
+ }));
+}
+
+async function materializeContentAddressedAssets(assets, concurrency) {
+ let nextIndex = 0;
+ await Promise.all(Array.from({ length: Math.min(concurrency, assets.length) }, async () => {
+ while (nextIndex < assets.length) {
+ const index = nextIndex;
+ nextIndex += 1;
+ await hardlinkAtomic(assets[index].source, assets[index].target, index);
+ }
+ }));
+}
+
+async function hardlinkAtomic(source, target, uniqueIndex) {
+ await mkdir(dirname(target), { recursive: true });
+ try {
+ const [sourceStat, targetStat] = await Promise.all([stat(source), stat(target)]);
+ if (sourceStat.dev === targetStat.dev && sourceStat.ino === targetStat.ino) return;
+ } catch (error) {
+ if (error?.code !== "ENOENT") throw error;
+ }
+ const temporary = `${target}.tmp-${process.pid}-${uniqueIndex}`;
+ try {
+ await link(source, temporary);
+ } catch (error) {
+ if (error?.code !== "EXDEV") throw error;
+ await copyFile(source, temporary);
+ }
+ await rename(temporary, target);
+}
+
+export async function createCssflowerPreparedLightingPageStore() {
+ const binding = await lightingCacheBinding(repoRoot);
+ const cacheParent = join(repoRoot, ".local", "cache", "cssflower", "prepared-leaf-lighting");
+ const cacheRoot = join(cacheParent, binding);
+ const pagePaths = new Map();
+ let hitCount = 0;
+ let migratedHitCount = 0;
+ let missCount = 0;
+ let writeCount = 0;
+ return Object.freeze({
+ binding,
+ async read(expectedPage) {
+ const roots = [cacheRoot, ...(await legacyLightingCacheRoots(cacheParent, cacheRoot))];
+ for (const root of roots) {
+ const paths = cachePaths(root, expectedPage.index);
+ try {
+ const [metadataBytes, bytes] = await Promise.all([
+ readFile(paths.metadata),
+ readFile(paths.png),
+ ]);
+ const metadata = JSON.parse(metadataBytes.toString("utf8"));
+ const page = metadata?.page;
+ if (!preparedLightingCacheEntryMatches({
+ metadata,
+ bytes,
+ binding,
+ expectedPage,
+ })) {
+ continue;
+ }
+ pagePaths.set(page.index, paths.png);
+ if (page.index === 0) await writeAtomic(generatedLightingPagePath(page.index), bytes);
+ hitCount += 1;
+ if (root !== cacheRoot) migratedHitCount += 1;
+ return Object.freeze(page);
+ } catch (error) {
+ if (error?.code !== "ENOENT" && !(error instanceof SyntaxError)) throw error;
+ }
+ }
+ missCount += 1;
+ return null;
+ },
+ async write(page) {
+ if (!validPageWithBytes(page)) {
+ throw new TypeError("Prepared cssFlower streaming lighting page is invalid");
+ }
+ const paths = cachePaths(cacheRoot, page.index);
+ const { bytes, ...descriptor } = page;
+ const metadata = Buffer.from(`${JSON.stringify({
+ schema: "cssflower-prepared-leaf-lighting-cache@1",
+ binding,
+ page: descriptor,
+ }, null, 2)}\n`);
+ await Promise.all([
+ ...(page.index === 0 ? [writeAtomic(generatedLightingPagePath(page.index), bytes)] : []),
+ writeAtomic(paths.png, bytes),
+ writeAtomic(paths.metadata, metadata),
+ ]);
+ pagePaths.set(page.index, paths.png);
+ writeCount += 1;
+ },
+ pathFor(pageIndex) {
+ const path = pagePaths.get(pageIndex);
+ if (!path) throw new Error(`Prepared cssFlower lighting page ${pageIndex} has no verified cache path`);
+ return path;
+ },
+ stats() {
+ return Object.freeze({ binding, hitCount, migratedHitCount, missCount, writeCount });
+ },
+ });
+}
+
+async function legacyLightingCacheRoots(cacheParent, currentRoot) {
+ try {
+ const entries = await readdir(cacheParent, { withFileTypes: true });
+ return entries
+ .filter((entry) => entry.isDirectory())
+ .map((entry) => join(cacheParent, entry.name))
+ .filter((root) => root !== currentRoot)
+ .sort();
+ } catch (error) {
+ if (error?.code === "ENOENT") return [];
+ throw error;
+ }
+}
+
+async function pruneLegacyLightingPages() {
+ await mkdir(generatedAssetDir, { recursive: true });
+ const entries = await readdir(generatedAssetDir, { withFileTypes: true });
+ await Promise.all(entries.map(async (entry) => {
+ if (!entry.isFile() || !/^flower-box-space-texels-page-\d{3}\.png$/u.test(entry.name)) return;
+ await unlink(join(generatedAssetDir, entry.name));
+ }));
+}
+
+async function prunePublicOracleProofAssets() {
+ await mkdir(generatedAssetDir, { recursive: true });
+ const entries = await readdir(generatedAssetDir, { withFileTypes: true });
+ await Promise.all(entries.map(async (entry) => {
+ if (!entry.isFile() || !/^projected-(?:pixel-proof|spike|transition)-/u.test(entry.name)) return;
+ await unlink(join(generatedAssetDir, entry.name));
+ }));
+}
+
+async function unlinkIfPresent(path) {
+ try {
+ await unlink(path);
+ } catch (error) {
+ if (error?.code !== "ENOENT") throw error;
+ }
+}
+
+async function lightingCacheBinding(repoRoot) {
+ const paths = [
+ "pnpm-lock.yaml",
+ "src/adapters/flowerbox/src/cssflower/renderContract.mjs",
+ "src/adapters/flowerbox/src/prepare/cssflower/bloomCycle.mjs",
+ "src/adapters/flowerbox/src/prepare/cssflower/compilePreparedCycle.mjs",
+ "src/adapters/flowerbox/src/prepare/cssflower/cubeTopology.mjs",
+ "src/adapters/flowerbox/src/prepare/cssflower/leafRasterLighting.mjs",
+ "src/adapters/flowerbox/src/prepare/cssflower/sourceProfile.mjs",
+ ];
+ const hash = createHash("sha256");
+ for (const path of paths) {
+ hash.update(path);
+ hash.update("\0");
+ hash.update(await readFile(join(repoRoot, path)));
+ hash.update("\0");
+ }
+ return hash.digest("hex");
+}
+
+function cachePaths(cacheRoot, index) {
+ const stem = `page-${String(index).padStart(3, "0")}`;
+ return Object.freeze({
+ png: join(cacheRoot, `${stem}.png`),
+ metadata: join(cacheRoot, `${stem}.json`),
+ });
+}
+
+function sameExpectedPage(actual, expected) {
+ if (!actual || !Number.isSafeInteger(actual.byteLength) || actual.byteLength < 1 ||
+ !/^[a-f0-9]{64}$/u.test(actual.sha256 ?? "")) return false;
+ return Object.entries(expected).every(([key, value]) => actual[key] === value);
+}
+
+export function preparedLightingCacheEntryMatches({ metadata, bytes, binding, expectedPage }) {
+ const page = metadata?.page;
+ return metadata?.schema === "cssflower-prepared-leaf-lighting-cache@1" &&
+ metadata.binding === binding &&
+ sameExpectedPage(page, expectedPage) &&
+ bytes instanceof Uint8Array &&
+ bytes.length === page.byteLength &&
+ sha256(bytes) === page.sha256;
+}
+
+function validPageWithBytes(page) {
+ return Number.isSafeInteger(page?.index) && page.index >= 0 &&
+ page?.bytes instanceof Uint8Array && page.bytes.length === page.byteLength &&
+ sha256(page.bytes) === page.sha256;
+}
+
+async function executableIdentity(path, versionArgs) {
+ const [bytes, fileStat, version] = await Promise.all([
+ readFile(path),
+ stat(path),
+ execFileAsync(path, versionArgs, { maxBuffer: 1024 * 1024 }),
+ ]);
+ return Object.freeze({
+ path,
+ byteLength: fileStat.size,
+ sha256: sha256(bytes),
+ version: `${version.stdout}${version.stderr}`.trim(),
+ flags: Object.freeze([
+ "--qcolor", String(CSSFLOWER_LIGHTING_ATLAS_QUALITY),
+ "--speed", "6",
+ "--jobs", "2",
+ "--yuv", "444",
+ "--ignore-exif",
+ "--ignore-xmp",
+ "--ignore-icc",
+ ]),
+ });
+}
+
+function formatCssNumber(value) {
+ const rounded = Math.round(value * 1_000_000) / 1_000_000;
+ return String(Object.is(rounded, -0) ? 0 : rounded);
+}
+
+function sha256(bytes) {
+ return createHash("sha256").update(bytes).digest("hex");
+}
+
+async function writeAtomic(path, bytes) {
+ await mkdir(dirname(path), { recursive: true });
+ const temporary = `${path}.tmp`;
+ await writeFile(temporary, bytes);
+ await rename(temporary, path);
+}
diff --git a/src/adapters/flowerbox/tools/audit-runtime-surface.mjs b/src/adapters/flowerbox/tools/audit-runtime-surface.mjs
new file mode 100644
index 0000000..15d5685
--- /dev/null
+++ b/src/adapters/flowerbox/tools/audit-runtime-surface.mjs
@@ -0,0 +1,103 @@
+#!/usr/bin/env node
+
+import { existsSync } from "node:fs";
+import { mkdir, readFile, writeFile } from "node:fs/promises";
+import { dirname, join, resolve } from "node:path";
+import { inspectFlowerboxProductBank } from "./productBank.mjs";
+
+const repositoryRoot = resolve(import.meta.dirname, "..", "..", "..", "..");
+const adapterRoot = resolve(import.meta.dirname, "..");
+const runtimeFiles = [
+ "index.html",
+ "src/main.mjs",
+ "src/cssflower/client.mjs",
+ "src/cssflower/debugApi.mjs",
+ "src/cssflower/manifestClient.mjs",
+ "src/cssflower/polycssScene.mjs",
+ "src/cssflower/preparedAssetLoaders.mjs",
+ "src/cssflower/preparedPlayback.mjs",
+ "src/cssflower/renderContract.mjs",
+ "src/cssflower/routeState.mjs",
+ "src/cssflower/stagePresentation.mjs",
+ "src/cssflower/styles.css",
+];
+const failures = [];
+const sources = new Map(await Promise.all(runtimeFiles.map(async (path) => [
+ path,
+ await readFile(join(adapterRoot, path), "utf8"),
+])));
+
+const index = sources.get("index.html");
+if (!/<\/body>/u.test(index.replace(/\s+/gu, "")) ||
+ /<(?:main|header|section|article|form|button|input|output|img|video|canvas|svg)\b/iu.test(index) ||
+ /\bdata-[a-z0-9-]+=/iu.test(index)) {
+ failures.push("index.html is not the empty direct-camera shell");
+}
+for (const [path, source] of sources) {
+ reject(path, source, "alternate renderer", /(?:getContext\s*\(|WebGLRenderingContext|WebGPU|GPUDevice|from\s+["']three["']|createElement\s*\(\s*["'](?:canvas|svg)["']|<(?:canvas|svg)\b)/iu);
+ reject(path, source, "browser prepare or oracle import", /from\s+["'][^"']*(?:\/prepare\/|\/oracle\/)/u);
+ reject(path, source, "runtime geometry or lighting construction", /\b(?:buildCubeTopology|deformCubePoints|computeSmoothPointNormals|computePreparedVertexLighting|createPolyScene)\b/u);
+ reject(path, source, "native replay ingestion", /fetch[^\n]*(?:native|replay|state-packet)|(?:native|replay)[^\n]*fetch/iu);
+}
+const productCss = sources.get("src/cssflower/styles.css");
+reject("src/cssflower/styles.css", productCss, "paint-heavy product CSS", /(?:clip-path|mask(?:-image)?|filter|box-shadow|text-shadow|linear-gradient|radial-gradient|mix-blend-mode)\s*:/iu);
+const playback = sources.get("src/cssflower/preparedPlayback.mjs");
+if (!playback.includes("createPolyMorphPreparedDomTarget({") ||
+ !playback.includes("morphTarget.leaves[leafIndex].writeTransform(transform)")) {
+ failures.push("PolyCSS Morph prepared leaf target is missing");
+}
+if (/projectedPageStyles|applyPreparedProjectedLeafLayout|\.style\.cssText/u.test(playback)) {
+ failures.push("Projected-page or direct leaf cssText path is present");
+}
+if (/\b(?:document|DOMParser|MutationObserver|Image)\b|createElement|appendChild|replaceChildren/u.test(playback)) {
+ failures.push("Prepared playback constructs DOM");
+}
+if (existsSync(join(adapterRoot, "src/cssflower/projectedPageStyles.mjs"))) {
+ failures.push("Projected-page runtime module remains");
+}
+
+const bank = await inspectFlowerboxProductBank(join(repositoryRoot, "build", "generated", "public", "cssflower"));
+if (bank.closureBytes >= 7_000_000) failures.push(`Product bank is too large: ${bank.closureBytes}`);
+if (bank.timelineStateCount !== 360 || bank.geometryStateCount !== 46 ||
+ bank.transformBlockCount !== 3 || bank.lightingAssetCount !== 1 ||
+ bank.lightingQuality !== 60 || bank.visibilityMinimumOwnedPixels !== 8) {
+ failures.push("Product bank is not the accepted rounded q60/min-8 closure");
+}
+
+const report = {
+ schema: "cssgraphics-flowerbox-runtime-audit@2",
+ status: failures.length === 0 ? "pass" : "fail",
+ renderer: "retained-dom-polycss-only",
+ morphTarget: "@layoutit/polycss-morph#createPolyMorphPreparedDomTarget",
+ runtimeFiles,
+ bank,
+ runtime: {
+ retainedTriangleLeafCount: 1_200,
+ retainedRotationRootCount: 1,
+ geometryConstruction: false,
+ projectionCalculation: false,
+ rasterization: false,
+ normalCalculation: false,
+ lightingCalculation: false,
+ domGrowth: false,
+ },
+ excluded: [
+ "Microsoft source",
+ "Microsoft binaries",
+ "native captures",
+ "oracle packets",
+ "projected visual packs",
+ "Three.js",
+ "pixelmatch",
+ ],
+ failures,
+};
+const reportPath = join(repositoryRoot, "build", "reports", "flowerbox-runtime-audit.json");
+await mkdir(dirname(reportPath), { recursive: true });
+await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`);
+process.stdout.write(`${JSON.stringify({ reportPath, ...report }, null, 2)}\n`);
+if (failures.length) process.exitCode = 1;
+
+function reject(path, source, rule, expression) {
+ if (expression.test(source)) failures.push(`${path}: ${rule}`);
+}
diff --git a/src/adapters/flowerbox/tools/package-product-bank.mjs b/src/adapters/flowerbox/tools/package-product-bank.mjs
new file mode 100644
index 0000000..d3b2c6d
--- /dev/null
+++ b/src/adapters/flowerbox/tools/package-product-bank.mjs
@@ -0,0 +1,185 @@
+#!/usr/bin/env node
+
+import { createHash } from "node:crypto";
+import { cp, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
+import { dirname, join, resolve } from "node:path";
+import { gzipSync, gunzipSync } from "node:zlib";
+import {
+ inspectFlowerboxProductBank,
+ writeFlowerboxProductBankDescriptor,
+} from "./productBank.mjs";
+
+const args = parseArgs(process.argv.slice(2));
+if (!args.source) {
+ throw new Error("Usage: package-product-bank.mjs --source [--output ]");
+}
+const sourceRoot = resolve(args.source);
+const outputRoot = resolve(args.output ?? "build/generated/public/cssflower");
+const stagingRoot = `${outputRoot}.staging-${process.pid}`;
+
+await rm(stagingRoot, { recursive: true, force: true });
+await mkdir(dirname(stagingRoot), { recursive: true });
+await cp(sourceRoot, stagingRoot, { recursive: true, force: true });
+
+try {
+ const sourceManifestBytes = await readFile(join(stagingRoot, "manifest.json"));
+ const sourceSceneEncoded = await readFile(join(stagingRoot, "scenes", "default-cube.json.gz"));
+ const sourceSnapshotEncoded = await readFile(join(stagingRoot, "scenes", "default-cube.polycss.html.gz"));
+ const sourceSnapshotDecoded = gunzipSync(sourceSnapshotEncoded);
+ const manifest = JSON.parse(sourceManifestBytes.toString("utf8"));
+ const scene = JSON.parse(gunzipSync(sourceSceneEncoded).toString("utf8"));
+ const entry = manifest.scenes?.find((candidate) => candidate.id === "default-cube");
+ if (!entry) throw new Error("Source Flower Box manifest is missing default-cube");
+
+ scene.label = "Flower Box — default cube";
+ scene.source = {
+ project: "Flower Box — PolyCSS experiment",
+ dataKind: "documented-source-behavior",
+ behaviorAuthority: "src/adapters/flowerbox/README.md",
+ sourceRevision: scene.source?.sourceRevision,
+ sourceFiles: scene.source?.sourceFiles,
+ nativeAuthorityStatus: "qualified-locally-not-packaged",
+ legal: "independently-authored-results-only",
+ redistributableUpstreamBytes: false,
+ };
+ if (scene.sourceProfile) {
+ scene.sourceProfile.authority = "src/adapters/flowerbox/README.md";
+ scene.sourceProfile.authorityStatus = "pinned-source-native-state-validated-locally-not-packaged";
+ }
+ if (scene.lighting?.encoder) delete scene.lighting.encoder.path;
+ if (scene.playback?.transformAsset) delete scene.playback.transformAsset.sourceFloat32;
+ delete scene.meshes;
+ delete scene.oracle;
+ delete scene.playback.stateEvidenceUrl;
+ scene.warnings = [
+ "Independent source-informed PolyCSS experiment; Microsoft source, binaries, captures, and oracle packets are not packaged.",
+ ...scene.warnings,
+ ];
+
+ const sceneDecoded = Buffer.from(`${JSON.stringify(scene)}\n`);
+ const sceneEncoded = gzipSync(sceneDecoded, { level: 9, mtime: 0 });
+ await writeFile(join(stagingRoot, "scenes", "default-cube.json.gz"), sceneEncoded);
+ await rm(join(stagingRoot, "assets", "flower-box-state-evidence.json.gz"), { force: true });
+ await rm(join(stagingRoot, "product-bank.json"), { force: true });
+
+ const transformAssets = await Promise.all(scene.playback.transformAsset.blocks.map(async (block) => {
+ const bytes = await readFile(publicAssetPath(stagingRoot, block.assetUrl));
+ return identityAsset(`transform:${block.index}`, block.assetUrl, bytes);
+ }));
+ const lightingBytes = await readFile(publicAssetPath(stagingRoot, scene.lighting.grid.assetUrl));
+
+ manifest.title = "Flower Box — PolyCSS experiment";
+ entry.label = scene.label;
+ entry.warnings = [...scene.warnings];
+ entry.sceneEncoding = "gzip";
+ entry.snapshotEncoding = "gzip";
+ entry.snapshot = {
+ ...entry.snapshot,
+ url: entry.snapshotUrl,
+ transportEncoding: "gzip",
+ transportByteLength: sourceSnapshotEncoded.length,
+ transportSha256: sha256(sourceSnapshotEncoded),
+ };
+ manifest.assets = {
+ transforms: {
+ distribution: scene.playback.transformAsset.distribution,
+ schema: scene.playback.transformAsset.schema,
+ blockCount: scene.playback.transformAsset.blockCount,
+ byteLength: scene.playback.transformAsset.byteLength,
+ },
+ lighting: {
+ distribution: scene.lighting.distribution,
+ schema: scene.lighting.schema,
+ assetSha256: scene.lighting.assetSha256,
+ byteLength: scene.lighting.grid.byteLength,
+ timelineRowCount: scene.lighting.timelineRowCount,
+ quality: scene.lighting.grid.quality,
+ },
+ };
+ manifest.productionTransport = {
+ schema: "cssflower-product-static-transport@2",
+ exactDecodedSceneAndSnapshotBytes: true,
+ runtimeGeometryConstruction: false,
+ runtimeProjection: false,
+ runtimeRasterization: false,
+ runtimeLightingCalculation: false,
+ assets: [
+ gzipAsset("scene:default-cube", entry.sceneUrl, sceneDecoded, sceneEncoded),
+ gzipAsset("snapshot:default-cube", entry.snapshotUrl, sourceSnapshotDecoded, sourceSnapshotEncoded),
+ ...transformAssets,
+ identityAsset("lighting:grid", scene.lighting.grid.assetUrl, lightingBytes),
+ ],
+ };
+ await writeFile(join(stagingRoot, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`);
+
+ const summary = await inspectFlowerboxProductBank(stagingRoot, { verifyDescriptor: false });
+ await writeFlowerboxProductBankDescriptor(stagingRoot, summary, {
+ sourceManifestSha256: sha256(sourceManifestBytes),
+ sourceSceneEncodedSha256: sha256(sourceSceneEncoded),
+ sourceSnapshotEncodedSha256: sha256(sourceSnapshotEncoded),
+ productPolicy: "rounded-360-state-retained-polycss-morph-q60-minimum-8-owned-pixels",
+ sanitization: [
+ "native qualification identities",
+ "oracle metadata and state evidence",
+ "prepare-only mesh geometry",
+ "ignored transform source descriptor",
+ "local encoder path",
+ "projected visual-pack transport",
+ ],
+ });
+ await inspectFlowerboxProductBank(stagingRoot);
+ await rm(outputRoot, { recursive: true, force: true });
+ await rename(stagingRoot, outputRoot);
+ process.stdout.write(`${JSON.stringify({ outputRoot, ...summary }, null, 2)}\n`);
+} catch (error) {
+ await rm(stagingRoot, { recursive: true, force: true });
+ throw error;
+}
+
+function gzipAsset(id, url, decoded, encoded) {
+ return {
+ id,
+ url,
+ encoding: "gzip",
+ decodedByteLength: decoded.length,
+ decodedSha256: sha256(decoded),
+ encodedByteLength: encoded.length,
+ encodedSha256: sha256(encoded),
+ };
+}
+
+function identityAsset(id, url, bytes) {
+ return {
+ id,
+ url,
+ encoding: "identity",
+ byteLength: bytes.length,
+ sha256: sha256(bytes),
+ };
+}
+
+function publicAssetPath(root, url) {
+ if (typeof url !== "string" || !url.startsWith("/cssflower/") || url.includes("..")) {
+ throw new Error(`Unsafe Flower Box product asset URL: ${url}`);
+ }
+ return join(root, url.slice("/cssflower/".length));
+}
+
+function parseArgs(argv) {
+ const parsed = {};
+ for (let index = 0; index < argv.length; index += 1) {
+ const argument = argv[index];
+ if (!argument.startsWith("--")) continue;
+ const value = argv[index + 1];
+ if (!value || value.startsWith("--")) parsed[argument.slice(2)] = true;
+ else {
+ parsed[argument.slice(2)] = value;
+ index += 1;
+ }
+ }
+ return parsed;
+}
+
+function sha256(bytes) {
+ return createHash("sha256").update(bytes).digest("hex");
+}
diff --git a/src/adapters/flowerbox/tools/polycss-snapshot-page.html b/src/adapters/flowerbox/tools/polycss-snapshot-page.html
new file mode 100644
index 0000000..f5506dc
--- /dev/null
+++ b/src/adapters/flowerbox/tools/polycss-snapshot-page.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+ cssFlower — Microsoft Flower Box PolyCSS Snapshot
+
+
+
+
+
+
+
diff --git a/src/adapters/flowerbox/tools/polycss-snapshot-page.mjs b/src/adapters/flowerbox/tools/polycss-snapshot-page.mjs
new file mode 100644
index 0000000..fe5476c
--- /dev/null
+++ b/src/adapters/flowerbox/tools/polycss-snapshot-page.mjs
@@ -0,0 +1,448 @@
+import {
+ collectPolyRenderStats,
+ createPolyPerspectiveCamera,
+ createPolyScene,
+ exportPolySceneSnapshot,
+} from "@layoutit/polycss";
+import {
+ CSSFLOWER_BOUNDARY_SEAM_BLEED,
+ CSSFLOWER_BOUNDARY_SEAM_BLEED_TEXT,
+ CSSFLOWER_LIGHTING_ATLAS_HEIGHT,
+ CSSFLOWER_LIGHTING_ATLAS_WIDTH,
+ CSSFLOWER_LIGHTING_GRID_COLUMNS,
+ CSSFLOWER_LIGHTING_GRID_HEIGHT,
+ CSSFLOWER_LIGHTING_GRID_ROWS,
+ CSSFLOWER_LIGHTING_GRID_WIDTH,
+ CSSFLOWER_LIGHTING_LAYOUT,
+ CSSFLOWER_LIGHTING_PAGE_COUNT,
+ CSSFLOWER_LIGHTING_PAGE_ROWS,
+ CSSFLOWER_LIGHTING_RASTER_MODE,
+ CSSFLOWER_LIGHTING_SCHEMA,
+ CSSFLOWER_SEAM_BLEED,
+ CSSFLOWER_SEAM_BLEED_TEXT,
+ CSSFLOWER_SEAM_BLEED_POLICY,
+} from "../src/cssflower/renderContract.mjs";
+
+const host = document.getElementById("scene");
+const params = new URLSearchParams(location.search);
+const sceneUrl = params.get("sceneUrl");
+
+main().catch((error) => {
+ window.__cssFlowerDebugSnapshot = {
+ status: "error",
+ error: error.stack || error.message || String(error),
+ };
+});
+
+async function main() {
+ if (!(host instanceof HTMLElement) || !sceneUrl?.startsWith("/cssflower/scenes/")) {
+ throw new Error("cssFlower snapshot page requires a generated cssFlower sceneUrl");
+ }
+ const sceneData = await fetchJson(sceneUrl);
+ validateScene(sceneData);
+ const preparationLightingUrl = sceneData.lighting.grid.assetUrl;
+ const [, , initialTransforms] = await Promise.all([
+ fetchVerifiedBytes(preparationLightingUrl, sceneData.lighting.grid.sha256),
+ loadImage(preparationLightingUrl),
+ loadInitialPreparedTransforms(sceneData),
+ ]);
+ const { scene, mesh } = createSnapshotScene(sceneData);
+ try {
+ const retained = mountRetainedTargets({ scene, mesh, sceneData, initialTransforms, preparationLightingUrl });
+ scene.applyCamera();
+ await scene.whenTexturesReady();
+ await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+
+ const stats = collectPolyRenderStats(host, {
+ polygonCount: sceneData.metrics.preparedLeafCount,
+ scopeSelector: "[data-cssflower-rotation-root]",
+ });
+ assertSnapshotStats(stats, retained, sceneData);
+ const exported = await exportPolySceneSnapshot(host);
+ const preparedAtlasUrl = sceneData.lighting.grid.assetUrl;
+ const html = prepareExportedSnapshot(restorePreparedLightingReference(exported, preparedAtlasUrl));
+ assertExportedSnapshot(html, sceneData);
+ window.__cssFlowerDebugSnapshot = {
+ status: "ready",
+ sceneUrl,
+ sceneId: sceneData.id,
+ html,
+ stats,
+ retainedLeafCount: retained.leaves.length,
+ retainedRotationRootCount: 1,
+ triangleIdCount: retained.triangleIds.size,
+ seamBleed: CSSFLOWER_SEAM_BLEED,
+ boundarySeamBleed: CSSFLOWER_BOUNDARY_SEAM_BLEED,
+ boundaryAdjacentTriangleCount: sceneData.renderer.seamBleedBoundaryAdjacentTriangleCount,
+ mergedCellCount: 0,
+ lightingAtlasStateCount: sceneData.lighting.timelineRowCount,
+ lightingAtlasDataUrlCount: (html.match(/data:image\/png;base64/gu) ?? []).length,
+ preparedAtlasReferenceCount: (html.match(/\/cssflower\/assets\/lighting\/grid-[a-f0-9]{64}\.avif/gu) ?? []).length,
+ scriptCount: (html.match(/