Revision 0.9 adds section 20, the cadence and event subsystems contract, and carries two corrections the implementation forced. Section 6.1 now states that a duration is the authored literal or a non-negative finite number already in milliseconds, since a DurationSpec may be the resolved output of a ValueSpec or a bounded TimeSpec, with the one documented exception of an automation track's `at`, which 19.1 keeps literal-only so that point ordering stays decidable at import. Section 20.11 documents the rejection of an undeclared input name in an event action's `with` map as ERR_UNKNOWN_FIELD — the section's own convention for that shape of error, replacing an invented code that appeared nowhere in the registry. The review record is committed with the code it describes: the two code triages that found these defects, the reconciliation plan that sequenced the fixes, and a follow-up debt record listing what was deliberately left open — the unchecked JSON Schema artifact, degenerate path arcs, post-effect transient allocation, the window-traffic fixture's per-copy wrap bounds, and the unstated `ownership: "persistent"` value on a sound action. None of the five blocks phase 6; all five are written down rather than dropped. Devlog entries are backfilled for the two milestones that had none: phase 3c slice 2, the audio lifecycle and voice ceilings, and slice 4d, the renderer core. The implementation status summary now reflects the reconciled state rather than the in-flight one. 231 tests pass. tools/verify-spec-contract.py reports 46 declared diagnostic codes with every used code resolving and its two long-standing unresolved cross-references unchanged. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01ShxxFqFmCUDQnQvFNm4TKy
86 lines
3.3 KiB
JavaScript
86 lines
3.3 KiB
JavaScript
#!/usr/bin/env node
|
|
import { createHash } from 'node:crypto';
|
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
import { dirname, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
const sourceFiles = Object.freeze([
|
|
'src/runtime/constants.js',
|
|
'src/runtime/diagnostics.js',
|
|
'src/runtime/rng.js',
|
|
'src/runtime/types.js',
|
|
'src/runtime/values.js',
|
|
'src/runtime/audio-contract.js',
|
|
'src/runtime/audio-protection.js',
|
|
'src/runtime/audio-automation.js',
|
|
'src/runtime/audio-graph.js',
|
|
'src/runtime/audio-controls.js',
|
|
'src/runtime/visual-contract.js',
|
|
'src/runtime/visual-diagnostics.js',
|
|
'src/runtime/visual-math.js',
|
|
'src/runtime/visual-geometry.js',
|
|
'src/runtime/visual-noise.js',
|
|
'src/runtime/visual-behaviors.js',
|
|
'src/runtime/visual-distributions.js',
|
|
'src/runtime/visual-fields.js',
|
|
'src/runtime/visual-systems.js',
|
|
'src/runtime/visual-validation.js',
|
|
'src/runtime/cadence-validation.js',
|
|
'src/runtime/validator.js',
|
|
'src/runtime/persistence.js',
|
|
'src/runtime/library.js',
|
|
'src/runtime/resolution.js',
|
|
'src/runtime/visual-motion.js',
|
|
'src/runtime/visual-automation.js',
|
|
'src/runtime/visual-lifecycle.js',
|
|
'src/runtime/visual-effects.js',
|
|
'src/runtime/visual-engine.js',
|
|
'src/runtime/visual-canvas2d.js',
|
|
'src/runtime/visual-subsystem.js',
|
|
'src/runtime/actions.js',
|
|
'src/runtime/cadence.js',
|
|
'src/runtime/performance.js',
|
|
'src/runtime/activation.js',
|
|
'src/runtime/audio-engine.js',
|
|
'src/runtime/app.js'
|
|
]);
|
|
|
|
function normalized(path) {
|
|
return readFileSync(resolve(repositoryRoot, path), 'utf8').replace(/\r\n/g, '\n');
|
|
}
|
|
|
|
function bundleModule(source, path) {
|
|
const withoutImports = source.replace(/^import\s+[\s\S]*?\s+from\s+['"][^'"]+['"];\s*$/gm, '');
|
|
const withoutExports = withoutImports.replace(/\bexport\s+/g, '');
|
|
return `\n/* ${path} */\n${withoutExports.trim()}\n`;
|
|
}
|
|
|
|
export function bundleRuntime(includeApp = true) {
|
|
return sourceFiles.filter(path => includeApp || path !== 'src/runtime/app.js')
|
|
.map(path => bundleModule(normalized(path), path)).join('');
|
|
}
|
|
|
|
export function buildStandalone(outputPath = resolve(repositoryRoot, 'XZBT.html')) {
|
|
const template = normalized('src/XZBT.template.html');
|
|
const styles = normalized('src/styles.css').trim();
|
|
const script = `'use strict';\n(() => {\n${bundleRuntime()}\n})();`;
|
|
if (!template.includes('/*__XZBT_STYLES__*/') || !template.includes('/*__XZBT_SCRIPT__*/')) {
|
|
throw new Error('Standalone template is missing a build placeholder.');
|
|
}
|
|
const artifact = template
|
|
.replace('/*__XZBT_STYLES__*/', () => styles)
|
|
.replace('/*__XZBT_SCRIPT__*/', () => script)
|
|
.replace(/\r\n/g, '\n');
|
|
writeFileSync(outputPath, artifact, 'utf8');
|
|
return { outputPath, bytes: Buffer.byteLength(artifact), sha256: createHash('sha256').update(artifact).digest('hex') };
|
|
}
|
|
|
|
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
const outputIndex = process.argv.indexOf('--output');
|
|
const outputPath = outputIndex >= 0 ? resolve(process.argv[outputIndex + 1]) : resolve(repositoryRoot, 'XZBT.html');
|
|
const result = buildStandalone(outputPath);
|
|
console.log(`Built ${result.outputPath}`);
|
|
console.log(`SHA-256 ${result.sha256} (${result.bytes} bytes)`);
|
|
}
|