- Give each Cast entry enough space to render muted relationship subtext - Preserve fixed Saga geometry with the slightly taller Cast rows - Add populated-Cast regression coverage for visible relationship labels
218 lines
10 KiB
Markdown
218 lines
10 KiB
Markdown
# Saga UX redesign plan
|
||
|
||
Goal: make the Saga surface a stable character browser for following the world's main
|
||
characters. Moving between glyphs must change the character, never the geometry.
|
||
|
||
Status: **Design approved in principle; implementation pending.**
|
||
|
||
Related documents:
|
||
|
||
- [Life Saga](life-saga.md)
|
||
- [Story system coherence](story-system-coherence-plan.md)
|
||
- [Story Planner](story-planner.md)
|
||
|
||
## Problem
|
||
|
||
The current hover dossier has the right information but its layout is still derived from
|
||
its contents. Cast width, legacy length, and portrait state can alter the panel height. Since
|
||
the card is bottom anchored, that makes the rail and nametag shift while the pointer moves
|
||
between glyphs. The result feels unstable even when every individual field is correct.
|
||
|
||
The surface also risks presenting two identities at once: the camera subject and the hovered
|
||
Saga character. During Saga preview, every identity-bearing element must belong to the Saga
|
||
character and camera-specific Beat and Context must disappear.
|
||
|
||
## Product contract
|
||
|
||
At a glance, Saga answers:
|
||
|
||
1. **Who is this main character?** — live name, role/species, sex, favorite state, and unique appearance.
|
||
2. **Who matters to them?** — a compact, prioritized Cast.
|
||
3. **What has defined their life?** — a short Legacy.
|
||
4. **What will interaction do?** — hover previews; the existing click action toggles Prefer.
|
||
|
||
Saga is a character browser. It does not restate the camera Beat or Story Planner Context.
|
||
|
||
## Fixed geometry
|
||
|
||
Use one fixed logical envelope for every Saga state:
|
||
|
||
| Region | Size | Contract |
|
||
|---|---:|---|
|
||
| Whole surface | `240 × 125` | Never content-sized |
|
||
| Glyph rail | `232 × 18` | Fixed position and hit targets |
|
||
| Gap | `232 × 2` | Reserved |
|
||
| Nametag | `232 × 15` | One line; fixed controls at right |
|
||
| Gap | `232 × 1` | Reserved |
|
||
| Body | `232 × 82` | Portrait, Cast, and Legacy stay inside |
|
||
| Outer padding | `4` horizontal, `3` vertical | Constant |
|
||
|
||
The body must never report a content-derived preferred height. Empty and full characters use
|
||
the same bounds. Supported UI scaling may scale the entire envelope uniformly but must not
|
||
reflow its internal regions.
|
||
|
||
```text
|
||
┌──────────────────────────────────────────────────────────┐
|
||
│ [glyph] [glyph] [glyph] [glyph] ... │ rail
|
||
├──────────────────────────────────────────────────────────┤
|
||
│ sprite Name · Role sex favorite │ nametag
|
||
├──────────────────────────────────────────────────────────┤
|
||
│ ┌──────────────┐ Cast │
|
||
│ │ │ [face Name] [face Name] [face Name] │
|
||
│ │ live unique │ [face Name] [face Name] [face +N] │
|
||
│ │ portrait │ │
|
||
│ └──────────────┘ Legacy │
|
||
│ • complete, bounded legacy line │
|
||
│ • complete, bounded legacy line │
|
||
│ • complete, bounded legacy line │
|
||
└──────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
## Portrait
|
||
|
||
- Reserve a fixed `40 × 40` vanilla stone frame at the upper-left of the body.
|
||
- A living character uses their current animated sprite so unique equipment and appearance remain visible.
|
||
- A fallen or unavailable character uses the last captured character sprite, then species art only as a fallback.
|
||
- The frame and surrounding regions never move when the sprite source changes.
|
||
- Vanity/decorative framing is disabled; the portrait identifies the character rather than decorating the card.
|
||
|
||
## Cast
|
||
|
||
Cast owns a fixed region to the right of the portrait.
|
||
|
||
- Maximum six visible entries in a `3 × 2` grid.
|
||
- Priority: partner, child/parent, close friend, rival, then other known relationships.
|
||
- Each entry has a fixed icon cell and a single text line.
|
||
- If more than six entries qualify, the sixth cell becomes `+N` rather than creating a third row.
|
||
- Long labels ellipsize inside their cell; they never shrink the panel or overlap adjacent cells.
|
||
- Empty state: `No close ties recorded` in the same region.
|
||
|
||
## Legacy
|
||
|
||
Legacy owns the full-width region below the portrait and Cast.
|
||
|
||
- Maximum two rendered lines, allocated by durable narrative importance.
|
||
- Prefer complete short statements. If a statement cannot fit its assigned line, ellipsize it.
|
||
- Never add another row or increase the body height.
|
||
- Do not repeat identity, species, role, relationship labels already visible in the nametag or Cast.
|
||
- Empty state: `Their story is still unfolding`.
|
||
|
||
## Rail portraits
|
||
|
||
The rail is a visual index of individuals, not species.
|
||
|
||
- Cache by `UnitId`, using the latest successfully captured inspect sprite.
|
||
- Capture on roster admission and refresh while watched or previewed at a bounded cadence.
|
||
- Retain the last valid unique sprite after focus changes or death.
|
||
- Never replace a valid character sprite with generic species art.
|
||
- Use species art only until no unique capture has ever succeeded.
|
||
- Remove cached entries only when the character leaves the roster permanently.
|
||
|
||
Two characters of the same species must remain visually distinguishable whenever the game has
|
||
provided distinct character sprites.
|
||
|
||
## Interaction states
|
||
|
||
### Camera dossier
|
||
|
||
Outside Saga hover, the normal dossier owns the nametag, Beat, Context, and live camera portrait.
|
||
|
||
### Saga preview
|
||
|
||
While a glyph is hovered:
|
||
|
||
- Acquire the existing camera read-pause lease.
|
||
- Rebind the primary nametag, species/role, sex, favorite, portrait, Cast, and Legacy to the hovered character.
|
||
- Hide camera Beat, Context, and camera-specific dossier rows.
|
||
- Keep the fixed Saga envelope and all controls in exactly the same coordinates.
|
||
- Restore the latest camera dossier atomically on pointer exit.
|
||
|
||
Rapidly scrubbing across glyphs must not briefly restore the camera identity between two Saga identities.
|
||
|
||
### Click behavior
|
||
|
||
Phase one keeps the existing click-to-toggle-Prefer behavior. A persistent pinned preview would
|
||
conflict with that undiscoverable action, so pinning is deferred until Prefer has its own explicit
|
||
control. Status banners cancel preview and retain priority.
|
||
|
||
## Ownership
|
||
|
||
| Element | Camera dossier | Saga preview |
|
||
|---|---|---|
|
||
| Glyph rail | `LifeSagaRail` | `LifeSagaRail` |
|
||
| Nametag and controls | Camera subject | Hovered Saga character |
|
||
| Large portrait | Camera subject | Hovered Saga character |
|
||
| Beat / Context | Camera story | Hidden |
|
||
| Cast / Legacy | Hidden | Hovered Saga character |
|
||
| Camera focus | `InterestDirector` | Unchanged and paused for reading |
|
||
|
||
## Layout invariants
|
||
|
||
1. The glyph rail has the same screen coordinates for every roster member.
|
||
2. The outer card rectangle has the same screen coordinates and dimensions for empty, short, and full content.
|
||
3. The nametag control cluster never moves with name length.
|
||
4. The large portrait frame never moves or resizes.
|
||
5. Cast never creates a third row.
|
||
6. Legacy never increases body height or exceeds two lines.
|
||
7. Empty states reserve the same geometry as populated states.
|
||
8. Entering or leaving hover does not move the glyph under the pointer.
|
||
9. Pointer exit restores the complete current camera dossier, not a stale snapshot.
|
||
10. Hover never changes camera focus or roster order.
|
||
|
||
## Implementation plan
|
||
|
||
### Phase A — Fixed shell
|
||
|
||
1. Give the Saga root, rail, nametag, and body explicit fixed bounds.
|
||
2. Remove content-driven preferred-height calculations from the hover path.
|
||
3. Reserve portrait, Cast, and Legacy rectangles before binding any content.
|
||
|
||
### Phase B — Portrait and Cast
|
||
|
||
1. Place the restored live stone-frame portrait in its fixed rectangle.
|
||
2. Replace flexible Cast layout with the bounded `3 × 2` grid.
|
||
3. Add deterministic relationship priority, overflow, and empty-state behavior.
|
||
|
||
### Phase C — Legacy allocator
|
||
|
||
1. Rank durable facts by narrative importance.
|
||
2. Remove facts redundant with the visible identity and Cast.
|
||
3. Allocate at most two complete lines with measured ellipsis.
|
||
|
||
### Phase D — Interaction hardening
|
||
|
||
1. Make hover-to-hover swaps atomic.
|
||
2. Make pointer exit restore the latest camera state.
|
||
3. Verify status-banner cancellation and camera pause ownership.
|
||
4. Consider pinned preview only after Prefer has an explicit separate control.
|
||
|
||
## Verification
|
||
|
||
Add deterministic scenarios:
|
||
|
||
- `saga_fixed_shell` — empty, sparse, and full subjects produce identical bounds.
|
||
- `saga_hover_identity_swap` — every identity element follows the hovered glyph.
|
||
- `saga_hover_restore_camera` — exit restores the latest camera dossier.
|
||
- `saga_rail_portrait_cache` — same-species characters retain distinct cached sprites.
|
||
- `saga_cast_fixed_grid` — seven or more ties render five entries plus `+N`, with no third row.
|
||
- `saga_legacy_fixed_budget` — long legacy prose remains inside two lines.
|
||
- `saga_hover_scrub_stability` — rapid glyph changes do not move the rail or flash camera identity.
|
||
|
||
Capture visual evidence for same-species characters, six Cast entries, a fallen cached portrait,
|
||
empty Cast/Legacy, maximum-length names, and every supported UI scale.
|
||
|
||
## Definition of done
|
||
|
||
- All layout invariants pass in deterministic harness coverage.
|
||
- Existing Saga/coherence scenarios remain green.
|
||
- No duplicate camera/Saga information appears during preview.
|
||
- No text clips or paints beyond its assigned region.
|
||
- Same-species characters remain distinguishable after their unique sprite has been observed.
|
||
- A frame-by-frame comparison shows zero rail, nametag, portrait-frame, or card-edge movement while scrubbing glyphs.
|
||
|
||
## Non-goals
|
||
|
||
- Saga does not become a second Chronicle.
|
||
- Saga does not drive or lock camera selection.
|
||
- The card does not show the current camera Beat or Context while previewing another character.
|
||
- Persistent pinning is not added until its interaction no longer conflicts with Prefer.
|