97bb53f6cd
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
274 lines
15 KiB
Markdown
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`.
|