Work-Package: M02-RND-CHARACTER-PRESENTATION-001 Agent: sindo-main-codex
15 KiB
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
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
PackedScenecharacter asset; - existing
CharacterGeosetController,CharacterTextureCompositorandWowCharacterOutfitResolverpresentation components; AnimationPlayerand 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
CharacterModelsubtree; - 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
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
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
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
Visualand one animation presenter below it. - The appearance presenter exclusively owns the generated
CharacterModelsubtree; 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.