diff --git a/docs/evidence/phase4/2026-09-06-phase4g-challenges.md b/docs/evidence/phase4/2026-09-06-phase4g-challenges.md new file mode 100644 index 0000000..b83a42e --- /dev/null +++ b/docs/evidence/phase4/2026-09-06-phase4g-challenges.md @@ -0,0 +1,11 @@ +# Slice 4g: challenge fixtures and first exhibit visuals + +Implemented September 6, 2026. `tools/visual-challenge-fixtures.mjs` authors all fourteen PRD 130 challenge compositions and first visuals for Exhibits A–D. Generated `.xzbt` files are under `exhibits/visual-challenges` and `exhibits/exhibit-{a,b,c,d}.xzbt`. + +Each composition uses only existing generic primitives, systems, fields, behaviors, bindings and effects. No subject-specific renderer branch was added. The challenge suite validates every document, executes 120 logical ticks twice with different draw frequencies, verifies finite geometry and nonempty frame plans, compares final plans, and verifies cleanup. A nineteenth test checks deterministic standalone generation, embedded fixture identity and JavaScript syntax. + +The standalone `prototypes/phase4/XZBT-visual-acceptance.html` embeds production code and all eighteen fixtures. It provides fixture selection, pause/resume, seed restart, transient spawning, synthetic audio/state sliders and user observation export. Rebuild with `npm run build:visual-acceptance`. + +Exhibit D uses static text labels and state-driven geometry/layer presentation under the 0.1 contract. Live numeric string formatting was not added. The audio-reactive fixture uses the shared signal-binding surface; its review-page slider supplies an explicitly synthetic signal. Full A–D audio, cadence, scenario and final reference-suite obligations remain assigned to their later phases. + +The PRD 130 visual judgment is still a user-observed gate, and this artifact is not the 4h benchmark. No browser/display judgment or measured GC6 result is claimed by these tests. diff --git a/exhibits/exhibit-a.xzbt b/exhibits/exhibit-a.xzbt new file mode 100644 index 0000000..b4fb9d9 --- /dev/null +++ b/exhibits/exhibit-a.xzbt @@ -0,0 +1,110 @@ +{ + "xzbt": "0.1", + "meta": { + "id": "exhibit-a", + "name": "Exhibit A — Procedural Machine", + "version": "0.1.0", + "description": "First visual composition; full reference exhibit acceptance remains Phase 9." + }, + "runtime": { + "seed": 42 + }, + "visuals": { + "scene": { + "coordinateSpace": "virtual", + "width": 960, + "height": 540, + "background": "#101d25" + }, + "systems": { + "wheel": { + "type": "graphic", + "content": { + "outer": { + "type": "ring", + "position": { + "x": 480, + "y": 270 + }, + "radius": 160, + "innerRadius": 158, + "style": { + "fill": "#72c4bd" + } + }, + "inner": { + "type": "ring", + "position": { + "x": 480, + "y": 270 + }, + "radius": 125, + "innerRadius": 123, + "style": { + "fill": "#e1bd7c" + } + }, + "hub": { + "type": "ring", + "position": { + "x": 480, + "y": 270 + }, + "radius": 38, + "innerRadius": 36, + "style": { + "fill": "#e1bd7c" + } + } + } + }, + "spokes": { + "type": "repeater", + "count": 12, + "distribution": { + "type": "point", + "position": { + "x": 480, + "y": 270 + } + }, + "repeat": { + "type": "group", + "transform": { + "rotation": { + "op": "multiply", + "args": [ + { + "ref": "repeat.fraction" + }, + 330 + ] + } + }, + "behaviors": [ + { + "type": "rotate", + "speed": 12 + } + ], + "children": { + "bar": { + "type": "rectangle", + "position": { + "x": 40, + "y": -4 + }, + "size": { + "width": 85, + "height": 8 + }, + "style": { + "fill": "#e1bd7c" + } + } + } + } + } + } + } +} diff --git a/exhibits/exhibit-b.xzbt b/exhibits/exhibit-b.xzbt new file mode 100644 index 0000000..6fc9330 --- /dev/null +++ b/exhibits/exhibit-b.xzbt @@ -0,0 +1,178 @@ +{ + "xzbt": "0.1", + "meta": { + "id": "exhibit-b", + "name": "Exhibit B — Deep Abstract Field", + "version": "0.1.0", + "description": "First visual composition; full reference exhibit acceptance remains Phase 9." + }, + "runtime": { + "seed": 42 + }, + "visuals": { + "scene": { + "coordinateSpace": "virtual", + "width": 960, + "height": 540, + "background": "#101d25" + }, + "systems": { + "network": { + "type": "particles", + "count": 65, + "capacity": 96, + "distribution": { + "type": "rectangle", + "center": { + "x": 480, + "y": 270 + }, + "size": { + "width": 800, + "height": 410 + } + }, + "render": { + "type": "point", + "style": { + "fill": "#d8e9f5", + "pointSize": 4 + } + }, + "links": { + "rule": "nearest", + "count": 3, + "maxDistance": 180, + "maxLinks": 160, + "fadeWithDistance": true, + "style": { + "stroke": "#6899b7", + "strokeWidth": 1 + } + }, + "behaviors": [ + { + "type": "noise-displace", + "amplitude": { + "x": 25, + "y": 25 + }, + "scale": 120, + "speed": 0.1 + } + ] + }, + "morph": { + "type": "graphic", + "content": { + "shape": { + "type": "spline", + "points": [ + { + "x": 100, + "y": 250 + }, + { + "x": 195, + "y": 344.2081569929845 + }, + { + "x": 290, + "y": 225.72359530745103 + }, + { + "x": 385, + "y": 162.0476051788654 + }, + { + "x": 480, + "y": 296.94076835816776 + }, + { + "x": 575, + "y": 325.85627569923156 + }, + { + "x": 670, + "y": 183.51190467861346 + }, + { + "x": 765, + "y": 191.27697433748185 + }, + { + "x": 860, + "y": 331.6203724113671 + } + ], + "style": { + "stroke": "#ecbf85", + "strokeWidth": 4 + }, + "behaviors": [ + { + "type": "morph", + "to": "target", + "duration": "6s", + "loop": "ping-pong" + } + ] + }, + "target": { + "type": "spline", + "points": [ + { + "x": 100, + "y": 420 + }, + { + "x": 195, + "y": 363.24149524059965 + }, + { + "x": 290, + "y": 235.91968579603693 + }, + { + "x": 385, + "y": 134.38917869744083 + }, + { + "x": 480, + "y": 135.48623754987796 + }, + { + "x": 575, + "y": 238.38063008538305 + }, + { + "x": 670, + "y": 365.2039313913952 + }, + { + "x": 765, + "y": 419.97879545751226 + }, + { + "x": 860, + "y": 361.25269717983815 + } + ], + "style": { + "stroke": "#82b7c7", + "strokeWidth": 2 + }, + "visible": false + } + } + } + }, + "effects": [ + { + "type": "bloom", + "radius": 5, + "intensity": 0.5 + } + ] + } +} diff --git a/exhibits/exhibit-c.xzbt b/exhibits/exhibit-c.xzbt new file mode 100644 index 0000000..8c0815e --- /dev/null +++ b/exhibits/exhibit-c.xzbt @@ -0,0 +1,175 @@ +{ + "xzbt": "0.1", + "meta": { + "id": "exhibit-c", + "name": "Exhibit C — Natural Environment", + "version": "0.1.0", + "description": "First visual composition; full reference exhibit acceptance remains Phase 9." + }, + "runtime": { + "seed": 42 + }, + "visuals": { + "scene": { + "coordinateSpace": "virtual", + "width": 960, + "height": 540, + "background": "#101d25" + }, + "systems": { + "landscape": { + "type": "graphic", + "content": { + "sky": { + "type": "rectangle", + "size": { + "width": 960, + "height": 540 + }, + "style": { + "fill": { + "type": "linear-gradient", + "from": { + "x": 0, + "y": 0 + }, + "to": { + "x": 0, + "y": 540 + }, + "stops": [ + { + "offset": 0, + "color": "#aabcb8" + }, + { + "offset": 1, + "color": "#eddcb0" + } + ] + } + } + }, + "sun": { + "type": "ellipse", + "position": { + "x": 735, + "y": 120 + }, + "radius": 48, + "style": { + "fill": "#fff1c6" + } + }, + "far": { + "type": "polygon", + "points": [ + { + "x": 0, + "y": 390 + }, + { + "x": 220, + "y": 150 + }, + { + "x": 470, + "y": 340 + }, + { + "x": 660, + "y": 200 + }, + { + "x": 960, + "y": 410 + }, + { + "x": 960, + "y": 540 + }, + { + "x": 0, + "y": 540 + } + ], + "style": { + "fill": "#667e72" + } + }, + "near": { + "type": "spline", + "closed": true, + "points": [ + { + "x": 0, + "y": 450 + }, + { + "x": 250, + "y": 320 + }, + { + "x": 580, + "y": 480 + }, + { + "x": 960, + "y": 380 + }, + { + "x": 960, + "y": 600 + }, + { + "x": 0, + "y": 600 + } + ], + "style": { + "fill": "#314c41" + } + } + } + }, + "rain": { + "type": "particles", + "count": 240, + "capacity": 256, + "distribution": { + "type": "uniform", + "min": { + "x": 0, + "y": 0 + }, + "max": { + "x": 960, + "y": 540 + } + }, + "render": { + "type": "line", + "to": { + "x": -3, + "y": 15 + }, + "style": { + "stroke": "#90acba", + "strokeWidth": 1, + "opacity": 0.6 + } + }, + "velocity": { + "x": -35, + "y": 220 + }, + "behaviors": [ + { + "type": "wrap", + "bounds": "scene" + } + ] + } + } + } +} diff --git a/exhibits/exhibit-d.xzbt b/exhibits/exhibit-d.xzbt new file mode 100644 index 0000000..0969776 --- /dev/null +++ b/exhibits/exhibit-d.xzbt @@ -0,0 +1,172 @@ +{ + "xzbt": "0.1", + "meta": { + "id": "exhibit-d", + "name": "Exhibit D — Instrument Display", + "version": "0.1.0", + "description": "First visual composition; full reference exhibit acceptance remains Phase 9." + }, + "runtime": { + "seed": 42 + }, + "visuals": { + "scene": { + "coordinateSpace": "virtual", + "width": 960, + "height": 540, + "background": "#101d25" + }, + "systems": { + "rings": { + "type": "graphic", + "content": { + "a": { + "type": "ring", + "position": { + "x": 480, + "y": 270 + }, + "radius": 70, + "innerRadius": 68, + "style": { + "fill": "#72c4bd" + } + }, + "b": { + "type": "ring", + "position": { + "x": 480, + "y": 270 + }, + "radius": 130, + "innerRadius": 128, + "style": { + "fill": "#72c4bd" + } + }, + "c": { + "type": "ring", + "position": { + "x": 480, + "y": 270 + }, + "radius": 190, + "innerRadius": 188, + "style": { + "fill": "#72c4bd" + } + }, + "label": { + "type": "text", + "text": "FIELD / 04", + "position": { + "x": 35, + "y": 45 + }, + "size": 18, + "style": { + "fill": "#a7d3c4" + } + }, + "sweep": { + "type": "group", + "position": { + "x": 480, + "y": 270 + }, + "behaviors": [ + { + "type": "rotate", + "speed": 40 + } + ], + "children": { + "ray": { + "type": "line", + "to": { + "x": 188, + "y": 0 + }, + "style": { + "stroke": "#ddf6a0", + "strokeWidth": 3 + } + } + } + } + }, + "layer": "display" + }, + "ticks": { + "type": "repeater", + "count": 48, + "distribution": { + "type": "ring", + "center": { + "x": 480, + "y": 270 + }, + "radius": 210, + "innerRadius": 209, + "mode": "even" + }, + "repeat": { + "type": "point", + "style": { + "fill": "#789f95", + "pointSize": 3 + } + }, + "layer": "display" + }, + "contacts": { + "type": "particles", + "count": 18, + "capacity": 32, + "distribution": { + "type": "ellipse", + "center": { + "x": 480, + "y": 270 + }, + "radius": 170 + }, + "render": { + "type": "point", + "style": { + "fill": "#d8e6a7", + "pointSize": 5 + } + }, + "behaviors": [ + { + "type": "twinkle", + "min": 0.2, + "max": 1, + "rate": 0.6 + } + ], + "layer": "display" + } + }, + "layers": { + "display": {} + } + }, + "state": { + "level": { + "type": "number", + "initial": 0.7, + "min": 0, + "max": 1 + } + }, + "bindings": [ + { + "source": "state.level", + "target": "visuals.layers.display.opacity", + "scale": 0.7, + "offset": 0.3 + } + ] +} diff --git a/exhibits/visual-challenges/audio-reactive.xzbt b/exhibits/visual-challenges/audio-reactive.xzbt new file mode 100644 index 0000000..17ced25 --- /dev/null +++ b/exhibits/visual-challenges/audio-reactive.xzbt @@ -0,0 +1,91 @@ +{ + "xzbt": "0.1", + "meta": { + "id": "audio-reactive", + "name": "Audio-reactive visualization", + "version": "1.0.0", + "description": "PRD 130 challenge 12; generic visual primitives only." + }, + "runtime": { + "seed": 4211 + }, + "visuals": { + "scene": { + "coordinateSpace": "virtual", + "width": 960, + "height": 540, + "background": "#101d25" + }, + "layers": { + "field": {} + }, + "systems": { + "network": { + "type": "particles", + "count": 65, + "capacity": 96, + "distribution": { + "type": "rectangle", + "center": { + "x": 480, + "y": 270 + }, + "size": { + "width": 800, + "height": 410 + } + }, + "render": { + "type": "point", + "style": { + "fill": "#d8e9f5", + "pointSize": 4 + } + }, + "links": { + "rule": "nearest", + "count": 3, + "maxDistance": 180, + "maxLinks": 160, + "fadeWithDistance": true, + "style": { + "stroke": "#6899b7", + "strokeWidth": 1 + } + }, + "behaviors": [ + { + "type": "noise-displace", + "amplitude": { + "x": 25, + "y": 25 + }, + "scale": 120, + "speed": 0.1 + } + ], + "layer": "field" + } + }, + "effects": [ + { + "type": "bloom", + "intensity": 0.4 + } + ] + }, + "bindings": [ + { + "source": "signals.audio.energy", + "target": "visuals.layers.field.opacity", + "scale": 0.8, + "offset": 0.2 + }, + { + "source": "signals.audio.low", + "target": "visuals.camera.zoom", + "scale": 0.25, + "offset": 1 + } + ] +} diff --git a/exhibits/visual-challenges/constellation-network.xzbt b/exhibits/visual-challenges/constellation-network.xzbt new file mode 100644 index 0000000..11b0a87 --- /dev/null +++ b/exhibits/visual-challenges/constellation-network.xzbt @@ -0,0 +1,67 @@ +{ + "xzbt": "0.1", + "meta": { + "id": "constellation-network", + "name": "Constellation network", + "version": "1.0.0", + "description": "PRD 130 challenge 8; generic visual primitives only." + }, + "runtime": { + "seed": 4207 + }, + "visuals": { + "scene": { + "coordinateSpace": "virtual", + "width": 960, + "height": 540, + "background": "#101d25" + }, + "systems": { + "network": { + "type": "particles", + "count": 65, + "capacity": 96, + "distribution": { + "type": "rectangle", + "center": { + "x": 480, + "y": 270 + }, + "size": { + "width": 800, + "height": 410 + } + }, + "render": { + "type": "point", + "style": { + "fill": "#d8e9f5", + "pointSize": 4 + } + }, + "links": { + "rule": "nearest", + "count": 3, + "maxDistance": 180, + "maxLinks": 160, + "fadeWithDistance": true, + "style": { + "stroke": "#6899b7", + "strokeWidth": 1 + } + }, + "behaviors": [ + { + "type": "noise-displace", + "amplitude": { + "x": 25, + "y": 25 + }, + "scale": 120, + "speed": 0.1 + } + ] + } + } + } +} diff --git a/exhibits/visual-challenges/deep-parallax-starfield.xzbt b/exhibits/visual-challenges/deep-parallax-starfield.xzbt new file mode 100644 index 0000000..faefb82 --- /dev/null +++ b/exhibits/visual-challenges/deep-parallax-starfield.xzbt @@ -0,0 +1,113 @@ +{ + "xzbt": "0.1", + "meta": { + "id": "deep-parallax-starfield", + "name": "Deep parallax starfield", + "version": "1.0.0", + "description": "PRD 130 challenge 1; generic visual primitives only." + }, + "runtime": { + "seed": 4200 + }, + "visuals": { + "scene": { + "coordinateSpace": "virtual", + "width": 960, + "height": 540, + "background": "#101d25" + }, + "layers": { + "far": { + "parallax": 0.2 + }, + "near": { + "parallax": 1 + } + }, + "camera": { + "projection": "perspective", + "focalLength": 1000 + }, + "automation": [ + { + "target": "camera.x", + "loop": { + "mode": "ping-pong" + }, + "interpolation": "smooth", + "points": [ + { + "at": "0ms", + "value": 400 + }, + { + "at": "12s", + "value": 570 + } + ] + } + ], + "systems": { + "far": { + "type": "particles", + "count": 250, + "capacity": 256, + "distribution": { + "type": "uniform", + "min": { + "x": 0, + "y": 0 + }, + "max": { + "x": 960, + "y": 540 + } + }, + "render": { + "type": "point", + "style": { + "fill": "#8c9ebf", + "pointSize": 2 + } + }, + "layer": "far", + "z": { + "random": { + "min": 300, + "max": 1200 + } + } + }, + "near": { + "type": "particles", + "count": 90, + "capacity": 256, + "distribution": { + "type": "uniform", + "min": { + "x": 0, + "y": 0 + }, + "max": { + "x": 960, + "y": 540 + } + }, + "render": { + "type": "point", + "style": { + "fill": "#ecdfc1", + "pointSize": 3 + } + }, + "layer": "near", + "z": { + "random": { + "min": 0, + "max": 200 + } + } + } + } + } +} diff --git a/exhibits/visual-challenges/flow-field.xzbt b/exhibits/visual-challenges/flow-field.xzbt new file mode 100644 index 0000000..fd2ee72 --- /dev/null +++ b/exhibits/visual-challenges/flow-field.xzbt @@ -0,0 +1,71 @@ +{ + "xzbt": "0.1", + "meta": { + "id": "flow-field", + "name": "Fog or smoke-like flow field", + "version": "1.0.0", + "description": "PRD 130 challenge 2; generic visual primitives only." + }, + "runtime": { + "seed": 4201 + }, + "visuals": { + "scene": { + "coordinateSpace": "virtual", + "width": 960, + "height": 540, + "background": "#101d25" + }, + "fields": { + "flow": { + "type": "noise", + "mode": "curl", + "scale": 160, + "amplitude": 25, + "speed": 0.12 + } + }, + "systems": { + "flow": { + "type": "particles", + "count": 150, + "capacity": 256, + "distribution": { + "type": "uniform", + "min": { + "x": 0, + "y": 0 + }, + "max": { + "x": 960, + "y": 540 + } + }, + "render": { + "type": "ellipse", + "radius": 20, + "style": { + "fill": "#b5c1c0", + "opacity": 0.025 + } + }, + "fields": [ + "flow" + ], + "drag": 0.2, + "behaviors": [ + { + "type": "wrap", + "bounds": "scene" + } + ] + } + }, + "effects": [ + { + "type": "blur", + "radius": 5 + } + ] + } +} diff --git a/exhibits/visual-challenges/instrument-display.xzbt b/exhibits/visual-challenges/instrument-display.xzbt new file mode 100644 index 0000000..f2a3917 --- /dev/null +++ b/exhibits/visual-challenges/instrument-display.xzbt @@ -0,0 +1,150 @@ +{ + "xzbt": "0.1", + "meta": { + "id": "instrument-display", + "name": "Instrument and radar display", + "version": "1.0.0", + "description": "PRD 130 challenge 7; generic visual primitives only." + }, + "runtime": { + "seed": 4206 + }, + "visuals": { + "scene": { + "coordinateSpace": "virtual", + "width": 960, + "height": 540, + "background": "#101d25" + }, + "systems": { + "rings": { + "type": "graphic", + "content": { + "a": { + "type": "ring", + "position": { + "x": 480, + "y": 270 + }, + "radius": 70, + "innerRadius": 68, + "style": { + "fill": "#72c4bd" + } + }, + "b": { + "type": "ring", + "position": { + "x": 480, + "y": 270 + }, + "radius": 130, + "innerRadius": 128, + "style": { + "fill": "#72c4bd" + } + }, + "c": { + "type": "ring", + "position": { + "x": 480, + "y": 270 + }, + "radius": 190, + "innerRadius": 188, + "style": { + "fill": "#72c4bd" + } + }, + "label": { + "type": "text", + "text": "FIELD / 04", + "position": { + "x": 35, + "y": 45 + }, + "size": 18, + "style": { + "fill": "#a7d3c4" + } + }, + "sweep": { + "type": "group", + "position": { + "x": 480, + "y": 270 + }, + "behaviors": [ + { + "type": "rotate", + "speed": 40 + } + ], + "children": { + "ray": { + "type": "line", + "to": { + "x": 188, + "y": 0 + }, + "style": { + "stroke": "#ddf6a0", + "strokeWidth": 3 + } + } + } + } + } + }, + "ticks": { + "type": "repeater", + "count": 48, + "distribution": { + "type": "ring", + "center": { + "x": 480, + "y": 270 + }, + "radius": 210, + "innerRadius": 209, + "mode": "even" + }, + "repeat": { + "type": "point", + "style": { + "fill": "#789f95", + "pointSize": 3 + } + } + }, + "contacts": { + "type": "particles", + "count": 18, + "capacity": 32, + "distribution": { + "type": "ellipse", + "center": { + "x": 480, + "y": 270 + }, + "radius": 170 + }, + "render": { + "type": "point", + "style": { + "fill": "#d8e6a7", + "pointSize": 5 + } + }, + "behaviors": [ + { + "type": "twinkle", + "min": 0.2, + "max": 1, + "rate": 0.6 + } + ] + } + } + } +} diff --git a/exhibits/visual-challenges/mechanical-abstraction.xzbt b/exhibits/visual-challenges/mechanical-abstraction.xzbt new file mode 100644 index 0000000..fad8586 --- /dev/null +++ b/exhibits/visual-challenges/mechanical-abstraction.xzbt @@ -0,0 +1,110 @@ +{ + "xzbt": "0.1", + "meta": { + "id": "mechanical-abstraction", + "name": "Rotating mechanical abstraction", + "version": "1.0.0", + "description": "PRD 130 challenge 5; generic visual primitives only." + }, + "runtime": { + "seed": 4204 + }, + "visuals": { + "scene": { + "coordinateSpace": "virtual", + "width": 960, + "height": 540, + "background": "#101d25" + }, + "systems": { + "wheel": { + "type": "graphic", + "content": { + "outer": { + "type": "ring", + "position": { + "x": 480, + "y": 270 + }, + "radius": 160, + "innerRadius": 158, + "style": { + "fill": "#72c4bd" + } + }, + "inner": { + "type": "ring", + "position": { + "x": 480, + "y": 270 + }, + "radius": 125, + "innerRadius": 123, + "style": { + "fill": "#e1bd7c" + } + }, + "hub": { + "type": "ring", + "position": { + "x": 480, + "y": 270 + }, + "radius": 38, + "innerRadius": 36, + "style": { + "fill": "#e1bd7c" + } + } + } + }, + "spokes": { + "type": "repeater", + "count": 12, + "distribution": { + "type": "point", + "position": { + "x": 480, + "y": 270 + } + }, + "repeat": { + "type": "group", + "transform": { + "rotation": { + "op": "multiply", + "args": [ + { + "ref": "repeat.fraction" + }, + 330 + ] + } + }, + "behaviors": [ + { + "type": "rotate", + "speed": 12 + } + ], + "children": { + "bar": { + "type": "rectangle", + "position": { + "x": 40, + "y": -4 + }, + "size": { + "width": 85, + "height": 8 + }, + "style": { + "fill": "#e1bd7c" + } + } + } + } + } + } + } +} diff --git a/exhibits/visual-challenges/morphing-art.xzbt b/exhibits/visual-challenges/morphing-art.xzbt new file mode 100644 index 0000000..1b9aee0 --- /dev/null +++ b/exhibits/visual-challenges/morphing-art.xzbt @@ -0,0 +1,126 @@ +{ + "xzbt": "0.1", + "meta": { + "id": "morphing-art", + "name": "Morphing geometric art", + "version": "1.0.0", + "description": "PRD 130 challenge 11; generic visual primitives only." + }, + "runtime": { + "seed": 4210 + }, + "visuals": { + "scene": { + "coordinateSpace": "virtual", + "width": 960, + "height": 540, + "background": "#101d25" + }, + "systems": { + "morph": { + "type": "graphic", + "content": { + "shape": { + "type": "spline", + "points": [ + { + "x": 100, + "y": 250 + }, + { + "x": 195, + "y": 344.2081569929845 + }, + { + "x": 290, + "y": 225.72359530745103 + }, + { + "x": 385, + "y": 162.0476051788654 + }, + { + "x": 480, + "y": 296.94076835816776 + }, + { + "x": 575, + "y": 325.85627569923156 + }, + { + "x": 670, + "y": 183.51190467861346 + }, + { + "x": 765, + "y": 191.27697433748185 + }, + { + "x": 860, + "y": 331.6203724113671 + } + ], + "style": { + "stroke": "#ecbf85", + "strokeWidth": 4 + }, + "behaviors": [ + { + "type": "morph", + "to": "target", + "duration": "6s", + "loop": "ping-pong" + } + ] + }, + "target": { + "type": "spline", + "points": [ + { + "x": 100, + "y": 420 + }, + { + "x": 195, + "y": 363.24149524059965 + }, + { + "x": 290, + "y": 235.91968579603693 + }, + { + "x": 385, + "y": 134.38917869744083 + }, + { + "x": 480, + "y": 135.48623754987796 + }, + { + "x": 575, + "y": 238.38063008538305 + }, + { + "x": 670, + "y": 365.2039313913952 + }, + { + "x": 765, + "y": 419.97879545751226 + }, + { + "x": 860, + "y": 361.25269717983815 + } + ], + "style": { + "stroke": "#82b7c7", + "strokeWidth": 2 + }, + "visible": false + } + } + } + } + } +} diff --git a/exhibits/visual-challenges/natural-abstraction.xzbt b/exhibits/visual-challenges/natural-abstraction.xzbt new file mode 100644 index 0000000..c3b3f7f --- /dev/null +++ b/exhibits/visual-challenges/natural-abstraction.xzbt @@ -0,0 +1,179 @@ +{ + "xzbt": "0.1", + "meta": { + "id": "natural-abstraction", + "name": "Natural meadow abstraction", + "version": "1.0.0", + "description": "PRD 130 challenge 14; generic visual primitives only." + }, + "runtime": { + "seed": 4213 + }, + "visuals": { + "scene": { + "coordinateSpace": "virtual", + "width": 960, + "height": 540, + "background": "#101d25" + }, + "systems": { + "landscape": { + "type": "graphic", + "content": { + "sky": { + "type": "rectangle", + "size": { + "width": 960, + "height": 540 + }, + "style": { + "fill": { + "type": "linear-gradient", + "from": { + "x": 0, + "y": 0 + }, + "to": { + "x": 0, + "y": 540 + }, + "stops": [ + { + "offset": 0, + "color": "#aabcb8" + }, + { + "offset": 1, + "color": "#eddcb0" + } + ] + } + } + }, + "sun": { + "type": "ellipse", + "position": { + "x": 735, + "y": 120 + }, + "radius": 48, + "style": { + "fill": "#fff1c6" + } + }, + "far": { + "type": "polygon", + "points": [ + { + "x": 0, + "y": 390 + }, + { + "x": 220, + "y": 150 + }, + { + "x": 470, + "y": 340 + }, + { + "x": 660, + "y": 200 + }, + { + "x": 960, + "y": 410 + }, + { + "x": 960, + "y": 540 + }, + { + "x": 0, + "y": 540 + } + ], + "style": { + "fill": "#667e72" + } + }, + "near": { + "type": "spline", + "closed": true, + "points": [ + { + "x": 0, + "y": 450 + }, + { + "x": 250, + "y": 320 + }, + { + "x": 580, + "y": 480 + }, + { + "x": 960, + "y": 380 + }, + { + "x": 960, + "y": 600 + }, + { + "x": 0, + "y": 600 + } + ], + "style": { + "fill": "#314c41" + } + } + } + }, + "seeds": { + "type": "particles", + "count": 70, + "capacity": 256, + "distribution": { + "type": "uniform", + "min": { + "x": 0, + "y": 0 + }, + "max": { + "x": 960, + "y": 540 + } + }, + "render": { + "type": "point", + "style": { + "fill": "#e8daa5", + "pointSize": 3 + } + }, + "velocity": { + "x": 12, + "y": -4 + }, + "behaviors": [ + { + "type": "wrap", + "bounds": "scene" + }, + { + "type": "noise-displace", + "amplitude": { + "x": 12, + "y": 24 + }, + "scale": 100, + "speed": 0.1 + } + ] + } + } + } +} diff --git a/exhibits/visual-challenges/organic-pulse.xzbt b/exhibits/visual-challenges/organic-pulse.xzbt new file mode 100644 index 0000000..ebea07a --- /dev/null +++ b/exhibits/visual-challenges/organic-pulse.xzbt @@ -0,0 +1,102 @@ +{ + "xzbt": "0.1", + "meta": { + "id": "organic-pulse", + "name": "Organic pulsing structure", + "version": "1.0.0", + "description": "PRD 130 challenge 6; generic visual primitives only." + }, + "runtime": { + "seed": 4205 + }, + "visuals": { + "scene": { + "coordinateSpace": "virtual", + "width": 960, + "height": 540, + "background": "#101d25" + }, + "systems": { + "organic": { + "type": "graphic", + "content": { + "body": { + "type": "spline", + "closed": true, + "points": [ + { + "x": 590, + "y": 270 + }, + { + "x": 595.1813787033303, + "y": 332.5 + }, + { + "x": 558, + "y": 378.25317547305485 + }, + { + "x": 480, + "y": 395 + }, + { + "x": 413.5, + "y": 378.25317547305485 + }, + { + "x": 344.90003700962757, + "y": 332.5 + }, + { + "x": 370, + "y": 270 + }, + { + "x": 364.8186212966696, + "y": 207.50000000000003 + }, + { + "x": 401.99999999999994, + "y": 161.7468245269452 + }, + { + "x": 480, + "y": 145 + }, + { + "x": 546.5, + "y": 161.74682452694518 + }, + { + "x": 615.0999629903724, + "y": 207.49999999999994 + } + ], + "style": { + "fill": "#537a63", + "stroke": "#acd2a0", + "strokeWidth": 3 + }, + "behaviors": [ + { + "type": "point-wander", + "amplitude": { + "x": 28, + "y": 28 + }, + "rate": 0.15 + }, + { + "type": "pulse", + "property": "style.opacity", + "amplitude": -0.35, + "frequency": 0.2 + } + ] + } + } + } + } + } +} diff --git a/exhibits/visual-challenges/persistent-landscape.xzbt b/exhibits/visual-challenges/persistent-landscape.xzbt new file mode 100644 index 0000000..aa54cf9 --- /dev/null +++ b/exhibits/visual-challenges/persistent-landscape.xzbt @@ -0,0 +1,137 @@ +{ + "xzbt": "0.1", + "meta": { + "id": "persistent-landscape", + "name": "Persistent landscape", + "version": "1.0.0", + "description": "PRD 130 challenge 9; generic visual primitives only." + }, + "runtime": { + "seed": 4208 + }, + "visuals": { + "scene": { + "coordinateSpace": "virtual", + "width": 960, + "height": 540, + "background": "#101d25" + }, + "systems": { + "landscape": { + "type": "graphic", + "content": { + "sky": { + "type": "rectangle", + "size": { + "width": 960, + "height": 540 + }, + "style": { + "fill": { + "type": "linear-gradient", + "from": { + "x": 0, + "y": 0 + }, + "to": { + "x": 0, + "y": 540 + }, + "stops": [ + { + "offset": 0, + "color": "#aabcb8" + }, + { + "offset": 1, + "color": "#eddcb0" + } + ] + } + } + }, + "sun": { + "type": "ellipse", + "position": { + "x": 735, + "y": 120 + }, + "radius": 48, + "style": { + "fill": "#fff1c6" + } + }, + "far": { + "type": "polygon", + "points": [ + { + "x": 0, + "y": 390 + }, + { + "x": 220, + "y": 150 + }, + { + "x": 470, + "y": 340 + }, + { + "x": 660, + "y": 200 + }, + { + "x": 960, + "y": 410 + }, + { + "x": 960, + "y": 540 + }, + { + "x": 0, + "y": 540 + } + ], + "style": { + "fill": "#667e72" + } + }, + "near": { + "type": "spline", + "closed": true, + "points": [ + { + "x": 0, + "y": 450 + }, + { + "x": 250, + "y": 320 + }, + { + "x": 580, + "y": 480 + }, + { + "x": 960, + "y": 380 + }, + { + "x": 960, + "y": 600 + }, + { + "x": 0, + "y": 600 + } + ], + "style": { + "fill": "#314c41" + } + } + } + } + } + } +} diff --git a/exhibits/visual-challenges/spline-network.xzbt b/exhibits/visual-challenges/spline-network.xzbt new file mode 100644 index 0000000..7836a9a --- /dev/null +++ b/exhibits/visual-challenges/spline-network.xzbt @@ -0,0 +1,381 @@ +{ + "xzbt": "0.1", + "meta": { + "id": "spline-network", + "name": "Evolving spline network", + "version": "1.0.0", + "description": "PRD 130 challenge 3; generic visual primitives only." + }, + "runtime": { + "seed": 4202 + }, + "visuals": { + "scene": { + "coordinateSpace": "virtual", + "width": 960, + "height": 540, + "background": "#101d25" + }, + "systems": { + "curves": { + "type": "graphic", + "content": { + "c0": { + "type": "spline", + "points": [ + { + "x": 100, + "y": 250 + }, + { + "x": 195, + "y": 344.2081569929845 + }, + { + "x": 290, + "y": 225.72359530745103 + }, + { + "x": 385, + "y": 162.0476051788654 + }, + { + "x": 480, + "y": 296.94076835816776 + }, + { + "x": 575, + "y": 325.85627569923156 + }, + { + "x": 670, + "y": 183.51190467861346 + }, + { + "x": 765, + "y": 191.27697433748185 + }, + { + "x": 860, + "y": 331.6203724113671 + } + ], + "style": { + "stroke": "#82b7c7", + "strokeWidth": 2 + }, + "position": { + "x": 0, + "y": -105 + }, + "behaviors": [ + { + "type": "point-wander", + "amplitude": { + "x": 30, + "y": 45 + }, + "rate": 0.13 + } + ] + }, + "c1": { + "type": "spline", + "points": [ + { + "x": 100, + "y": 250 + }, + { + "x": 195, + "y": 344.2081569929845 + }, + { + "x": 290, + "y": 225.72359530745103 + }, + { + "x": 385, + "y": 162.0476051788654 + }, + { + "x": 480, + "y": 296.94076835816776 + }, + { + "x": 575, + "y": 325.85627569923156 + }, + { + "x": 670, + "y": 183.51190467861346 + }, + { + "x": 765, + "y": 191.27697433748185 + }, + { + "x": 860, + "y": 331.6203724113671 + } + ], + "style": { + "stroke": "#82b7c7", + "strokeWidth": 2 + }, + "position": { + "x": 0, + "y": -70 + }, + "behaviors": [ + { + "type": "point-wander", + "amplitude": { + "x": 30, + "y": 45 + }, + "rate": 0.13 + } + ] + }, + "c2": { + "type": "spline", + "points": [ + { + "x": 100, + "y": 250 + }, + { + "x": 195, + "y": 344.2081569929845 + }, + { + "x": 290, + "y": 225.72359530745103 + }, + { + "x": 385, + "y": 162.0476051788654 + }, + { + "x": 480, + "y": 296.94076835816776 + }, + { + "x": 575, + "y": 325.85627569923156 + }, + { + "x": 670, + "y": 183.51190467861346 + }, + { + "x": 765, + "y": 191.27697433748185 + }, + { + "x": 860, + "y": 331.6203724113671 + } + ], + "style": { + "stroke": "#82b7c7", + "strokeWidth": 2 + }, + "position": { + "x": 0, + "y": -35 + }, + "behaviors": [ + { + "type": "point-wander", + "amplitude": { + "x": 30, + "y": 45 + }, + "rate": 0.13 + } + ] + }, + "c3": { + "type": "spline", + "points": [ + { + "x": 100, + "y": 250 + }, + { + "x": 195, + "y": 344.2081569929845 + }, + { + "x": 290, + "y": 225.72359530745103 + }, + { + "x": 385, + "y": 162.0476051788654 + }, + { + "x": 480, + "y": 296.94076835816776 + }, + { + "x": 575, + "y": 325.85627569923156 + }, + { + "x": 670, + "y": 183.51190467861346 + }, + { + "x": 765, + "y": 191.27697433748185 + }, + { + "x": 860, + "y": 331.6203724113671 + } + ], + "style": { + "stroke": "#82b7c7", + "strokeWidth": 2 + }, + "position": { + "x": 0, + "y": 0 + }, + "behaviors": [ + { + "type": "point-wander", + "amplitude": { + "x": 30, + "y": 45 + }, + "rate": 0.13 + } + ] + }, + "c4": { + "type": "spline", + "points": [ + { + "x": 100, + "y": 250 + }, + { + "x": 195, + "y": 344.2081569929845 + }, + { + "x": 290, + "y": 225.72359530745103 + }, + { + "x": 385, + "y": 162.0476051788654 + }, + { + "x": 480, + "y": 296.94076835816776 + }, + { + "x": 575, + "y": 325.85627569923156 + }, + { + "x": 670, + "y": 183.51190467861346 + }, + { + "x": 765, + "y": 191.27697433748185 + }, + { + "x": 860, + "y": 331.6203724113671 + } + ], + "style": { + "stroke": "#82b7c7", + "strokeWidth": 2 + }, + "position": { + "x": 0, + "y": 35 + }, + "behaviors": [ + { + "type": "point-wander", + "amplitude": { + "x": 30, + "y": 45 + }, + "rate": 0.13 + } + ] + }, + "c5": { + "type": "spline", + "points": [ + { + "x": 100, + "y": 250 + }, + { + "x": 195, + "y": 344.2081569929845 + }, + { + "x": 290, + "y": 225.72359530745103 + }, + { + "x": 385, + "y": 162.0476051788654 + }, + { + "x": 480, + "y": 296.94076835816776 + }, + { + "x": 575, + "y": 325.85627569923156 + }, + { + "x": 670, + "y": 183.51190467861346 + }, + { + "x": 765, + "y": 191.27697433748185 + }, + { + "x": 860, + "y": 331.6203724113671 + } + ], + "style": { + "stroke": "#82b7c7", + "strokeWidth": 2 + }, + "position": { + "x": 0, + "y": 70 + }, + "behaviors": [ + { + "type": "point-wander", + "amplitude": { + "x": 30, + "y": 45 + }, + "rate": 0.13 + } + ] + } + } + } + } + } +} diff --git a/exhibits/visual-challenges/transient-impact.xzbt b/exhibits/visual-challenges/transient-impact.xzbt new file mode 100644 index 0000000..f7c6f2a --- /dev/null +++ b/exhibits/visual-challenges/transient-impact.xzbt @@ -0,0 +1,78 @@ +{ + "xzbt": "0.1", + "meta": { + "id": "transient-impact", + "name": "Transient impact burst", + "version": "1.0.0", + "description": "PRD 130 challenge 10; generic visual primitives only." + }, + "runtime": { + "seed": 4209 + }, + "visuals": { + "scene": { + "coordinateSpace": "virtual", + "width": 960, + "height": 540, + "background": "#101d25" + }, + "systems": { + "burst": { + "type": "particles", + "lifecycle": "spawned", + "spawn": { + "lifetime": "2s", + "release": "1s" + }, + "count": 120, + "capacity": 128, + "distribution": { + "type": "point", + "position": { + "x": 480, + "y": 270 + } + }, + "velocity": { + "x": { + "random": { + "min": -160, + "max": 160 + } + }, + "y": { + "random": { + "min": -160, + "max": 160 + } + } + }, + "lifetime": "3s", + "size": { + "from": 1.5, + "to": 0.1 + }, + "opacity": { + "from": 1, + "to": 0 + }, + "render": { + "type": "point", + "style": { + "fill": "#f5c276", + "pointSize": 4 + } + }, + "trail": { + "length": 20, + "mode": "line", + "width": 2, + "style": { + "stroke": "#d98963", + "strokeWidth": 2 + } + } + } + } + } +} diff --git a/exhibits/visual-challenges/weather-particles.xzbt b/exhibits/visual-challenges/weather-particles.xzbt new file mode 100644 index 0000000..2f020d1 --- /dev/null +++ b/exhibits/visual-challenges/weather-particles.xzbt @@ -0,0 +1,60 @@ +{ + "xzbt": "0.1", + "meta": { + "id": "weather-particles", + "name": "Rain particles", + "version": "1.0.0", + "description": "PRD 130 challenge 4; generic visual primitives only." + }, + "runtime": { + "seed": 4203 + }, + "visuals": { + "scene": { + "coordinateSpace": "virtual", + "width": 960, + "height": 540, + "background": "#101d25" + }, + "systems": { + "rain": { + "type": "particles", + "count": 240, + "capacity": 256, + "distribution": { + "type": "uniform", + "min": { + "x": 0, + "y": 0 + }, + "max": { + "x": 960, + "y": 540 + } + }, + "render": { + "type": "line", + "to": { + "x": -3, + "y": 15 + }, + "style": { + "stroke": "#90acba", + "strokeWidth": 1, + "opacity": 0.6 + } + }, + "velocity": { + "x": -35, + "y": 220 + }, + "behaviors": [ + { + "type": "wrap", + "bounds": "scene" + } + ] + } + } + } +} diff --git a/exhibits/visual-challenges/window-traffic.xzbt b/exhibits/visual-challenges/window-traffic.xzbt new file mode 100644 index 0000000..70fb8a3 --- /dev/null +++ b/exhibits/visual-challenges/window-traffic.xzbt @@ -0,0 +1,130 @@ +{ + "xzbt": "0.1", + "meta": { + "id": "window-traffic", + "name": "Window containing procedural traffic", + "version": "1.0.0", + "description": "PRD 130 challenge 13; generic visual primitives only." + }, + "runtime": { + "seed": 4212 + }, + "visuals": { + "scene": { + "coordinateSpace": "virtual", + "width": 960, + "height": 540, + "background": "#101d25" + }, + "systems": { + "background": { + "type": "graphic", + "content": { + "wall": { + "type": "rectangle", + "size": { + "width": 960, + "height": 540 + }, + "style": { + "fill": "#c8b69c" + } + }, + "window": { + "type": "rectangle", + "position": { + "x": 150, + "y": 90 + }, + "size": { + "width": 660, + "height": 360 + }, + "style": { + "fill": "#273f49" + } + } + } + }, + "traffic": { + "type": "repeater", + "count": 14, + "distribution": { + "type": "grid", + "columns": 7, + "rows": 2, + "origin": { + "x": 185, + "y": 235 + }, + "spacing": { + "x": 88, + "y": 75 + } + }, + "repeat": { + "type": "rectangle", + "size": { + "width": 24, + "height": 8 + }, + "style": { + "fill": "#eed092" + }, + "behaviors": [ + { + "type": "drift", + "velocity": { + "x": 20, + "y": 0 + } + }, + { + "type": "wrap", + "bounds": { + "x": -30, + "y": -30, + "width": 60, + "height": 60 + } + } + ] + } + }, + "frame": { + "type": "graphic", + "content": { + "mullion": { + "type": "rectangle", + "position": { + "x": 470, + "y": 85 + }, + "size": { + "width": 12, + "height": 370 + }, + "style": { + "fill": "#d8c8af" + } + }, + "border": { + "type": "rectangle", + "position": { + "x": 145, + "y": 85 + }, + "size": { + "width": 670, + "height": 370 + }, + "style": { + "stroke": "#e8d8bf", + "strokeWidth": 12 + } + } + } + } + } + } +} diff --git a/test/phase4-challenges.test.mjs b/test/phase4-challenges.test.mjs new file mode 100644 index 0000000..40d4a77 --- /dev/null +++ b/test/phase4-challenges.test.mjs @@ -0,0 +1,55 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { visualChallenges, visualExhibits } from '../tools/visual-challenge-fixtures.mjs'; +import { validateExhibit } from '../src/runtime/validator.js'; +import { VisualEngine } from '../src/runtime/visual-engine.js'; +import { ResolutionEngine } from '../src/runtime/resolution.js'; +import { SeededRNG } from '../src/runtime/rng.js'; +import { Script } from 'node:vm'; +import { readFileSync } from 'node:fs'; +import { buildVisualAcceptance } from '../tools/build-visual-acceptance.mjs'; + +for (const fixture of [...visualChallenges, ...visualExhibits]) test(`4g ${fixture.id}: validates, executes, draws, and reproduces across frame rates`, () => { + assert.deepEqual(validateExhibit(fixture.document).errors, []); + const run = cadence => { + const rng = new SeededRNG(fixture.document.runtime.seed); + const resolution = new ResolutionEngine(fixture.document, rng); + const engine = new VisualEngine(fixture.document, { rng, resolution }); + if (fixture.id === 'transient-impact') engine.spawn('burst'); + let plan; + for (let i = 0; i < 120; i++) { + resolution.signals.set('signals.audio.energy', (Math.sin(i / 20) + 1) / 2); + resolution.signals.set('signals.audio.low', (Math.cos(i / 30) + 1) / 2); + resolution.advance(1000 / 60); engine.advance(1000 / 60); + if (i % cadence === 0 || i === 119) plan = engine.planFrame({ width: 960, height: 540 }); + } + assert.equal(plan.deferred.length, 0); + assert.ok(plan.layers.some(layer => layer.nodes.length > 0)); + const finite = object => { + if (typeof object === 'number') assert.ok(Number.isFinite(object)); + else if (object && typeof object === 'object') for (const value of Object.values(object)) finite(value); + }; + finite(plan.layers); + engine.dispose(); assert.equal(engine.instances.size, 0); assert.equal(resolution.automation.size, 0); + return plan.layers; + }; + assert.deepEqual(run(1), run(4)); +}); + +test('4g standalone acceptance build is deterministic, contains every fixture and parses as JavaScript', () => { + const first = buildVisualAcceptance(), second = buildVisualAcceptance(); + assert.equal(first.html, second.html); assert.equal(first.count, 18); + const script = /