refactor(M03): extract M2 placement transform resolver
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
# M2 Placement Transform Resolver
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented |
|
||||
| Target/work package | M03 / `M03-RND-M2-PLACEMENT-TRANSFORM-001` |
|
||||
| Owners | Pure ADT M2 placement basis and model-specific origin compensation |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-m2-placement-transform`, 2026-07-16 |
|
||||
| Profiles/capabilities | Existing static M2 paths; four calibrated model exceptions |
|
||||
|
||||
## Purpose
|
||||
|
||||
Resolve the unscaled Godot-space orientation and optional local origin offset
|
||||
used by ADT M2 worker grouping, placeholder rendering and instance creation.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Convert WoW/server/ADT coordinates or own placement positions.
|
||||
- Apply final scale clamps, build transforms, group placements or create Nodes.
|
||||
- Add, generalize or configure model-specific corrections.
|
||||
- Change M2 materials, animation, caches, visibility or quality profiles.
|
||||
- Claim general build-12340 placement parity.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
ADT[ADT placement rotation/path/scale] --> Resolver[M2PlacementTransformResolver]
|
||||
Resolver --> Basis[Unscaled Basis]
|
||||
Resolver --> Offset[Local origin offset]
|
||||
Basis --> Group[Worker grouping]
|
||||
Basis --> Placeholder[Placeholder MultiMesh path]
|
||||
Basis --> Instance[Instance path]
|
||||
Offset --> Group
|
||||
Offset --> Placeholder
|
||||
Offset --> Instance
|
||||
Group --> Loader[StreamingWorldLoader-owned queues/build]
|
||||
```
|
||||
|
||||
Allowed dependencies are `Vector3`, `Basis`, scalar math and path-string
|
||||
normalization. Node, SceneTree, RenderingServer, ResourceLoader, tasks, caches,
|
||||
files, gameplay, network and editor UI are forbidden.
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `resolve_basis(rotation_radians, relative_m2_path)` | Pure query | Return unscaled regular or calibrated placement orientation | Worker/main thread; call-local result | Unknown paths use `Basis.from_euler` |
|
||||
| `resolve_origin_offset(rotation_radians, relative_m2_path, scale_value)` | Pure query | Compensate the measured tall-waterfall twist anchor | Worker/main thread; call-local result | Regular/unanchored paths return zero; scale clamps to `0.0001` for compensation |
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Godot Euler rotation in radians | ADT loader placement record | Resolver | Scalar copy | One call |
|
||||
| Input | Relative M2 path | ADT MMDX/name table | Resolver allowlist | Caller string | One call |
|
||||
| Input | Raw placement scale | ADT placement record | Offset compensation | Scalar copy | One call |
|
||||
| Output | Unscaled `Basis` | Resolver | Group/placeholder/instance adapter | Value copy | One call |
|
||||
| Output | Local `Vector3` offset | Resolver | Transform origin adapter | Value copy | One call |
|
||||
|
||||
Callers preserve their historical basis scale minima: grouping/instance use
|
||||
`0.0001`, while placeholders use `0.01`.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
BasisCall[resolve_basis] --> ModelName[Normalize separators/case/basename]
|
||||
ModelName --> Waterfall{Waterfall sheet?}
|
||||
Waterfall -->|yes| WaterfallBasis[+90 degree world yaw * Euler * local-axis twist]
|
||||
Waterfall -->|no| Cliff{Elwynn cliff rock?}
|
||||
Cliff -->|yes| CliffBasis[Corrected Y/BACK/RIGHT composition]
|
||||
Cliff -->|no| Regular[Basis.from_euler]
|
||||
OffsetCall[resolve_origin_offset] --> Anchored{Tall waterfall anchor?}
|
||||
Anchored -->|no| Zero[Vector3.ZERO]
|
||||
Anchored -->|yes| Compensation[(base anchor - twisted anchor) * clamped scale]
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
The resolver is stateless. It may be shared between the main thread and worker
|
||||
calls because each result is computed solely from value inputs. Construction,
|
||||
world reset and shutdown require no clear, cancellation or resource release.
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Loader as StreamingWorldLoader
|
||||
participant Resolver as M2PlacementTransformResolver
|
||||
participant Consumer as Group/placeholder/instance path
|
||||
Loader->>Resolver: resolve_basis(rotation, path)
|
||||
Resolver-->>Loader: unscaled Basis
|
||||
Loader->>Resolver: resolve_origin_offset(rotation, path, scale)
|
||||
Resolver-->>Loader: local offset
|
||||
Loader->>Consumer: compose historical scaled Transform3D
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- The resolver retains no mutable state or engine resources.
|
||||
- The loader owns placement records, final transforms, tasks, queues, caches,
|
||||
MultiMesh/Node/Mesh/material/RID creation and cleanup.
|
||||
- Pure methods are safe for current worker grouping and main-thread consumers.
|
||||
- Returned `Basis`/`Vector3` values have no lifetime coupling to the resolver.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Empty/unknown path | Allowlist miss | Regular Euler basis and zero offset | Contract fixture | Expected fallback |
|
||||
| Mixed case/backslashes/MDX | Normalization | Same calibrated model match | Contract fixture | None required |
|
||||
| Zero/negative scale for anchored offset | Numeric clamp | Use `0.0001` compensation scale | Contract fixture | Caller may supply valid ADT scale later |
|
||||
| World reset/shutdown | No retained state | No action | Existing shutdown verifier | New calls remain independent |
|
||||
|
||||
There is no asynchronous operation or cancellation inside the resolver.
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
The resolver introduces no settings. The preserved constants and allowlists are:
|
||||
|
||||
- world yaw `PI` for `ElwynnCliffRock01/02`;
|
||||
- world yaw `PI/2` for `NewWaterfall` and `ElwynnTallWaterfall01`;
|
||||
- measured local axes/twist signs for both waterfall sheets;
|
||||
- measured tall-waterfall anchor `(-2.667799, 89.62273, 0.00129)`.
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
No persistence, schema or cache format participates. Existing ADT/M2 cache
|
||||
versions and rebuild instructions remain unchanged.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
The resolver emits no logs. The contract verifier compares formulas/constants,
|
||||
checks all three loader consumers and records bounded synthetic timing. Visual
|
||||
and asset-backed evidence remains in the renderer checkpoint workflow.
|
||||
|
||||
## Verification
|
||||
|
||||
- `verify_m2_placement_transform_resolver.gd`: regular fallback, path
|
||||
normalization, both cliff rocks, both waterfalls, twist anchor, negative-scale
|
||||
compensation, three loader adapters, source boundary and 10,000-pair timing.
|
||||
- M2 unique/dedupe, terrain, facade, internal-access, manifest, shutdown,
|
||||
scheduler, streaming and coordinate regressions remain required.
|
||||
- Fidelity evidence is exact extraction of existing calibrated formulas. The
|
||||
private asset/camera comparison that motivated them is not reproduced here.
|
||||
|
||||
## Extension points
|
||||
|
||||
- The next package may compose this resolver into a pure placement grouper.
|
||||
- New model corrections require separate measured fidelity evidence; this module
|
||||
is not a generic override registry.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| Consistent transform rules across three M2 paths | Implemented | Formula/source/timing contract | Asset-backed visual recheck pending |
|
||||
| General ADT M2 placement parity | Partial | Existing checkpoint notes | Broader fixture coverage required |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- Four model-specific exceptions are empirical compatibility rules.
|
||||
- Most placements use raw Godot Euler behavior; independent client-wide
|
||||
transform validation is incomplete.
|
||||
- Callers intentionally retain different final scale clamps.
|
||||
- Grouping, build, cache and render ownership remain in the streamer.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/m2/m2_placement_transform_resolver.gd` | Pure basis/offset rules and calibrated allowlists |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Three adapters and final Transform3D/resource ownership |
|
||||
| `src/tools/verify_m2_placement_transform_resolver.gd` | Formula, source and performance regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`m2-unique-placement-registry.md`](m2-unique-placement-registry.md)
|
||||
- [`world-renderer.md`](world-renderer.md)
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||
Reference in New Issue
Block a user