Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
Cache-Control = "public, max-age=31536000, immutable"

[[headers]]
for = "/csssolitaire/assets/*"
for = "/csssolitaire/assets/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"

Expand All @@ -67,6 +67,16 @@
[headers.values]
Cache-Control = "public, max-age=0, must-revalidate"

[[headers]]
for = "/cssplatonicfolding/*"
[headers.values]
Cache-Control = "public, max-age=0, must-revalidate"

[[headers]]
for = "/cssplatonicfolding/model/*/assets/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"

[[headers]]
for = "/cssselectropaint/variants/*/chunks/*.gz"
[headers.values]
Expand Down
10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"dev:electropaint": "vite --config src/adapters/electropaint/vite.config.mjs --host 127.0.0.1",
"dev:menger": "vite --config src/adapters/menger/vite.config.mjs --host 127.0.0.1",
"dev:maze": "vite --config src/adapters/maze/vite.config.mjs --host 127.0.0.1",
"dev:platonic-folding": "vite --config src/adapters/platonic-folding/vite.config.mjs --host 127.0.0.1",
"dev:solitaire": "vite --config src/adapters/solitaire/vite.config.mjs --host 127.0.0.1",
"build": "pnpm build:site && pnpm build:lib",
"build:site": "vite build",
Expand All @@ -63,10 +64,12 @@
"build:maze": "vite build --config src/adapters/maze/vite.config.mjs",
"build:maze:full": "pnpm prepare:maze:source && pnpm build:maze",
"build:maze:deploy": "CSSMAZE_DEPLOY_BUILD=1 pnpm build:maze",
"build:platonic-folding": "vite build --config src/adapters/platonic-folding/vite.config.mjs",
"build:platonic-folding:full": "pnpm prepare:platonic-folding && pnpm build:platonic-folding",
"build:solitaire": "vite build --config src/adapters/solitaire/vite.config.mjs",
"build:solitaire:full": "pnpm prepare:solitaire && pnpm build:solitaire",
"build:solitaire:deploy": "CSSSOLITAIRE_DEPLOY_BUILD=1 pnpm build:solitaire",
"build:deploy": "pnpm exec playwright install chromium && pnpm prepare:3dpipes && pnpm prepare:flowerbox:artifact && pnpm prepare:gears:artifact && pnpm prepare:gravitywell:artifact && pnpm prepare:menger:artifact && pnpm prepare:maze:artifact && pnpm prepare:electropaint:deploy && pnpm prepare:solitaire && CSSPIPES_DEPLOY_BUILD=1 pnpm build:3dpipes && CSSFLOWER_DEPLOY_BUILD=1 pnpm build:flowerbox && CSSGEARS_DEPLOY_BUILD=1 pnpm build:gears && CSSGRAVITYWELL_DEPLOY_BUILD=1 pnpm build:gravitywell && CSSMENGER_DEPLOY_BUILD=1 pnpm build:menger && CSSMAZE_DEPLOY_BUILD=1 pnpm build:maze && CSSSELECTROPAINT_DEPLOY_BUILD=1 pnpm build:electropaint && CSSSOLITAIRE_DEPLOY_BUILD=1 pnpm build:solitaire && CSSGRAPHICS_DEPLOY_BUILD=1 pnpm build:site",
"build:deploy": "pnpm exec playwright install chromium && pnpm prepare:3dpipes && pnpm prepare:flowerbox:artifact && pnpm prepare:gears:artifact && pnpm prepare:gravitywell:artifact && pnpm prepare:menger:artifact && pnpm prepare:maze:artifact && pnpm prepare:electropaint:deploy && pnpm prepare:platonic-folding && pnpm prepare:solitaire && CSSPIPES_DEPLOY_BUILD=1 pnpm build:3dpipes && CSSFLOWER_DEPLOY_BUILD=1 pnpm build:flowerbox && CSSGEARS_DEPLOY_BUILD=1 pnpm build:gears && CSSGRAVITYWELL_DEPLOY_BUILD=1 pnpm build:gravitywell && CSSMENGER_DEPLOY_BUILD=1 pnpm build:menger && CSSMAZE_DEPLOY_BUILD=1 pnpm build:maze && CSSSELECTROPAINT_DEPLOY_BUILD=1 pnpm build:electropaint && CSSPLATONICFOLDING_DEPLOY_BUILD=1 pnpm build:platonic-folding && CSSSOLITAIRE_DEPLOY_BUILD=1 pnpm build:solitaire && CSSGRAPHICS_DEPLOY_BUILD=1 pnpm build:site",
"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",
Expand All @@ -88,6 +91,8 @@
"prepare:maze:artifact": "node scripts/prepare/cssmaze-product-bank.mjs",
"prepare:maze:source": "node src/adapters/maze/tools/prepare-cssmaze.mjs && node src/adapters/maze/tools/prepare-polycss-snapshot.mjs",
"prepare:maze:check": "node src/adapters/maze/tools/check-prepare-determinism.mjs",
"prepare:platonic-folding": "node src/adapters/platonic-folding/tools/prepare-cssplatonicfolding.mjs",
"prepare:platonic-folding:check": "node src/adapters/platonic-folding/tools/check-prepare-determinism.mjs",
"prepare:solitaire": "node src/adapters/solitaire/tools/prepare-csssolitaire.mjs",
"prepare:solitaire:check": "node src/adapters/solitaire/tools/check-prepare-determinism.mjs",
"prepare:catalog": "pnpm build:lib && node scripts/prepare/catalog.mjs",
Expand Down Expand Up @@ -147,6 +152,9 @@
"capture:maze:browser:frames": "node src/adapters/maze/tools/capture-browser-frames.mjs",
"compare:maze:frames": "node src/adapters/maze/tools/compare-frame-sequence.mjs",
"perf:maze:trace": "node src/adapters/maze/tools/trace-performance.mjs",
"test:platonic-folding": "node --test src/adapters/platonic-folding/test/*.test.mjs",
"test:platonic-folding:browser": "node src/adapters/platonic-folding/tools/smoke-browser.mjs",
"oracle:platonic-folding": "node src/adapters/platonic-folding/tools/oracle/run-visual-oracle.mjs",
"test:solitaire": "node --test src/adapters/solitaire/test/csssolitaire-bank-selection.test.mjs src/adapters/solitaire/test/csssolitaire-shell.test.mjs src/adapters/solitaire/test/csssolitaire-source-profile.test.mjs",
"test:solitaire:assets": "node --test src/adapters/solitaire/test/csssolitaire-assets.test.mjs",
"test:solitaire:browser": "node src/adapters/solitaire/tools/smoke-browser.mjs",
Expand Down
9 changes: 9 additions & 0 deletions src/adapters/platonic-folding/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Platonic Folding adapter contract

- Read `notes/provenance-bible.md` before changing topology, face geometry, colors, lighting, camera, timing, motion, or parity claims.
- Preserve the pinned XScreenSaver face graphs, 4/6/8/12/20 face census, dihedral fold angles, 25 ms cadence, quintic hinge easing, 45-degree field of view, and source portrait/landscape travel direction.
- Preparation owns the deterministic unfolding trees, complete sparse playback, preformatted transforms, face colors, raster lighting rows, and two responsive product banks. The mobile bank's 1.55x eye distance is the only camera adaptation from the source constants.
- Runtime may mount the one shared `static-prepared` Morph package, bind its source-ordered elements with `createPolyMorphPreparedDomTarget`, consume the selected bank's sparse operation rows, and update the responsive perspective carrier. Normal playback must not materialize full prepared states, diff the retained graph, format matrices, look up ids, calculate geometry, rasterize assets, grow the DOM, scan all leaves, or rebuild topology.
- Keep one stable retained graph with 50 face roots and 50 `<s>` leaves. Only the active solid is visible. No Canvas, WebGL, WebGPU, SVG scene geometry, masks, clip paths, runtime atlas work, or paint-heavy CSS effects.
- Keep source checkouts, native binaries, captures, traces, and generated prepared assets ignored.
- Keep the independent headless CGL frame-sequence oracle passing across all five solids. Its qualified claim is a bounded source-faithful approximation, not visual parity or pixel identity.
12 changes: 12 additions & 0 deletions src/adapters/platonic-folding/LICENSE.XSCREENSAVER
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Copyright (c) 2025-2026 Carsten Steger <carsten@mirsanmir.org>.

Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted, provided that
the above copyright notice appear in all copies and that both that copyright
notice and this permission notice appear in supporting documentation.

This file is provided AS IS with no warranties of any kind. The author shall
have no liability with respect to the infringement of copyrights, trade
secrets or any patents by this file or any part thereof. In no event will the
author be liable for any lost revenue or profits or other special, indirect
and consequential damages.
37 changes: 37 additions & 0 deletions src/adapters/platonic-folding/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# css.graphics/platonic-folding

Source-backed XScreenSaver Platonic Folding rendered as 50 retained PolyCSS
Morph face roots. Preparation bakes one shared `static-prepared` Morph package,
both responsive sparse playback banks, every hinge pose, source-style face
colors, six-decimal transforms, and quantized lighting with quarter-pixel edge
coverage into one raster atlas.

```sh
export CSSPLATONICFOLDING_SOURCE_ROOT=/path/to/xscreensaver
pnpm prepare:platonic-folding
pnpm dev:platonic-folding
pnpm test:platonic-folding
pnpm prepare:platonic-folding:check
pnpm test:platonic-folding:browser
pnpm build:platonic-folding
pnpm oracle:platonic-folding
```

Local preparation verifies the exact checkout when
`CSSPLATONICFOLDING_SOURCE_ROOT` is set. Release builds fetch the same two files
from the pinned GitHub revision and verify their locked SHA-256 identities
before producing anything.

Runtime owns only the prepared playback clock, source-ordered sparse DOM
publication through `createPolyMorphPreparedDomTarget`, and responsive
perspective. Normal playback consumes preformatted transform and atlas-row
selections directly. It performs no prepared-state materialization, full-state
diff, matrix formatting, id lookup, geometry construction, atlas rasterization,
topology rebuilding, DOM growth, or full retained-graph scan.

The oracle compiles the pinned XScreenSaver source into a headless CGL capture,
captures the same deterministic frame schedule from the browser, proves exact
native and browser A/A stability, and compares all five solids. The qualified
result is a bounded source-faithful approximation, not visual parity or pixel
identity; the remaining delta is confined to prepared atlas filtering and
64-state per-face color quantization.
39 changes: 39 additions & 0 deletions src/adapters/platonic-folding/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="XScreenSaver Platonic Folding rendered entirely with retained HTML and CSS using PolyCSS.">
<meta name="robots" content="index, follow">
<meta name="theme-color" content="#000000">
<link rel="canonical" href="https://css.graphics/platonic-folding/">
<link rel="icon" href="/favicon.ico" sizes="any">
<meta property="og:type" content="website">
<meta property="og:site_name" content="css.graphics">
<meta property="og:title" content="Platonic Folding - Powered by PolyCSS">
<meta property="og:description" content="XScreenSaver Platonic Folding rendered entirely with retained HTML and CSS using PolyCSS.">
<meta property="og:url" content="https://css.graphics/platonic-folding/">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Platonic Folding - Powered by PolyCSS">
<meta name="twitter:description" content="XScreenSaver Platonic Folding rendered entirely with retained HTML and CSS using PolyCSS.">
<title>Platonic Folding - Powered by PolyCSS</title>
</head>
<body class="loading">
<header class="site-header">
<h1 class="site-wordmark-heading">
<a class="site-wordmark" href="/">
<svg class="site-wordmark-svg" viewBox="0 0 302 30">
<text x="0" y="23"><tspan class="site-wordmark-css">css.</tspan><tspan class="site-wordmark-graphics">graphics</tspan><tspan class="site-wordmark-path">/platonic-folding</tspan></text>
</svg>
</a>
</h1>
<a class="site-action-icon-only" href="https://github.com/layoutit/cssGraphics" target="_blank" rel="noopener noreferrer" title="View cssGraphics on GitHub">
<svg class="site-action-icon" viewBox="0 0 24 24">
<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3.3-.4 6.8-1.6 6.8-7A5.5 5.5 0 0 0 19.3 3.7 5.1 5.1 0 0 0 19.1 0S17.9-.4 15 1.5a13.4 13.4 0 0 0-6 0C6.1-.4 4.9 0 4.9 0a5.1 5.1 0 0 0-.2 3.7 5.5 5.5 0 0 0-1.5 3.8c0 5.4 3.5 6.6 6.8 7A4.8 4.8 0 0 0 9 18v4"></path>
<path d="M9 19c-3 .9-3-1.5-4.2-2"></path>
</svg>
</a>
</header>
<script type="module" src="/src/main.mjs"></script>
</body>
</html>
35 changes: 35 additions & 0 deletions src/adapters/platonic-folding/notes/provenance-bible.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Platonic Folding provenance

The authority is XScreenSaver `hacks/glx/platonicfolding.c` at revision
`906693799e4fb7581436590cf84ecb2d3c9186ba`. The source file carries a
permissive use, copy, modification, and distribution notice from Carsten
Steger. The adjacent XML config supplies the public defaults and description.

The prepared source model preserves the five face-adjacency graphs, base face
geometry, face counts, eye distances, maximum fold angles, 25 ms cadence,
180-step entrance and exit, quintic hinge easing, 0.5-degree rotation cadence,
45-degree field of view, and portrait/landscape travel direction. The mobile
bank moves the eye 1.55 times farther away so complete nets fit narrow screens.
The product
chooses the source-supported one-folding mode and a deterministic valid run in
which each solid uses joint folding.

All five solids share one `static-prepared` Morph graph: 50 face roots and 50
raster leaves. Only the active solid's 4, 6, 8, 12, or 20 leaves are visible.
Preparation owns the unfolding trees, complete 2,710-frame sequence,
source-ordered sparse operation rows, six-decimal preformatted face matrices,
source-style face colors, quantized baked lighting rows, and the atlas with
quarter-pixel prepared edge coverage. Runtime binds the shared
graph with `createPolyMorphPreparedDomTarget` and owns only the clock, direct
prepared operation selection, and responsive perspective. Full prepared-state
materialization, full-state diffs, matrix formatting, id lookup, and normal-path
full-graph scans remain outside playback.

The independent oracle includes the pinned source file directly in a headless
CGL harness and exercises its GLSL folding draw path. Native and browser A/A
captures are exact. The matched native/browser schedule covers entrance,
folded, transition, unfolded, refolded, and exit poses for all five solids. The
qualified claim is a bounded source-faithful approximation rather than visual
parity or pixel identity: the remaining delta is confined to prepared atlas
filtering and the 64-state per-face color palettes. The existing upstream video
remains preview material, not proof.
14 changes: 14 additions & 0 deletions src/adapters/platonic-folding/notes/references/source-lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"title": "XScreenSaver Platonic Folding",
"revision": "906693799e4fb7581436590cf84ecb2d3c9186ba",
"repository": "https://github.com/Zygo/xscreensaver",
"primary": {
"path": "hacks/glx/platonicfolding.c",
"sha256": "2c2c31987b09941814000f2c1dbbfee8f92f7fa1a82fca120c2c57c03e03057d"
},
"config": {
"path": "hacks/config/platonicfolding.xml",
"sha256": "2fb097c5365aba8b3510c355b3b81695177275ef3d4a0d4c3fd1c0bd38df759f"
},
"license": "Per-file permissive notice by Carsten Steger, 2025-2026"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function selectPlatonicBank({ width = innerWidth, height = innerHeight } = {}) {
return width < height ? "mobile" : "desktop";
}
119 changes: 119 additions & 0 deletions src/adapters/platonic-folding/src/cssplatonicfolding/client.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { createPolyPerspectiveCamera } from "@layoutit/polycss";
import {
loadPolyMorphPackage,
mountPolyMorphModel,
} from "@layoutit/polycss-morph";
import { selectPlatonicBank } from "./bankSelection.mjs";
import { createPlatonicPreparedPlayer } from "./preparedPlayback.mjs";

const MODEL_ID = "platonic-folding";

export function mountPlatonicFolding(host) {
const state = { ready: false, errors: [], bankId: null, metadata: null, mounted: null, player: null };
installDebugApi(state);
main().catch(fail);
return state;

async function main() {
const bankId = selectPlatonicBank({ width: host.clientWidth || innerWidth, height: host.clientHeight || innerHeight });
const playbackPath = `/cssplatonicfolding/banks/${bankId}/playback.json`;
const [loaded, metadata, playback] = await Promise.all([
loadPolyMorphPackage("/cssplatonicfolding/model/", { modelId: MODEL_ID }),
fetch("/cssplatonicfolding/prepared.json", { cache: "no-store" })
.then(assertResponse)
.then((response) => response.json()),
fetch(playbackPath)
.then(assertResponse)
.then((response) => response.json()),
]);
const preparedBank = metadata?.preparedBanks?.find((bank) => bank.id === bankId);
if (metadata?.schema !== "cssplatonicfolding-prepared-scene@1" ||
loaded.model.identity.id !== MODEL_ID || preparedBank?.modelId !== MODEL_ID ||
preparedBank?.playbackPath !== playbackPath || playback?.bankId !== bankId ||
playback?.modelId !== MODEL_ID) {
throw new Error("Platonic Folding prepared model binding drifted");
}
const mounted = mountPolyMorphModel(host, loaded.model, {
resources: loaded.resources,
camera: createPolyPerspectiveCamera({
perspective: 800,
target: [0, 0, 0],
rotX: 0,
rotY: 0,
zoom: 50,
distance: 0,
}),
});
cleanPreparedDom(mounted);
const player = createPlatonicPreparedPlayer({ mounted, playback });
player.resize();
addEventListener("resize", player.resize, { passive: true });
state.ready = true;
state.bankId = bankId;
state.metadata = metadata;
state.mounted = mounted;
state.player = player;
document.body.classList.replace("loading", "ready");
player.resume();
}

function fail(error) {
state.errors.push(String(error?.stack || error));
document.body.classList.remove("loading");
document.body.classList.add("error");
console.error(error);
}
}

function cleanPreparedDom(mounted) {
mounted.cameraElement.className = "polycss-camera";
mounted.cameraElement.removeAttribute("data-polycss-camera-projection");
mounted.sceneElement.className = "polycss-scene";
mounted.modelElement.removeAttribute("class");
mounted.modelElement.removeAttribute("data-poly-morph-model");
for (const element of mounted.shapeElements.values()) {
element.removeAttribute("class");
element.removeAttribute("data-poly-morph-shape");
}
for (const { element } of mounted.leafHandles.values()) {
element.removeAttribute("class");
element.removeAttribute("data-poly-morph-leaf");
element.removeAttribute("data-poly-morph-strategy");
element.removeAttribute("data-poly-morph-resolved-strategy");
element.style.removeProperty("backface-visibility");
element.style.removeProperty("background-repeat");
element.style.removeProperty("opacity");
element.style.removeProperty("transform-origin");
element.style.removeProperty("visibility");
}
}

function installDebugApi(state) {
Object.defineProperty(window, "__cssPlatonicFoldingDebug", {
configurable: true,
value: Object.freeze({
get ready() { return state.ready; },
get errors() { return Object.freeze([...state.errors]); },
pause() { return state.player?.pause(); },
resume() { return state.player?.resume(); },
seekFrame(index) { return state.player?.seekFrame(index); },
state() { return state.player?.stats() ?? null; },
metadata() { return state.metadata; },
stats() {
if (!state.mounted || !state.player) return null;
return Object.freeze({
retainedFaceRootCount: state.mounted.shapeElements.size,
retainedPolygonLeafCount: state.mounted.leafHandles.size,
selectedPreparedBank: state.bankId,
preparedBankLoadCount: 1,
...state.player.stats(),
});
},
}),
});
}

function assertResponse(response) {
if (!response.ok) throw new Error(`Platonic Folding prepared asset failed: ${response.status} ${response.url}`);
return response;
}
Loading