# 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 the pure placement grouper, 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[M2PlacementGrouper]
    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 | Grouper/placeholder/instance adapter | Value copy | One call |
| Output | Local `Vector3` offset | Resolver | Grouper/placeholder/instance 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 Caller as Grouper or StreamingWorldLoader
    participant Resolver as M2PlacementTransformResolver
    participant Consumer as Grouper/placeholder/instance path
    Caller->>Resolver: resolve_basis(rotation, path)
    Resolver-->>Caller: unscaled Basis
    Caller->>Resolver: resolve_origin_offset(rotation, path, scale)
    Resolver-->>Caller: local offset
    Caller->>Consumer: compose historical scaled Transform3D
```

## Ownership, threading and resources

- The resolver retains no mutable state or engine resources.
- The grouper owns worker-path final transforms; the loader owns placement records,
  tasks, queues, caches and MultiMesh/Node/Mesh/material/RID creation and cleanup.
- Pure methods are safe for grouper worker use 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 two direct loader consumers plus the grouper consumer 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 consumers, 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

- `M2PlacementGrouper` composes this resolver for the worker grouping path.
- 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/render/m2/m2_placement_grouper.gd` | Grouping adapter and local Transform3D ownership |
| `src/scenes/streaming/streaming_world_loader.gd` | Two direct adapters and renderer resource ownership |
| `src/tools/verify_m2_placement_transform_resolver.gd` | Formula, source and performance regression |

## Related decisions and references

- [`m2-placement-grouper.md`](m2-placement-grouper.md)
- [`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)
