feat(phase0): complete shared contracts and milestone plan

This commit is contained in:
2026-09-05 12:08:36 -07:00
parent ee774d0307
commit 63895c4fe7
23 changed files with 1433 additions and 161 deletions
+222 -30
View File
@@ -1,8 +1,8 @@
# XZBT Format Specification 0.1
**XZBT format version:** 0.1
**Document revision:** 0.2
**Status:** Normative specification for shared contracts (document, types, ValueSpec, ConditionSpec, resolution); subsystem contracts in progress
**Document revision:** 0.3
**Status:** Normative specification for shared contracts (document, types, ValueSpec, ConditionSpec, resolution, bindings, and transitions); subsystem contracts in progress
**Related resources:** [PRD](../XZBT_0-1_MVP_Product_Requirements_Document.md), [decisions](XZBT_0-1_Gap_Closure_Decisions.md), [verification](XZBT_0-1_Verification_Gates.md)
This document defines normative syntax and runtime semantics for XZBT 0.1 exhibits. An exhibit is a UTF-8 JSON document that configures generic procedural visual, audio, cadence, and orchestration primitives. It does not contain executable JavaScript.
@@ -273,8 +273,17 @@ To ensure consistent error reporting between structural schema validation, seman
| `ERR_INVALID_OPERATOR` | Semantic | ValueSpec `op` or ConditionSpec `op` is not in the recognized operator set. |
| `ERR_INVALID_ARITY` | Semantic | ValueSpec `args` array length does not match operator requirement. |
| `ERR_INVALID_DURATION` | Structural / Semantic | Duration string violates single-unit regex or contains negative values. |
| `ERR_UNSUPPORTED_TARGET` | Semantic / Runtime | An action, binding, or resolution stage addresses a target that does not expose that operation. |
| `ERR_CONFLICTING_BINDING` | Semantic | More than one enabled ordinary binding writes a scalar target. |
| `ERR_INVALID_TRANSITION` | Semantic / Runtime | A transition is incompatible with its target, easing, scope, or duration. |
| `ERR_DISPATCH_BUDGET` | Runtime | Ordinary event/action work exceeded the per-tick dispatch budget. |
| `WARN_CLOCK_STALL` | Runtime | Elapsed wall time or accumulated work exceeded the fixed-step per-turn limits and was discarded. |
| `WARN_CLEANUP_FORCED` | Runtime | A cleanup owner reached its deadline and force-disposed remaining resources. |
| `INFO_AUDIO_UNLOCK_SKIP` | Runtime | One or more pre-unlock one-shots were intentionally not replayed. |
## 8. Shared value resolution
## 8. Shared value resolution, bindings, and transitions
### 8.1 Resolution pipeline and target capabilities
For each supported target, evaluate:
@@ -282,48 +291,202 @@ For each supported target, evaluate:
base -> binding -> automation -> winning override -> modulation -> safety clamp
```
Skip stages not exposed by the target contract. A target cannot accept automation or modulation merely because it is numeric. Additive modulation is summed only where explicitly supported.
Stages that the target does not expose are absent, not identity hooks available to authors. The shared 0.1 registry is:
Parameters store user configuration separately from exhibit defaults. State stores simulation values separately from parameters. A property may obtain its base through ValueSpec. State is not an implicit layer overwriting every parameter.
| Target family | Type and base | Binding target | Automation | Override | Additive modulation | Safety clamp |
| --- | --- | :---: | :---: | :---: | :---: | --- |
| `parameters.<id>` | Declared primitive; stored user value | Yes | No | Yes | No | Declared `min`/`max`; integer targets round to the nearest integer after interpolation and before clamping |
| `state.<id>` | Declared primitive; current state value | Yes | No | Yes | No | Declared `min`/`max` |
| `audio.buses.<id>.gain` | Number; bus `gain` ValueSpec | Yes | Yes | Yes | Yes | `[0, 4]` in 0.1 before master output protection |
References ordinarily read resolved values. Parameter controls read and edit stored user values, and display an override indicator when appropriate. Underlying bindings and automation continue to evaluate while masked by an override.
`signals.*` and `modulators.*` are read-only sources and never binding or action targets. `instances.*` is a runtime action-addressing namespace, not a ValueSpec or binding namespace. Sound inputs are sampled at sound invocation and cannot be ordinary binding targets. Visual properties, audio recipe-instance properties, and any target family absent from this table remain unsupported until their subsystem contract adds an explicit capability row. Merely being numeric does not grant automation, override, or modulation support.
Override lifetime and priority are independent. Use explicit priority when supported, otherwise inherit the originating scenario's priority; equal priorities resolve by activation order. Duration scope confers no additional priority. The complete action contract must define non-scenario default priority, permitted explicit priority fields, and ordering identifiers before implementation.
Parameters store user configuration separately from exhibit defaults. State stores simulation values separately from parameters. State is not an implicit layer above user configuration. References read the value resolved for the current logical tick. Parameter controls read and edit stored user values and show an override indicator whenever any live override exists for the parameter, including a masked or releasing override.
On release, blend toward the current lower resolved value rather than a snapshot taken when the override started. User edits and changing bindings remain visible to that lower evaluation. Numeric release interpolation, interruptions by another override, and nonnumeric release behavior require exact contracts below.
Within one logical tick, resolve the directed dependency graph in topological order. Each source reference observes the source's resolved value for that same tick. Dependency cycles are `ERR_CYCLIC_DEPENDENCY`; the runtime must not insert an undocumented previous-tick delay.
Reject conflicting ordinary bindings and dependency cycles that cannot be evaluated under documented semantics. Do not introduce an implicit previous-frame delay to make a cycle appear legal.
### 8.2 BindingSpec
### Required resolution examples
The canonical fields are `source`, `target`, `scale`, `offset`, `clamp`, `smoothing`, and `when`. Earlier planning-only spellings `from`, `to`, and `transform` are not 0.1 aliases and are rejected as `ERR_UNKNOWN_FIELD`.
```json
{
"source": "state.machine-load",
"target": "audio.buses.deep.gain",
"scale": 0.5,
"offset": 0.5,
"clamp": [0, 1],
"smoothing": "50ms",
"when": {
"op": "gt",
"left": { "ref": "parameters.activity" },
"right": 0
}
}
```
`source` and `target` are required reference paths. `scale` and `offset` are finite numbers with defaults `1` and `0`. `clamp`, when present, is a two-number inclusive `[minimum, maximum]` array with `minimum <= maximum`. `smoothing` is a DurationSpec and defaults to `"0ms"` (disabled). `when` is a ConditionSpec and defaults to `true`.
For numeric sources and targets, first calculate `clamp(source * scale + offset)`. A nonnumeric binding must omit `scale`, `offset`, `clamp`, and nonzero `smoothing`, and its source and target types must match exactly. A false `when` disables the binding for that tick and exposes the target's base stage. When a binding becomes enabled, its smoother initializes to the transformed source value; disabled time is not replayed.
Nonzero smoothing is the deterministic one-pole update below, evaluated once per fixed logical tick. `tau` is the authored smoothing duration in seconds, `dt` is the fixed logical step, `x` is the transformed and binding-clamped input, and `yPrevious` is the prior enabled tick's binding output:
```text
alpha = 1 - exp(-dt / tau)
y = yPrevious + alpha * (x - yPrevious)
```
At most one ordinary binding may target a scalar property. Multiple writers are `ERR_CONFLICTING_BINDING` even when their `when` conditions appear mutually exclusive; conditional exclusivity is not a precedence mechanism. A binding to a missing or read-only target is `ERR_UNSUPPORTED_TARGET`.
### 8.3 Override action and precedence
An override action has required `type: "override"`, `target`, `value`, and `scope` fields. It may also contain common action fields plus `priority`, `duration`, and `transition`.
| Field | Contract |
| --- | --- |
| `target` | A target whose capability row permits override. |
| `value` | ValueSpec sampled once when the override instance activates; result must match the target type. |
| `scope` | `"scenario"` or `"duration"`. |
| `priority` | Optional integer from `-1000` through `1000`. If omitted under a scenario, inherit the scenario instance priority; otherwise use `0`. |
| `duration` | Required and greater than `0ms` for duration scope; forbidden for scenario scope. Time begins at activation, including while masked. |
| `transition` | Optional OverrideTransitionSpec; defaults to zero-duration `in` and `out` with `linear` easing. |
Each activated override receives a monotonically increasing runtime `activationSequence` within the performance and a runtime ID `instances.override-<activationSequence>`. An author `id`, when supplied, identifies the action definition and does not replace the runtime ID. The live winner is the greatest tuple `(priority, activationSequence)`. Scope affects lifetime only: duration scope receives no precedence advantage over scenario scope.
Underlying binding and automation stages continue to evaluate while masked. Overrides also advance their own durations and envelopes while masked. When a winner changes, the newly winning override's attack begins from the target's current pre-modulation resolved value, preventing a discontinuity. If that override had already completed its attack while masked, it takes effect immediately at its sampled value.
### 8.4 TransitionSpec and interruption
A numeric `set` action accepts `{ "duration": DurationSpec, "easing": Easing }`, with defaults `"0ms"` and `"linear"`. An override accepts `{ "in": DurationSpec, "out": DurationSpec, "easing": Easing }`, with both durations defaulting to `"0ms"`. Easing is one of `linear`, `ease-in`, `ease-out`, or `ease-in-out`, defined for normalized `t` in `[0,1]`:
```text
linear: t
ease-in: t * t
ease-out: 1 - (1 - t) * (1 - t)
ease-in-out: 2 * t * t when t < 0.5
1 - ((-2 * t + 2)^2) / 2 otherwise
```
Zero duration is an immediate step. Non-numeric targets permit only zero-duration transitions; any nonzero duration is `ERR_INVALID_TRANSITION`. Numeric interpolation uses double precision. Integer targets round half away from zero after interpolation and then apply their declared clamp.
An attack interpolates from the pre-modulation value visible immediately before the override becomes winner to its sampled override value. Release begins when duration expires, its scenario owner terminates, or the override is explicitly removed. Release output on each tick is:
```text
lerp(currentLowerValue, releaseStartValue, 1 - easing(elapsed / outDuration))
```
`currentLowerValue` is recomputed on every tick without the releasing override, so stored user edits, bindings, and automation remain observable during release. `releaseStartValue` is the override's actual pre-modulation output when release began. A zero-duration release removes the override before that tick resolves.
A higher-priority interruption does not cancel a lower override. The lower override continues its lifetime and release while masked. If the higher override releases, the next live winner is selected and its current envelope value is used. Reactivating the same action creates a distinct override instance with a new activation sequence.
The safety clamp always runs after modulation. A constant override or binding value outside the target's legal range is `ERR_OUT_OF_BOUNDS` at validation; a dynamic result is clamped at runtime and emits a rate-limited `ERR_OUT_OF_BOUNDS` diagnostic rather than poisoning the graph.
### 8.5 Required resolution traces
The normative deterministic traces are stored with the GC3 tests. They cover:
| Case | Expected behavior |
| --- | --- |
| Bus gain is bound to activity, then directly overridden | The override supplies the pre-modulation value until release; the binding continues underneath |
| Activity is overridden and referenced by a binding | The binding observes resolved activity |
| User edits stored activity while its override is active | The stored edit persists; release approaches the updated lower value |
| Two overrides compete | Higher priority wins; equal priority uses activation order |
| Duration and scenario overrides compete | Priority and activation order decide, not scope |
| A target has legal additive modulation | Modulation follows the winning override, then the safety clamp applies |
| Bus gain is bound to activity, then directly overridden | Override supplies the pre-modulation value; the binding continues underneath and its current value becomes the release destination |
| Activity is overridden and referenced by a binding | The binding observes resolved activity during the same logical tick |
| User edits stored activity while its override is active | Stored edit persists; release approaches the updated lower value |
| Two overrides compete | Higher priority wins; equal priority uses greater activation sequence |
| Duration and scenario overrides compete | Priority and activation sequence decide, not scope |
| A target has legal additive modulation | Modulation follows the winning override; safety clamp runs last |
| Binding conditions, smoothing, clamps, and invalid graphs | Disabled binding exposes base, re-enable initializes smoothing, and conflicts/cycles are rejected |
## 9. Time and random evaluation
## 9. Time, audio alignment, and random evaluation
The initial logical simulation step is 1/60 second. Rendering does not own simulation time. Audio scheduling maps logical time to the audio clock with a bounded horizon.
### 9.1 Fixed logical clock
Application pause and document visibility loss suspend logical progression and audio. Resume continues the same logical performance without a wall-clock catch-up burst. Visibility resume does not clear a user pause. Audio unlock does not replay expired sound invocations.
The 0.1 logical step is exactly `1000 / 60` milliseconds. Runtime time is represented by an integer `tickIndex`; logical seconds are `tickIndex / 60`. Rendering reads the most recently published state and never advances simulation time.
Random and weighted-choice ValueSpecs are sampled at the containing object's documented instantiation or invocation boundary, not every render frame. Evolving randomness belongs to modulators. Nested ValueSpec evaluation boundaries and random time sampling must be specified per construct.
The foreground driver accumulates nonnegative elapsed monotonic time. One browser turn executes at most eight logical ticks. A single driver observation contributes at most `250ms`; excess elapsed time is discarded and emits one rate-limited `WARN_CLOCK_STALL` diagnostic. After eight ticks, the driver yields and retains at most one step of accumulator; additional accumulated time is discarded with the same diagnostic. These bounds prevent catch-up bursts. They are runtime responsiveness policy, not permission for accelerated tests to skip authored ticks.
Separate random streams isolate cadence, scenario instances, visual systems, sound instances, and manual sampling. Reproducibility is scoped to a runtime version, recorded numeric seed, and logical input sequence. Tests that use external or analysed signals must supply deterministic input traces.
Each logical tick uses this order:
## 10. Ownership and termination
1. Increment `tickIndex` and latch queued user/external inputs in arrival-sequence order. Tests replace external inputs, including audio analysis, with deterministic fixtures.
2. Advance time-based modulators and automation; resolve the dependency graph in topological order.
3. Evaluate cadence, event conditions, and scenario timelines once against that tick's resolved snapshot; enqueue work using document order as the final tie-break.
4. Drain ordinary event/action work up to the GC5 dispatch budget. Actions in one array execute in document order; a state mutation invalidates affected resolved values before the next action executes.
5. Advance owned audio/visual instance state and perform mandatory cleanup, which is not charged to the ordinary dispatch budget.
6. Schedule eligible audio work within the lookahead horizon, then publish the immutable state read by rendering and generated UI.
Ownership propagates through nested events and actions. Scenario resources inherit the scenario owner unless the resource type allows an explicit persistent owner. Scenario-created duration overrides cannot outlive the owner, apart from their bounded release cleanup.
Application pause and document visibility are independent pause reasons. Logical progression and audio are paused while either reason is present. On any transition from paused to running, reset the monotonic wall-time anchor while preserving `tickIndex` and the sub-step accumulator. Do not add paused wall time or replay missed work. Removing the visibility reason does not remove an explicit application pause.
Persistent `set` changes survive scenario failure. Termination cancels future work and guarantees cleanup, including when a termination hook fails. Release work transfers to a bounded cleanup owner and ultimately disposes all temporary resources.
### 9.2 Audio clock mapping and unlock
Condition triggers require a false condition before rearming after a successful firing. Each scenario definition may hold at most one deferred start request. Requests expire and recheck eligibility when dispatched. Exact timeout and ordering contracts remain required.
The initial audio scheduling lookahead is `100ms`, refilled after every logical tick. Maintain an anchor pair `(logicalSeconds, audioContext.currentTime)` and map an eligible logical start to the audio clock relative to that pair. On audio unlock or resume, establish a fresh anchor at the current logical position. Engine-owned future starts from an obsolete anchor are cancelled and rescheduled where the Web Audio node contract permits it.
Statically check event/scenario feedback where possible and bound runtime dispatch. Resource cleanup must remain possible after the ordinary dispatch budget is exhausted.
If audio is locked, logical sound invocations still receive their deterministic IDs, ownership, sampled values, and nominal logical intervals, but create no Web Audio nodes. At unlock:
- a one-shot whose logical start is earlier than the unlock position is skipped, whether expired or partially elapsed; emit one rate-limited `INFO_AUDIO_UNLOCK_SKIP` count rather than replaying it;
- a continuous sound that is still logically owned starts at its current logical phase/state, not from its original attack, and applies an engine-owned `20ms` anti-click ramp from silence to its current gain;
- future invocations inside the new lookahead are scheduled normally.
Pause or visibility loss suspends the AudioContext after cancelling engine-owned future starts. Resume re-anchors and refills the horizon. Accelerated logical tests validate scheduling decisions only; audible behavior, anti-click ramps, and real-time soak acceptance require browser/audio observations.
### 9.3 Seed normalization and PRNG
An authored numeric seed must be an integer in `[0, 4294967295]` and is normalized with unsigned 32-bit semantics. For `"random"`, obtain one unsigned 32-bit integer from `crypto.getRandomValues`, store it with the performance record, and use that recorded integer for every subsequent derivation. Failure to obtain entropy is a startup error; time or `Math.random()` is not a fallback.
XZBT 0.1 uses `xoshiro128**` with unsigned 32-bit arithmetic. A stream seed is FNV-1a-32 over the UTF-8 bytes of:
```text
xzbt-0.1\0<root-seed-decimal>\0<domain>\0<stable-instance-key>
```
Expand that hash into four words with SplitMix32. If all four words are zero, set the fourth word to `1`. Each sample converts the next unsigned result to `[0,1)` by division by `4294967296`. Integer ranges use unbiased rejection sampling; weighted choices consume one sample and select by cumulative positive weight in document order.
The reserved domains are `cadence`, `scenario`, `visual`, `sound`, and `manual-sample`. Stable instance keys use the declared definition ID plus that definition's monotonically increasing invocation ordinal within its domain. A subsystem may add a documented child key but may not draw from another domain. Rendering consumes no procedural stream. Manual SAMPLE always uses `manual-sample`; therefore it cannot perturb cadence, scenario, visual, or automatic sound choices.
Random and weighted-choice ValueSpecs are sampled once at the containing object's documented instantiation or invocation boundary. Nested random ValueSpecs use the same owning stream in depth-first, property-document order. Evolving randomness belongs to modulators and consumes only the owning modulator/visual stream at logical ticks.
Reproducibility means identical procedural decisions for the same XZBT runtime version, normalized seed, stable exhibit definition, and logical input sequence. It does not promise identical pixels, floating-point audio samples, or browser timing across devices.
## 10. Ownership, failure, and bounded dispatch
### 10.1 Ownership propagation
Every action dispatch carries an ownership context: a scenario instance owner or the performance root. An event action passes the same context to the invoked event; nesting never resets ownership. Sound, spawn, subscription, and override resources inherit that context unless their resource contract permits `ownership: "persistent"` and the action explicitly requests it. Persistent resources transfer to the performance root. Unsupported persistent ownership is `ERR_UNSUPPORTED_TARGET`.
A scenario instance owns its future timeline records, repeats, relative actions, subscriptions, scenario-scope overrides, duration overrides it creates, and nonpersistent continuous audio/visual resources, including resources created indirectly through nested events. A duration override owned by a scenario begins release at its authored deadline or owner termination, whichever occurs first.
`set` changes are persistent mutations, not owned resources. A successfully executed `set` is never rolled back because a later action, hook, or scenario fails.
### 10.2 Hook order and allowed termination work
`onStart` runs after the instance owner and deterministic streams exist but before timeline activation. It may use the full Action Model. A critical `onStart` failure cancels remaining start actions, prevents timeline activation, and terminates the instance as `FAILED`.
Termination first blocks new ordinary dispatch for the owner and cancels its future timeline/repeat/relative work. Exactly one termination hook then runs:
| Cause | Hook | Terminal state after cleanup |
| --- | --- | --- |
| Natural duration/timeline completion | `onComplete` | `COMPLETED` |
| Manual cancel, replacement, exhibit deactivation | `onCancel` | `CANCELLED` |
| Critical startup, ordinary action, or nested-event failure | `onCancel` | `FAILED` |
Termination hooks execute their actions in document order and may contain only `set` and one-shot `sound` actions. Termination sounds are owned by the cleanup owner and may not request persistent ownership. `override`, `event`, `spawn`, `remove`, and `control` actions in a termination hook are validation errors. A hook action failure is diagnosed and remaining hook actions continue regardless of `critical`; it cannot suppress cleanup or replace the original terminal cause.
After the hook attempt, detach subscriptions, release overrides and continuous audio, remove visual systems, clear scheduler records, and release runtime references. Release work transfers to an engine cleanup owner. Each release uses the shorter of its authored release and `5s`; the cleanup owner has an absolute deadline of five seconds of advancing logical time, after which remaining resources are force-disposed with `WARN_CLEANUP_FORCED`. Destroying or replacing the entire performance force-disposes immediately after its deactivation hook; it does not leave a detached timer.
### 10.3 Condition trigger state
A condition trigger has `disarmed`, `holding`, and `armed` states. A false evaluation clears its hold timer and arms it. Once armed, continuous true evaluations accumulate logical ticks; it fires once when the declared `for` interval is met, then becomes disarmed. It cannot fire again until at least one later logical tick evaluates false. Startup with a true condition begins disarmed, preventing an unobserved pre-launch edge from firing.
### 10.4 Deferred starts
Each scenario definition may have at most one deferred request. Its creation tick and sampled trigger inputs are retained. A later opportunity for the same definition neither adds a request nor extends expiry.
The expiry is `eligibility.timeout` when present, otherwise `5m`, measured in advancing logical time. At each tick, remove expired requests first. Then consider the remaining queue in descending scenario priority, ascending creation tick, and scenario document order. Immediately before dispatch, recheck that the definition is enabled, its eligibility is true, cooldown permits it, and its concurrency conflict has cleared. A failed recheck leaves the request pending until a later tick or expiry; it does not resample the original trigger.
### 10.5 Dispatch budget and feedback
Static validation rejects direct and indirect event invocation cycles and detectable event/scenario trigger feedback cycles as `ERR_CYCLIC_DEPENDENCY`. Runtime uses a second backstop because data-dependent feedback may remain.
One logical tick permits at most `1024` ordinary dispatch units. Entering an event consumes one unit and each attempted action consumes one unit, including skipped `when`/`chance` actions. When the next unit would exceed the budget, emit `ERR_DISPATCH_BUDGET`, discard the remaining ordinary queue for that tick, and terminate each scenario owner represented by discarded or currently executing feedback work as `FAILED`. Unowned work is dropped. Mandatory termination hooks and cleanup are budget-exempt and use a separate hard limit of `256` termination-hook actions per owner; exceeding it truncates the hook and continues cleanup.
The maximum nested event depth remains 16 and is checked before entering the next event. Budget and depth failures are critical runtime failures for an owned chain. Diagnostics include tick, owner, event/action path, consumed units, and discarded queue count.
## 11. Contract completion register
@@ -333,14 +496,14 @@ Complete shared contracts before implementing dependent subsystems. Use PRD sect
| --- | --- | --- | --- |
| Document/schema | 9-14, 113-116, 121 | Top-level shapes, unknown-field policy, identifier regex, reference paths, standard diagnostic codes | **Complete (Rev 0.2)** |
| Values and conditions | 15-21, 33 | Operator arity/table, division-by-zero protection, sampling timing boundaries, edge-trigger re-arming | **Complete (Rev 0.2)** |
| References and bindings | 13, 17, 31-32 | Target-capability table, instance/input scope, evaluation order, cycles, disabled bindings, exact smoothing | Planned (Phase 0 / GC3) |
| Actions and transitions | 22-30 | Fields and defaults per action, override priorities outside scenarios, target/command matrix, interrupted transitions, instance IDs | Planned (Phase 0 / GC3) |
| References and bindings | 13, 17, 31-32 | Target-capability table, instance/input scope, evaluation order, cycles, disabled bindings, exact smoothing | **Complete (Rev 0.3 / GC3)** |
| Actions and transitions | 22-30 | Shared `set`/`override` fields and defaults, override target matrix, interrupted transitions, instance IDs; subsystem action matrices remain with their subsystems | **Shared contract complete (Rev 0.3 / GC3)** |
| Audio | 34-60 | Complete recipe/component shapes, node defaults, automation timing, clamp implementation, one-shot endings and tails, unlock behavior, master protection contract | Subsystem contract (Phase 3) |
| Cadence | 61-68 | Exact selection/cooldown/overlap fields, clocks, pool collisions, pending audio, manual sampling isolation and continuous-sample termination | Subsystem contract (Phase 5) |
| Visuals | 69-89 | Primitive/system/behavior fields, angle and motion units, transform order, depth projection, field behavior, morph compatibility, effect approximation and limits | Subsystem contract (Phase 4) |
| Events/scenarios | 90-102 | Trigger shapes, hooks and failure ordering, scope inheritance, deferred ordering/expiry, relative/repeated timeline semantics and termination boundaries | Subsystem contract (Phase 6) |
| Events/scenarios | 90-102 | Shared ownership, hooks/failure ordering, condition rearming, deferred ordering/expiry, dispatch limits; full trigger/timeline shapes remain for Phase 6 | **Shared lifecycle contract complete (Rev 0.3 / GC5)** |
| Generated UI | 103-107 | Widget compatibility, button actions, parameter validation and override display, group/control ordering | Subsystem contract (Phase 7) |
| Runtime/library | 108-112, 117-127 | Clock/audio synchronization and stalls, import equality, update compatibility, transactions, failure recovery, persistence schema | Subsystem contract (Phase 1/8) |
| Runtime/library | 108-112, 117-127 | Clock/audio synchronization, stalls, seed/stream derivation complete in GC4; import equality, update compatibility, transactions, failure recovery, persistence schema remain | **Shared clock/PRNG contract complete (Rev 0.3 / GC4); subsystem remainder Phase 1/8** |
## 12. Contract template and conformance artifacts
@@ -348,4 +511,33 @@ Each construct must record its JSON shape; required and optional fields; types,
Supply a valid minimal example, a meaningful composition example, invalid cases with expected diagnostics, and expected semantic traces where timing or ordering matters. Two contrasting complete exhibits must exercise parameters, sound, visuals, and a temporary scenario override early in development.
## 13. Library identity, update compatibility, and build boundary
### 13.1 Import identity
After successful UTF-8 decoding, remove one leading byte-order mark if present. The resulting source bytes are the import payload. Compute SHA-256 over those bytes; equality requires both the same `meta.id` and the same digest. An identical import is a no-op and does not change cached timestamps, source associations, selected exhibit, or parameter values.
The same ID with a different digest is a replacement candidate even if parsed values appear equivalent. Whitespace, property-order, or numeric-spelling changes may therefore require confirmation; this conservative rule preserves document-order semantics and exact provenance. A changed candidate is parsed and fully validated before the user is offered explicit replace/cancel choices. Invalid candidates never replace the cached valid definition.
### 13.2 Parameter reconciliation
After an explicit replacement choice, reconcile stored parameters by parameter ID:
| Change | Result |
| --- | --- |
| Same type and still valid | Preserve stored value. |
| `number`/`integer` bounds changed | Clamp to the new inclusive range and show a notice; integer values are rounded half away from zero before clamping. |
| Enum values changed | Preserve only if the stored token remains declared; otherwise use the new default and show a notice. |
| Type changed, including `number` to `integer` | Use the new default and show an incompatibility notice. |
| Parameter removed | Delete its stored value after successful activation. |
| Parameter added | Initialize from the new default. |
Do not mutate the old cached definition or settings while validating or preparing a candidate. Preparation may allocate parsed data and inert compiled plans but must not start timers, render visible output, create audible nodes, or persist replacement state.
On successful candidate activation, atomically commit the new definition, reconciled settings, digest, and source metadata, then dispose the old performance. If activation fails after the old performance has stopped, dispose the candidate and attempt a fresh activation of the old cached definition with its unchanged saved settings. Report both failures if recovery fails; never represent the failed candidate as active.
### 13.3 Reproducible standalone build
Development uses separate source modules. The build must have pinned tool versions and inputs, stable module/asset order, no wall-clock timestamps or absolute paths in output, and a documented single command that produces `XZBT.html`. Two clean builds from the same revision must have identical SHA-256 digests. The artifact must contain all runtime code/assets/fonts and pass direct-file offline verification with no external runtime requests.
Structural JSON Schema does not replace semantic validation. The internal schema and fixtures belong to 0.1 implementation work; public schema distribution and editor integration may follow later.