From d5d70912899552638256c7d5cf389ba8555d892a Mon Sep 17 00:00:00 2001 From: Labyricorn Date: Sun, 6 Sep 2026 21:53:38 +0000 Subject: [PATCH] feat(visual): add the slice 4g challenge fixtures and reference exhibits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fourteen visual challenge fixtures and the first visuals for reference exhibits A through D, all executing without a line of subject-specific renderer code — which is the claim slice 4g exists to test. A subject the engine needs special handling for is a subject the format cannot express. The standalone CLI validator no longer disagrees with the production one. It had been falling through to ERR_UNSUPPORTED_TARGET for every `visuals.*` binding target, so legal exhibits — including the committed minimal-visual fixture — failed the CLI while passing the runtime. The four section 8.1 visual target families are now recognized in both, and a test runs the CLI validator across every exhibit and challenge fixture so the two paths cannot drift apart again. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01ShxxFqFmCUDQnQvFNm4TKy --- .../phase4/2026-09-06-phase4g-challenges.md | 11 + exhibits/exhibit-a.xzbt | 110 +++++ exhibits/exhibit-b.xzbt | 178 ++++++++ exhibits/exhibit-c.xzbt | 175 ++++++++ exhibits/exhibit-d.xzbt | 172 ++++++++ .../visual-challenges/audio-reactive.xzbt | 91 +++++ .../constellation-network.xzbt | 67 +++ .../deep-parallax-starfield.xzbt | 113 ++++++ exhibits/visual-challenges/flow-field.xzbt | 71 ++++ .../visual-challenges/instrument-display.xzbt | 150 +++++++ .../mechanical-abstraction.xzbt | 110 +++++ exhibits/visual-challenges/morphing-art.xzbt | 126 ++++++ .../natural-abstraction.xzbt | 179 ++++++++ exhibits/visual-challenges/organic-pulse.xzbt | 102 +++++ .../persistent-landscape.xzbt | 137 +++++++ .../visual-challenges/spline-network.xzbt | 381 ++++++++++++++++++ .../visual-challenges/transient-impact.xzbt | 78 ++++ .../visual-challenges/weather-particles.xzbt | 60 +++ .../visual-challenges/window-traffic.xzbt | 130 ++++++ test/phase4-challenges.test.mjs | 55 +++ tools/validate-exhibit.mjs | 30 +- tools/visual-challenge-fixtures.mjs | 91 +++++ 22 files changed, 2616 insertions(+), 1 deletion(-) create mode 100644 docs/evidence/phase4/2026-09-06-phase4g-challenges.md create mode 100644 exhibits/exhibit-a.xzbt create mode 100644 exhibits/exhibit-b.xzbt create mode 100644 exhibits/exhibit-c.xzbt create mode 100644 exhibits/exhibit-d.xzbt create mode 100644 exhibits/visual-challenges/audio-reactive.xzbt create mode 100644 exhibits/visual-challenges/constellation-network.xzbt create mode 100644 exhibits/visual-challenges/deep-parallax-starfield.xzbt create mode 100644 exhibits/visual-challenges/flow-field.xzbt create mode 100644 exhibits/visual-challenges/instrument-display.xzbt create mode 100644 exhibits/visual-challenges/mechanical-abstraction.xzbt create mode 100644 exhibits/visual-challenges/morphing-art.xzbt create mode 100644 exhibits/visual-challenges/natural-abstraction.xzbt create mode 100644 exhibits/visual-challenges/organic-pulse.xzbt create mode 100644 exhibits/visual-challenges/persistent-landscape.xzbt create mode 100644 exhibits/visual-challenges/spline-network.xzbt create mode 100644 exhibits/visual-challenges/transient-impact.xzbt create mode 100644 exhibits/visual-challenges/weather-particles.xzbt create mode 100644 exhibits/visual-challenges/window-traffic.xzbt create mode 100644 test/phase4-challenges.test.mjs create mode 100644 tools/visual-challenge-fixtures.mjs 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 = /