Files
open-wc/docs/modules/m2-placement-grouper.md
T

8.1 KiB

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

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

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

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

  • A later package may extract the M2 build-job state machine behind explicit budgets while retaining this pure grouping input.
  • 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/scenes/streaming/streaming_world_loader.gd Worker/result/build lifecycle and resources
src/tools/verify_m2_placement_grouper.gd Contract, dependency and timing regression