# 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 | `97bb53f`, 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`.
