# 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`.