RUNE-1001: Story Actions and Execution Proposed
Summary
Story scripts are ordered Sibling Object Action invocations. Descriptors bind Primary and Initializer values into strongly typed objects or dynamic Composite Action frames. Execution uses lexical scopes, child timelines, scoped work collection, aggregate completion, and transactional property bindings. Root execution additionally owns world/screen composition and persistent camera/world property objects.
Sibling Object Contract
- type@name: primaryValue
field: valueDescriptors expose:
StoryObjectDescriptor
Type
Primary?
Fields[]
MaterializerBinding MUST:
- Split the Object header into Type ID and optional object name.
- Resolve the descriptor in the expected domain.
- Convert Primary and Initializers before runtime mutation.
- Apply Primary first, then Initializers in source order.
- Allow an Initializer for the Primary Field to override Primary.
- Preserve effective Field values and source spans.
- Materialize and assign the object/result name only after successful preflight.
Mapping Primary values MUST NOT be implicitly expanded. Semantic duplicate Initializers MUST fail before materialization. Global Type IDs MUST use /; dot MUST remain object/member access.
Generated CLR descriptors may map Primary to one constructor input. Dynamic Composite and Action descriptors map Primary to their first declared Field.
Every generated/dynamic Field and Primary MUST carry one exact ordinal StoryTypeId; projected Primaries inherit the Field ID. Generated common CLR properties infer compatibility IDs (double → number, string → string), while duration and font-family semantics require explicit metadata. The compile-time StoryValueTypeRegistry, not CLR assignability, is authoritative for conversion.
Dynamic Action Definitions
members:
- action@flip:
- field@to: string
required: true
- field@duration: duration
default: 0.083167s
script:
- set@turn:
front.rotationX: 180
with:
tween: $durationAn Action definition's Primary is its ordered Field declaration list; script is an Initializer. The first declared Action Field receives call Primary. Action Fields are immutable for one invocation and MUST reject animatable.
Calls are ordinary Sibling Objects:
- cell.flip@done: B
duration: 0.1s
- cell.flip@done:
to: B
duration: 0.1s
- cell.reset:self.action invokes an Action on the current Composite. child.action invokes an Action on a private nested Composite. Definition compilation MUST reject direct and indirect call cycles. Each invocation MUST materialize a fresh Action graph and immutable Field frame.
Action-created private Sprites MUST join the owning Composite's shared private composition and persist until explicitly destroyed or the Composite is permanently removed.
Value Evaluation and Binding
| Syntax | Meaning |
|---|---|
| bare value | Expected-type literal |
| bare identity/path | Expected-type literal |
| bare wait identifier | Zero-to-many completion selector |
$path | Snapshot of current model target value or one named completion facet in a wait slot |
=path | Persistent one-way property relation |
!bind $path | Explicit tagged form of a direct $path binding |
$ MUST NOT read sampled presentation state. Quoted prefixes MUST remain literal strings. Named Sprite/Action references in identity slots use $path; bare identifiers are reserved for literal values and wait selectors.
Design token aliases use alias@name: $source for one exact facet or alias@name: !semanticType $source when the source symbol has multiple facets. A declaration such as brush@name: $color resolves the source and runs the target constructor's Primary/converter; it is not an alias. VoiceRef/AnimationRef values selected by these references are frozen during compilation.
Ordinary token/style reads MUST request the exact target Field/Primary StoryTypeId; symbolic style references remain unresolved until that target is known. Same-CLR semantic facets MUST NOT cross-convert implicitly. Explicit typed construction is the only cross-semantic path and MUST use the caller's StoryCompileContext, descriptor validation, and immutable freeze policy.
Binding endpoints MUST match both exact CLR types and ordinal StoryTypeIds. Runtime endpoint metadata MUST retain the descriptor ID and MUST NOT maintain an authoritative second CLR-to-semantic map. Installation MUST reject cycles and preflight the complete downstream DAG before mutation. Synchronization MUST occur in stable topological order. Failure MUST restore values and the prior incoming edge.
A literal or $ write MUST remove only the direct target's incoming edge, retain outgoing edges, and propagate the new value. Installing another = MUST replace the incoming edge. Endpoint removal MUST dispose incident edges; former targets retain their last model values.
An Action Field initialized from =persistent.property MUST retain an immutable endpoint alias. $field evaluates that endpoint and =field forwards it. Snapshot-only Action Fields MUST NOT be binding sources.
Assignment Animation Intent
with is an action-owned Field that supplies one PropertyAnimationIntent for the complete assignment synchronization. Omission means immediate assignment, except motion, which resolves the built-in $default animation facet. Named animation facets are compile-time AnimationRef values; no runtime animation registry is consulted.
- set@fade:
source.opacity: 1
with: $smoothFor A -> B -> C, assignment to A MUST forward the same resolved intent to B and C. Each endpoint independently decides whether it can animate; non-animatable endpoints update immediately. Bindings MUST NOT retain an old animation curve. Later source writes forward the later assignment's intent.
The assignment invocation MUST aggregate timing across every finite animated participant, including nested private targets:
settleis the maximum perceptual duration;restis the maximum precise duration;- property timing diagnostics include each animated endpoint.
Named completion and selector-driven tween timing MUST resolve only already-visible sources in the lexical completion scope. Empty selector matches and zero remaining duration become immediate assignment. Token-defined tweens MUST use a literal or compile-time duration token; runtime completion/selector timing in a token is a diagnostic.
Built-in Camera and World Property Objects
Root scope MUST expose camera and world through the ordinary property assignment runtime:
| Target | Type | Default | Semantics |
|---|---|---|---|
camera.x, camera.y | Dimension | 50%, 50% | World point displayed at canvas center |
camera.zoom | number | 1 | Finite positive uniform view zoom |
camera.rotation | number | 0 | Finite degrees, applied inversely to world presentation |
world.x, world.y | Dimension | 0px, 0px | Direct logical-canvas translation |
world.scale | number | 1 | Finite positive uniform direct scale |
world.rotation | number | 0 | Finite direct clockwise degrees |
world.origin | TransformOrigin | 50%,50% | Direct scale/rotation pivot |
X percentages MUST resolve against logical-canvas width and Y percentages against height. TransformOrigin components MUST both be percentages or both pixels. world.origin percentages always reference the fixed logical canvas, never world content bounds.
- set@push:
camera.x: 1280
camera.y: 540
camera.zoom: 1.5
world.rotation: 8
world.origin: 50%,50%
with: $smoothThe built-ins MUST support ordinary set, $ model reads, exact-type = bindings, animation intent, named completion, and settle/rest. They MUST NOT resolve as SpriteRef; Sprite-only actions including destroy, morph, motion, data, playback, layout subjects, and reorder MUST reject them.
Rotation MUST use ordinary numeric-degree interpolation. Values MUST NOT be normalized and no shortest-angle path is implied. Animated origin MUST resolve both current and target origins to logical-canvas pixel X/Y channels before interpolation. Its completion metadata MUST use the longer channel timing. $world.origin MUST still return the last model value and units, and = MUST remain an exact TransformOrigin relationship.
Zoom/scale model targets MUST be finite and greater than zero. Presentation output MUST clamp spring/tween samples to a small positive internal epsilon before rendering, preventing a singular or inverted transform. The raw animation state and velocity MUST remain unchanged; retargeting MUST start from that raw sampled state, not the clamped output.
An exact-type binding such as camera.x: =hero.x copies later model assignments and their animation intent. V1 motion writes outside the property-binding runtime and MUST NOT drive that binding or expose its sampled path position. Bindings also MUST NOT project nested transforms, layout movement, or world presentation into screen coordinates.
Create
create accepts an optional with fade-in and a root-only optional space:
- create@cell:
./sprites/cell: A
with: $fade
- create@caption:
text: "Screen-fixed"
space: screen
- wait: $cellEvery named create MUST publish exactly one aggregate source, including built-in and synchronously created Sprites. Synchronous create MUST publish a pre-completed aggregate. Composite create MUST join recursive initialization timelines and fade-in work. Anonymous create MUST publish anonymous aggregate work when construction blocks.
The Sprite construction mapping is itself a Sibling Object. Its Primary and Initializers bind the selected Sprite descriptor. Composite semantic public Fields and inherited Sprite properties share the effective construction namespace. A style is expanded as canonical sibling Initializers before binding; its symbolic values resolve by each concrete target Field ID. Nested style Primary maps are invalid.
Root space MAY be world or screen and MUST default to world. Space is immutable lifetime metadata; nested children inherit it. Composite-private create MUST reject explicit space. camera and world are reserved root create names. Root names MUST be unique across both spaces.
Hierarchical Execution Scopes
One StoryBakeRuntime owns shared clock, rendering, diagnostics, media, snapshot, and cancellation services. Each StoryExecutionScope owns lexical identities and state:
Root scope
unified world/screen Sprite namespace
camera/world property objects
root track timelines
Composite scope
self
public Fields/Actions
private Sprites/layouts
binding graph
source origin
Action invocation scope
immutable Action FieldsPrivate Sprite/layout lookup MUST NOT fall back to the parent/root. Callers MUST NOT enter private identity namespaces. Nested Composite public Fields and Actions are visible only through the nested Composite identity.
Only root scope and root-created track timelines may resolve built-in camera/world property objects or explicit create.space. Composite initialization and Composite Action scopes MUST NOT. Ordinary exact-type model bindings MAY cross world/screen spaces; they do not perform coordinate projection.
Private Timelines and Scheduling
Composite initialization and Action bodies run on ordinary StoryTimeline instances with Composite-owned execution scopes. Registration order MUST be deterministic:
- Create registers initialization.
- Caller continues in the current drain pass.
- Registered child timelines execute on the next pass in registration order.
- Later Action timelines execute after earlier initialization registration.
No implicit initialization wait exists. If ordering is required, the caller waits on the create aggregate.
Scoped Work Collector
Every action body MUST execute with one scoped work collector:
| Mode | Completion behavior |
|---|---|
| Immediate | Adds no dependency |
| Blocking finite | Joins the invocation aggregate |
| Detached lifetime | Transfers to the owning execution scope |
After successful execution, a named invocation MUST expose at most one public aggregate. Anonymous blocking work MUST expose an anonymous aggregate for done. Failure MUST cancel collected work and fail bake.
Original phase/media tags MUST be projected into the caller scope as non-owning views. Projections MUST retain timing and tags, set ParticipatesInDone = false, avoid duplicate cancellation ownership, and never independently extend done.
Waits and Lexical Tags
- wait: $sourceName
- wait: done
- wait: settle
- wait: rest
- wait: speak
- wait: audio
- wait: video
- wait: 0.5s
- wait: 0wait: $name searches the current completion scope and ancestors. The source MUST already be registered; no forward promises are created. Bare selectors inspect the current lexical scope plus explicit child projections, not unrelated tracks or Composites.
done observes active owning sources only. Projected settle, rest, and media timing remain available without double ownership. Self-waits and cyclic timeline dependencies MUST fail.
Detached Ownership and Removal
Detached work MUST NOT join the invocation aggregate. Action schema/runtime selects detached mode; there is no generic authoring switch. A transient Action scope MUST transfer detached ownership to the nearest independent Composite/root scope.
Permanent Composite removal MUST cancel owned timelines and detached work, dispose lifetime handles, remove property endpoints/bindings, permanently remove private Sprites, and prevent late callbacks from mutating removed state. Render detach MUST remain distinct from permanent removal.
Compile, Bake, and Render
.story.yaml and .sprite.yaml syntax is parsed into source-aware immutable nodes. Compile resolves descriptors, local definitions, Field values, templates, and diagnostics without a render engine. Bake materializes Sprite instances and fresh Action graphs, executes timelines, and records action-boundary snapshots. Preview/export later seek snapshots and render requested times.
All waits and finite work are deterministic. The baker advances to event boundaries rather than encoding frames during bake.
Composite Limits
Phase 1 MUST reject CompositeSprite as a morph source or target, including a Layer subtree containing one. Iteration, standalone Action files, invocation-local automatic Sprite cleanup, events, slots, and independently playable nested stories are deferred.
Rationale
Strict Sibling Object binding gives generated and dynamic descriptors one vocabulary. Lexical scopes preserve reusable Sprite encapsulation; work aggregates provide one public completion contract; non-owning projections preserve phase timing; assignment-intent propagation keeps bound property graphs visually coherent.