194 lines
8.2 KiB
Markdown
194 lines
8.2 KiB
Markdown
# M2 Placement Grouper
|
|
|
|
## Metadata
|
|
|
|
| Field | Value |
|
|
|---|---|
|
|
| Status | Implemented extraction |
|
|
| Target/work package | M03 / `M03-RND-M2-PLACEMENT-GROUPER-001` |
|
|
| Owners | Pure ADT M2 placement validation, normalization and transform grouping |
|
|
| Last verified | Worktree `work/sindo-main-codex/m03-m2-placement-grouper`, 2026-07-16 |
|
|
| Profiles/capabilities | Existing static M2 worker-grouping path |
|
|
|
|
## Purpose
|
|
|
|
Convert valid raw ADT M2 placement records into ordered tile-local transforms
|
|
grouped by the historically normalized relative model path.
|
|
|
|
## Non-goals
|
|
|
|
- Reserve cross-tile unique IDs or choose placement ownership.
|
|
- Own WorkerThreadPool tasks, mutexes, result queues or tile lifecycle.
|
|
- Load models, choose animated/static rendering or build MultiMesh batches.
|
|
- Create Node, Mesh, material, Resource or RID objects.
|
|
- Change path case, transform rules, batching or visual fidelity.
|
|
|
|
## Context and boundaries
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
ADT[ADT names + placements] --> Registry[M2UniquePlacementRegistry]
|
|
Registry --> Grouper[M2PlacementGrouper]
|
|
Grouper --> Resolver[M2PlacementTransformResolver]
|
|
Resolver --> Grouper
|
|
Grouper --> Groups[Path to ordered Transform3D arrays]
|
|
Groups --> Worker[Loader worker result adapter]
|
|
Worker --> Build[Loader-owned build queue]
|
|
```
|
|
|
|
Allowed dependencies are value containers, `Vector3`, `Basis`, `Transform3D`
|
|
and `M2PlacementTransformResolver`. SceneTree, Node, RenderingServer,
|
|
WorkerThreadPool, mutexes, ResourceLoader, files, gameplay, network and editor UI
|
|
are forbidden.
|
|
|
|
## Public API
|
|
|
|
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
|
|---|---|---|---|---|
|
|
| `group_placements(tile_origin, m2_names, m2_placements)` | Pure query | Return valid placements grouped as ordered tile-local transforms | Worker/main thread; call-local output | Invalid variants/name IDs/empty paths are skipped |
|
|
|
|
## Inputs and outputs
|
|
|
|
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
|
|---|---|---|---|---|---|
|
|
| Input | Godot-space tile origin | Loader tile state | Local-position calculation | Value copy | One call |
|
|
| Input | ADT MMDX name table | Parsed ADT tile | Path lookup | Read-only PackedStringArray | One call |
|
|
| Input | Raw placement dictionaries | Unique registry result | Validation/transforms | Read-only shallow values | One call |
|
|
| Output | Relative path to ordered `Transform3D` arrays | Grouper | Loader worker/build adapter | Fresh Dictionary and arrays | One call/build job |
|
|
|
|
Output retains first-seen group-key order through Godot Dictionary insertion
|
|
order and placement order inside each array. Inputs are not retained or returned.
|
|
|
|
## Data flow
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
Call[group_placements] --> Next[Read placement]
|
|
Next --> Dict{Dictionary?}
|
|
Dict -->|no| Next
|
|
Dict -->|yes| Name{Valid name_id?}
|
|
Name -->|no| Next
|
|
Name -->|yes| Normalize[Backslash to slash; lowercase MDX/MDL to M2]
|
|
Normalize --> Empty{Path empty?}
|
|
Empty -->|yes| Next
|
|
Empty -->|no| Local[world position - tile origin]
|
|
Local --> Resolve[Resolve basis and origin offset]
|
|
Resolve --> Scale[Apply max scale 0.0001]
|
|
Scale --> Append[Append Transform3D to path group]
|
|
Append --> Next
|
|
Next -->|complete| Return[Return fresh groups]
|
|
```
|
|
|
|
## Lifecycle/state
|
|
|
|
The grouper retains no state. Every call constructs one stateless transform
|
|
resolver and fresh output containers. Reset, cancellation and shutdown require
|
|
no grouper action.
|
|
|
|
## Main sequence
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant Loader as Loader worker callback
|
|
participant Grouper as M2PlacementGrouper
|
|
participant Resolver as M2PlacementTransformResolver
|
|
Loader->>Grouper: group_placements(origin, names, placements)
|
|
loop valid placement
|
|
Grouper->>Resolver: resolve basis and origin offset
|
|
Resolver-->>Grouper: value-only basis and offset
|
|
Grouper->>Grouper: append local Transform3D
|
|
end
|
|
Grouper-->>Loader: fresh path groups
|
|
Loader->>Loader: mutex-protected result enqueue
|
|
```
|
|
|
|
## Ownership, threading and resources
|
|
|
|
- The grouper owns only call-local containers and transforms.
|
|
- The loader owns tasks, mutex/result queue, tile/build state, caches, model
|
|
loading, Nodes, MultiMesh, Mesh, materials and RIDs.
|
|
- Worker use is safe because the grouper and resolver mutate no shared state.
|
|
- The caller owns returned containers; mutation cannot affect later calls.
|
|
|
|
## Errors, cancellation and recovery
|
|
|
|
| Failure | Detection | Behavior | Diagnostic | Recovery |
|
|
|---|---|---|---|---|
|
|
| Non-Dictionary placement | Type guard | Skip | Contract fixture | Repair parser input |
|
|
| Invalid name ID | Bounds guard | Skip | Contract fixture | Repair name table/placement |
|
|
| Empty path | Empty guard | Skip | Contract fixture | Supply valid MMDX entry |
|
|
| Missing position/rotation/scale | Defaults | Zero position/rotation and scale 1 | Contract fixture | Expected fallback |
|
|
| Zero/negative scale | Historical clamp | Basis scale uses `0.0001` | Contract fixture | Supply valid ADT scale |
|
|
| Cancellation after grouping | Loader state check | Discard stale result | Loader regressions | Re-request eligible detail |
|
|
|
|
Cancellation stays loader-owned because grouping has no task or partial state.
|
|
|
|
## Configuration and capabilities
|
|
|
|
No setting or profile branch is introduced. Preserved behavior includes
|
|
backslash replacement without lowercasing, case-sensitive lowercase `.mdx/.mdl`
|
|
conversion, first-seen ordering, tile-local subtraction and scale clamp `0.0001`.
|
|
|
|
## Persistence, cache and migration
|
|
|
|
No persistence or cache format participates. ADT and M2 cache versions are
|
|
unchanged, so no rebuild or migration is required.
|
|
|
|
## Diagnostics and observability
|
|
|
|
The service emits no logs. Its verifier records contract results, source
|
|
boundaries and bounded synthetic 100-by-256 placement timing. Loader metrics
|
|
continue to own task/build/cache observability.
|
|
|
|
## Verification
|
|
|
|
- `verify_m2_placement_grouper.gd`: invalid input, path behavior, ordering,
|
|
defaults, local origin, regular/waterfall transforms, scale clamp, fresh output,
|
|
loader/source boundary and timing.
|
|
- `verify_m2_placement_transform_resolver.gd`: three transform consumers remain
|
|
covered as two direct loader adapters plus the grouper adapter.
|
|
- Registry/dedupe, facade, internal-access, manifest, shutdown, documentation and
|
|
coordination regressions remain required.
|
|
- Fidelity evidence is exact extraction; no new asset-backed parity claim is made.
|
|
|
|
## Extension points
|
|
|
|
- `M2BuildBatchPlanner` now owns pure batch sizing/cursor progression; the
|
|
remaining build-job state machine stays behind explicit loader budgets.
|
|
- Spatial cells require measured culling evidence and are outside this contract.
|
|
|
|
## Capability status
|
|
|
|
| Capability | Status | Evidence | Gap/next step |
|
|
|---|---|---|---|
|
|
| Pure path/transform grouping | Implemented extraction | Contract/source/timing verifier | Asset-backed traversal timing pending |
|
|
| Worker/build orchestration | Remains in loader | Existing regressions | Separate stateful extraction required |
|
|
| Spatial MultiMesh cells | Partial | Current path batches | Culling-driven design/evidence pending |
|
|
|
|
## Known gaps and risks
|
|
|
|
- Raw placement dictionaries remain the ADT parser boundary.
|
|
- Uppercase extensions remain unchanged even though transform exception matching
|
|
is case-insensitive.
|
|
- Grouping is by model path, not spatial culling cell.
|
|
- Asset-backed p95/p99 and visual comparison remain unavailable.
|
|
|
|
## Source map
|
|
|
|
| Path | Responsibility |
|
|
|---|---|
|
|
| `src/render/m2/m2_placement_grouper.gd` | Validation, normalization and grouping |
|
|
| `src/render/m2/m2_placement_transform_resolver.gd` | Basis and origin rules |
|
|
| `src/render/m2/m2_build_batch_planner.gd` | Downstream batch sizing and cursor planning |
|
|
| `src/scenes/streaming/streaming_world_loader.gd` | Worker/result/build lifecycle and resources |
|
|
| `src/tools/verify_m2_placement_grouper.gd` | Contract, dependency and timing regression |
|
|
|
|
## Related decisions and references
|
|
|
|
- [`m2-build-batch-planner.md`](m2-build-batch-planner.md)
|
|
- [`m2-placement-transform-resolver.md`](m2-placement-transform-resolver.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)
|