Files
open-wc/docs/modules/character-presentation.md
T
sindoring 97bb53f6cd rnd(M02): extract character presenters
Work-Package: M02-RND-CHARACTER-PRESENTATION-001

Agent: sindo-main-codex

Tests: presentation/input/movement/terrain/camera regressions; coordinate/streaming/docs/coordination gates; renderer dry-run 7/7

Fidelity: preserves current sandbox model/outfit and Stand/Idle/Run/Walk behavior; build-12340 parity remains unverified
2026-07-14 23:42:12 +04:00

274 lines
15 KiB
Markdown

# Character Presentation
## Metadata
| Field | Value |
|---|---|
| Status | Implemented boundary / Partial fidelity |
| Target/work package | M02 / M02-RND-CHARACTER-PRESENTATION-001 |
| Owners | Character appearance composition and locomotion animation presentation |
| Last verified | `working tree`, 2026-07-14 |
| Profiles/capabilities | Current render sandbox character experiment |
## Purpose
Compose the current sandbox character GLB, geosets, skin textures and starter
outfit below the existing `Visual` node, then independently present its
stationary or moving animation. Keep visual asset loading and AnimationPlayer
state out of gameplay movement and the player scene composition root.
## Non-goals
- Define authoritative character identity, equipment or movement state.
- Change the existing GLB, geoset, texture-compositor, DBC or outfit formats.
- Implement full build-12340 animation selection, blending or equipment visuals.
- Load world assets, query terrain, process input or control the camera.
- Introduce asynchronous streaming, background scene mutation or persistence.
## Context and boundaries
```mermaid
flowchart LR
Scene[ThirdPersonWowController composition] --> Appearance[CharacterAppearancePresenter]
Model[Configured PackedScene] --> Appearance
DBC[Extracted DBC/item textures] --> Appearance
Appearance --> Root[CharacterModel root]
Root --> Geosets[Existing geoset controller]
Root --> Compositor[Existing texture compositor]
Appearance -->|ready root| Scene
Scene --> Animation[CharacterAnimationPresenter]
Velocity[Movement read model] -->|moving boolean| Animation
Animation --> Player[AnimationPlayer below root]
```
Allowed dependencies:
- configured Godot `PackedScene` character asset;
- existing `CharacterGeosetController`, `CharacterTextureCompositor` and
`WowCharacterOutfitResolver` presentation components;
- `AnimationPlayer` and animation resources below the composed character root;
- main-thread player scene composition and movement velocity read model.
Forbidden dependencies:
- `MoveIntent`, input devices or movement integration;
- `TerrainQuery`, ADT parsing, coordinate conversion or streaming decisions;
- cameras, network packets, server databases or gameplay reducers;
- direct production database writes or cache-format ownership.
## Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `CharacterAppearancePresenter.load_character_appearance(content_root_override)` | Command | Replaces the owned character root and schedules final grounding/outfit composition | Main thread; may be called again during scene lifetime | Returns `false` for empty/invalid model; warning for invalid resource |
| `CharacterAppearancePresenter.character_root` | Read-only state | Current owned model root or null | Stable until next load | None |
| `character_appearance_ready(character_root)` | Signal | Publishes a fully attached, grounded root | Deferred main-thread completion | Not emitted for invalid/empty model |
| `CharacterAnimationPresenter.bind_character_root(character_root)` | Command | Discovers and prepares the first descendant AnimationPlayer | Main thread; binding lifetime | Returns `false` and clears state when absent |
| `CharacterAnimationPresenter.present_locomotion(is_moving)` | Command | Selects current stationary/moving candidate and starts it once | Main/physics thread | Returns `false` when unavailable, missing or unchanged |
| `active_animation_name` | Read-only state | Selected animation name | Stable until bind/state transition | Empty when unbound |
| `has_animation_player` | Read-only state | Whether the presenter has a valid bound player | Stable until bind | None |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Model resource path, class, scale and yaw | Runtime scene composition | Appearance presenter | Exported configuration | Captured at load |
| Input | Extracted content root | Player scene configuration | Appearance presenter | Copied string | Captured at load |
| Input | Packed character scene | Godot resource loader | Appearance presenter | Shared resource; presenter owns instance | Scene lifetime |
| Output | Composed `CharacterModel` root | Appearance presenter | Scene/animation presenter | Appearance presenter owns node | Until replacement |
| Output | Ready signal | Appearance presenter | Player composition | Synchronous signal after deferred completion | One per successful load |
| Input | Moving/stationary boolean | Player adapter from horizontal velocity | Animation presenter | Value copy | One physics tick |
| Output | AnimationPlayer playback mutation | Animation presenter | Rendered character | Bound root owns player/resources | Until next transition |
Side effects:
- loads and instantiates the configured model on the main thread;
- adds/removes one owned `CharacterModel` subtree;
- composes the existing texture/outfit components and reads their extracted inputs;
- changes model transform for scale, yaw and ground alignment;
- configures locomotion loop modes and starts AnimationPlayer playback;
- does not write files, caches, databases or network state.
## Data flow
```mermaid
flowchart TD
Config[Model/class/scale/yaw/content root] --> Load[Load PackedScene]
Load --> Replace[Replace owned CharacterModel]
Replace --> Attach[Attach model and compositor]
Attach --> Ground[Fit mesh bounds to visual ground]
DBC[Existing outfit resolver] --> Outfit[Apply starter outfit]
Ground --> Ready[Emit ready root]
Outfit --> Ready
Ready --> Bind[Discover AnimationPlayer]
Velocity[Horizontal movement velocity] --> State[Moving or stationary]
State --> Select[Run/Walk or Stand/Idle selection]
Bind --> Select
Select --> Playback[Loop and play with blend]
```
## Lifecycle/state
```mermaid
stateDiagram-v2
[*] --> Empty
Empty --> Loading: valid load command
Loading --> Ready: attach + deferred composition
Loading --> Empty: invalid resource
Ready --> Loading: replacement load
Ready --> Empty: empty model command
state Animation {
[*] --> Unbound
Unbound --> Stationary: bind root with animations
Stationary --> Moving: moving=true
Moving --> Stationary: moving=false
Stationary --> Unbound: bind missing player
Moving --> Unbound: bind missing player
}
```
Appearance replacement removes the old root from the scene immediately and
queues it for deletion. Deferred completion checks root identity, so a stale
completion cannot publish a replaced model.
## Main sequence
```mermaid
sequenceDiagram
participant Scene as ThirdPersonWowController
participant Appearance as CharacterAppearancePresenter
participant Existing as Geoset/Compositor/Outfit
participant Animation as CharacterAnimationPresenter
participant Player as AnimationPlayer
Scene->>Appearance: load_character_appearance(content root)
Appearance->>Appearance: load, instantiate and attach root
Appearance->>Existing: compose textures and starter outfit
Appearance-->>Scene: character_appearance_ready(root)
Scene->>Animation: bind_character_root(root)
Animation->>Player: prepare loops and play Stand/Idle
loop Physics ticks
Scene->>Animation: present_locomotion(is moving)
Animation->>Player: play only on state/name change
end
```
## Ownership, threading and resources
- The runtime scene owns one appearance presenter on `Visual` and one animation
presenter below it.
- The appearance presenter exclusively owns the generated `CharacterModel`
subtree; imported shared resources remain Godot-owned.
- The animation presenter borrows an AnimationPlayer reference until rebind or
subtree replacement; the player scene orders rebind through the ready signal.
- Loading, node mutation, bounds calculation, outfit setup and playback occur
on the main thread. There are no workers, locks, RIDs or cancellation tokens.
- The player controller owns neither model resources nor animation state.
## Errors, cancellation and recovery
| Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Empty model path | Load guard | Clears old root and returns `false` | Contract regression | Configure a path and retry |
| Invalid model resource | Failed PackedScene cast | Remains empty | Named warning with path | Correct/extract asset and retry |
| Replaced deferred completion | Root identity check | Stale completion ignored | Replacement regression | No action required |
| Missing DBC/outfit inputs | Existing resolver returns false/empty | Base model remains visible | Existing resolver diagnostics | Restore extracted inputs and reload |
| Missing AnimationPlayer | Recursive discovery returns null | Presenter becomes unbound | Contract return/read state | Bind a root with animations |
| Missing candidate animation | Candidate selection returns empty | Current playback unchanged | Verifier/source diagnostics | Extend verified candidate policy later |
There is no background load cancellation. Replacement is the recovery path and
invalid model state is explicit through the return value and null read model.
## Configuration and capabilities
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|---|---|---|---|---|
| Character model | Human male sandbox GLB | Current sandbox | Before reload | Model scene input |
| Character class ID | `1` | Current sandbox | Before reload | Starter outfit lookup |
| Uniform scale | `1.0` | Current sandbox | Before reload | Character root scale |
| Yaw offset | `90` degrees | Current sandbox | Before reload | Imported model correction |
| Extracted content root | `res://data/extracted` | Local content | Per load override | DBC/item texture input |
| Animation blend | `0.15` seconds | Current sandbox | Before bind/transition | Playback cross-fade/default blend |
| Stationary candidates | `Stand`, then `Idle` | Current sandbox | Fixed | Exact then substring selection |
| Moving candidates | `Run`, then `Walk` | Current sandbox | Fixed | Exact then substring selection |
## Persistence, cache and migration
Presenter state is transient. No saved settings, user profile, database schema
or cache format changes. Existing scene paths remain
`ThirdPersonPlayer/Visual` and add only `Visual/AnimationPresenter`; model and
extracted-data paths retain their defaults. Moving the model exports from the
player node to `Visual` is a scene-authoring ownership change, not persisted
runtime data migration.
## Diagnostics and observability
- Logs: invalid model path warns with the responsible presenter and path.
- Read models: current character root, animation binding and active name.
- Tests: dedicated verifier reports appearance, animation, scene and player boundaries.
- Metrics/correlation IDs: not applicable to this synchronous sandbox presenter.
## Verification
- Unit/contract: synthetic model fixture verifies load, scale, yaw, grounding,
compositor composition, ready signal, replacement and empty-model clearing.
- Animation: synthetic libraries verify nested discovery, exact and substring
candidate selection, loop policy, blend configuration and duplicate suppression.
- Boundary: player source contains no model loading, outfit, bounds or
AnimationPlayer logic; appearance and animation dependencies remain separate.
- Integration: Eastern Kingdoms, Kalimdor and player regression scenes compose
both presenters; existing input/movement/terrain/camera tests remain required.
- Fidelity evidence: only preservation of the current sandbox defaults and
selection rules. No build-12340 appearance or animation parity is claimed.
- Performance: one synchronous model composition per load and constant-time
locomotion calls except on state transition; asset streaming remains future work.
## Extension points
- Replace starter outfit inputs with a versioned appearance/equipment snapshot.
- Replace boolean locomotion input with a typed presentation state after
movement/network contracts define walk, run, jump, fall, swim, mount and emotes.
- Add asynchronous/preloaded model providers behind the appearance command
without moving resource loading back into gameplay.
- Add verified animation IDs/variation policy after build-12340 fixtures exist.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Independent appearance composition | Implemented | Synthetic fixture and three scene wirings | Integrator target update |
| Independent locomotion animation | Implemented | Exact/fallback/loop/blend regression | Add typed presentation state later |
| Current starter outfit | Partial | Existing resolver reused unchanged | Needs extracted DBC fixture and client comparison |
| Current skin/geoset composition | Partial | Existing components reused | Full equipment/customization fidelity incomplete |
| Build-12340 animation semantics | Planned | No original-client fixture | Capture animation IDs/transitions/timing |
| Runtime equipment/network updates | Planned | No snapshot contract | M08/M09/M12 work |
## Known gaps and risks
- Model loading remains synchronous and can hitch when not preloaded.
- Exact appearance, equipment, animation choice, playback speed and blending are unverified.
- The moving input is a sandbox threshold boolean, not authoritative locomotion state.
- The first descendant AnimationPlayer wins; multi-player imported scenes need a
more explicit binding contract if they appear.
- Existing geoset/compositor/outfit implementations retain their own fidelity gaps.
## Source map
| Path | Responsibility |
|---|---|
| `src/scenes/character/character_appearance_presenter.gd` | Model-root ownership, composition, ground fit and outfit setup |
| `src/scenes/character/character_animation_presenter.gd` | AnimationPlayer binding and locomotion playback state |
| `src/scenes/character/character_geoset_controller.gd` | Existing geoset visibility and outfit defaults |
| `src/scenes/character/character_texture_compositor.gd` | Existing skin/equipment texture composition |
| `src/scenes/character/wow_character_outfit_resolver.gd` | Existing DBC starter outfit lookup |
| `src/scenes/player/third_person_wow_controller.gd` | Scene composition and moving-state adapter |
| `src/tests/scenes/character_presentation_model_fixture.tscn` | Asset-free model/grounding fixture |
| `src/tools/verify_character_presentation.gd` | Presenter, scene and dependency regression |
## Related decisions and references
- ADR: none; current behavior and resource formats are preserved.
- Upstream/reference: `targets/02-player-decomposition.md`,
`targets/roadmap/02-rendering-and-graphics.md`,
`targets/roadmap/04-gameplay-systems.md`, `RENDER.md`.