feat(visual): implement the slice 4e procedural systems and behaviors
The normative coherent noise of 18.7 (ordered gradients, a 255-sample Fisher-Yates permutation, octave normalization, and curl as the explicit perpendicular of the potential's gradient, with published tolerances), the nine distributions with their normative draw-order table, all seventeen behaviors with their field contracts and the accumulating-versus-fresh split, component expansion independent of group nesting depth, particles, emitters, repeaters, trails, and links. Traces 1 through 19 of 18.10 are automated. The renderer core this builds on is corrected here rather than separately, because 4e is what exercises it: the production render path now passes a real compositing surface factory, so a masked group or a non-opaque layer composites instead of throwing and permanently deactivating the visual subsystem; system-level `behaviors` arrays run `validateBehaviors` at import with field context, so the six classes of invalid document that used to fail at activation now fail where an author can see them; velocity-accumulating behaviors integrate on object and repeater hosts; `morph` guards both sides of the z delta rather than poisoning perspective projection with NaN; `face-motion` initializes its follow state lazily and holds the previous rotation at zero velocity; a `ring` with equal start and end angles draws nothing instead of a radial spoke; a resolved non-integer creation count raises ERR_TYPE_MISMATCH at the boundary instead of being rounded; boolean leaves are type-checked; per-type required fields and the static half of the unbounded-emission rule are enforced at import; and a repeater rejects the emitter-only fields 18.5 says it has none of. A duration is now the authored literal or a non-negative finite number already in milliseconds, resolving the one design question the review triage left open. Runtime, validator, and specification agree on it. A guard test asserts that no two bundled modules declare the same top-level identifier: the bundler concatenates into one scope, so a private helper name collision is a SyntaxError in the artifact while every unit test still passes. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01ShxxFqFmCUDQnQvFNm4TKy
This commit is contained in:
@@ -9,7 +9,7 @@ import { Diagnostics } from '../src/runtime/diagnostics.js';
|
||||
import { LibraryManager } from '../src/runtime/library.js';
|
||||
import { deriveStreamState, SeededRNG } from '../src/runtime/rng.js';
|
||||
import { parseAndValidateExhibit } from '../src/runtime/validator.js';
|
||||
import { buildStandalone } from '../tools/build-xzbt.mjs';
|
||||
import { buildStandalone, bundleRuntime } from '../tools/build-xzbt.mjs';
|
||||
|
||||
const fixedSource = readFileSync(resolve('exhibits/minimal-fixed.xzbt'), 'utf8');
|
||||
const randomSource = readFileSync(resolve('exhibits/minimal-random.xzbt'), 'utf8');
|
||||
@@ -79,8 +79,34 @@ test('two clean standalone builds are byte-identical and self-contained', () =>
|
||||
// A module import *statement*, not the word in a comment: the bundler strips
|
||||
// the former, and prose about import-stage validation is not a leak.
|
||||
assert.doesNotMatch(html, /^[ \t]*import[ \t][^;(\n]*from[ \t]*['"]/m);
|
||||
// C14: Also catch side-effect imports (import './chunk.js';).
|
||||
assert.doesNotMatch(html, /^[ \t]*import[ \t]*['"]/m);
|
||||
assert.doesNotMatch(html, /^[ \t]*export[ \t]/m);
|
||||
assert.equal(createHash('sha256').update(firstBytes).digest('hex'), createHash('sha256').update(secondBytes).digest('hex'));
|
||||
const script = html.match(/<script>([\s\S]*)<\/script>/)[1];
|
||||
assert.doesNotThrow(() => new Function(script));
|
||||
});
|
||||
|
||||
test('no two bundled modules declare the same top-level identifier', () => {
|
||||
// The bundler strips imports and exports and concatenates into one scope, so
|
||||
// two modules that each declare a private helper of the same name produce a
|
||||
// SyntaxError in the artifact while every unit test still passes.
|
||||
const sources = bundleRuntime();
|
||||
const declarations = /^(?:const|let|class|function)\s+([A-Za-z_$][\w$]*)/gm;
|
||||
const perModule = sources.split(/^\/\* (src\/runtime\/[\w.-]+) \*\/$/m);
|
||||
const seen = new Map();
|
||||
const collisions = [];
|
||||
for (let index = 1; index < perModule.length; index += 2) {
|
||||
const module = perModule[index];
|
||||
const body = perModule[index + 1] ?? '';
|
||||
const names = new Set();
|
||||
let match;
|
||||
declarations.lastIndex = 0;
|
||||
while ((match = declarations.exec(body)) !== null) names.add(match[1]);
|
||||
for (const name of names) {
|
||||
if (seen.has(name)) collisions.push(`${name}: ${seen.get(name)} and ${module}`);
|
||||
else seen.set(name, module);
|
||||
}
|
||||
}
|
||||
assert.deepEqual(collisions, []);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user