RUNE-1002: Story Layout and Arrange Proposed
Summary
Story layout is split into calculation and application. Layout solver actions create named layout results containing descriptor-backed target/value pairs; arrange applies those results immediately or with animation. Results now retain stable logical Sprite identities.
Layout Solvers
layout is not a user action. Concrete solver actions include row, column, stack, grid, align, and flex. Each concrete action owns its solver logic and registers a StoryLayoutResult; StoryScene stores results without switching on a fixed layout kind enum.
- row@titleRow: [icon, title]
gap: 8
bounds: 0, 0, 640, 72
hAlign: center
vAlign: bottomSolvers MUST NOT mutate scene sprites. They produce layout.* results. The item list is the solver Primary. gap, bounds, hAlign, and vAlign are Field Initializers. bounds is Rect? and accepts scalar x, y, width, height, sequence [x, y, width, height], or mapping { x, y, width, height }; hAlign is backed by StoryHorizontalAlignment, and vAlign by StoryVerticalAlignment.
Current limitation:
rowandcolumncurrently honorgaponly.hAlign,vAlign, andboundsare accepted by the schema but are not yet applied; items still stack from0on the layout axis and keep their cross-axis position.
Each StoryLayoutResult entry MUST capture the participant's stable StorySpriteIdentity, root composition space, property name, exact descriptor type, and computed value. Values MUST NOT be recomputed by arrange.
Composition-Space Rule
All root participants in align, row, or column MUST belong to one composition space. A mixed world/screen solver request MUST fail. The result MUST record the shared space, and arrange MUST reject any live target that no longer belongs to it.
World-only layouts remain coherent under camera/world transforms, while screen-only layouts remain coherent under identity screen composition. Cross-space exact-type property bindings remain valid model relationships, but layout MUST NOT project transformed world coordinates into screen space. The same-space rule does not redefine existing nested-parent transform behavior within one space.
Align
align places one sprite relative to another sprite. Broader screen, layout, and anchor targets are deferred until the solver has concrete runtime semantics for them.
- align@badgeLayout: $badge
subjectAt: center
to: $card
targetAt: center
offset: [-8, 8]The subject is align Primary. The target remains the explicit to Field Initializer so the Action reads as "align $badge to $card".
For edge alignment, placement controls whether the subject sits inside, outside, or overlapping the target edge:
- align@tooltipLayout: $tooltip
subjectAt: top
to: $card
targetAt: bottom
placement: outside
offset: [0, 8]subjectAt and targetAt are backed by BoundsAnchor; placement is backed by AlignPlacement and MAY be inside, outside, or overlap. overlap is the default.
Arrange
arrange resolves each stored logical identity to its current Sprite implementation and applies the stored assignments.
- arrange@enterTitle: $titleRow
with: $smoothThe layout result name is arrange Primary. Without with, arrange applies immediately. With with, it creates animation work if named.
Before any write, arrange MUST preflight every live target for the stored root space, property name, exact property type, and value compatibility. An incompatibility MUST fail without partially arranging other entries.
- Successful
morphtransfers the logical identity, so later arrange targets the replacement. - Permanent destroy terminates the identity, so arrange skips that entry.
- Destroy followed by same-name create produces a new identity and MUST NOT receive a stale entry.
- Morph rollback transfers the identity back to the original.
Ordering Boundary
reorder is separate from layout calculation. Subjects and a non-null anchor MUST share one parent. A null anchor moves subjects to first/last within their own parent. Root reorder therefore MUST NOT cross world/screen, and structural screen-over-world ordering cannot be changed.
Nested Result Trees
Arbitrary nested path arrangement is deferred. If part of a composition needs separate arrangement, it should be emitted as a separate named layout result.
v1 Scope
Scene-level solvers consume existing scene sprites only. Inline virtual sprites are deferred because they overlap with create and sprite reuse design.
Rationale
Separating solver output from arrange lets authors calculate a composition once, then choose when and how participating sprites move into it. Stable logical identities preserve useful snapshots across morph without confusing same-name recreation, while full preflight keeps application atomic.