Skip to content

RUNE-1002: Story Layout and Arrange Proposed

Updated2026-08-23

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.

yaml
- row@titleRow: [icon, title]
  gap: 8
  bounds: 0, 0, 640, 72
  hAlign: center
  vAlign: bottom

Solvers 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: row and column currently honor gap only. hAlign, vAlign, and bounds are accepted by the schema but are not yet applied; items still stack from 0 on 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.

yaml
- 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:

yaml
- 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.

yaml
- arrange@enterTitle: $titleRow
  with: $smooth

The 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 morph transfers 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.

Document provenance

Author
@devkanro
Created
2026-05-07
Audited at
2026-08-24 @ ce1dbcb4b5e0161eab69faeb97507a50808b69eb
Dependencies
RUNE-1000, RUNE-1001

Rune Project brings Rune Story authoring together with the Rune Engine rendering foundation.