Compare commits

...

11 Commits

Author SHA1 Message Date
sindoring e794764269 docs(M02): publish player input handoff
Work-Package: M02-GMP-INPUT-001
Agent: sindo-main-codex
Tests: coordination and documentation gates; diff check
Fidelity: documents regression evidence and unverified build-12340 semantics
2026-07-14 22:45:14 +04:00
sindoring 6bd2e84048 gmp(M02): add player input intent seam
Work-Package: M02-GMP-INPUT-001
Agent: sindo-main-codex
Tests: verify_player_input; renderer baseline dry-run; coordinate, streaming, documentation and coordination gates
Fidelity: preserves current sandbox bindings and movement/camera behavior; exact build-12340 semantics remain unverified
2026-07-14 22:44:44 +04:00
sindoring 62eece991c Merge pull request 'docs(M01): claim milestone closeout' (#20) from work/sindo-main-codex/m01-integrator-closeout into master
Reviewed-on: #20
2026-07-14 12:55:09 +03:00
sindoring fc81059dfa docs(M01): mark milestone closeout ready
Work-Package: M01-QAR-INTEGRATOR-CLOSEOUT-001

Agent: sindo-main-codex
2026-07-14 12:14:39 +04:00
sindoring dd412e6d0d test(M01): close coordinates and seams milestone
Work-Package: M01-QAR-INTEGRATOR-CLOSEOUT-001

Agent: sindo-main-codex

Tests: all M01 domain gates, unified renderer dry-run, coordination and documentation gates

Fidelity: five build-12340 points, raw ADT and mapped AzerothCore spawn evidence; no 1:1 claim
2026-07-14 12:13:43 +04:00
sindoring 07f08044f4 docs(M01): claim milestone closeout
Work-Package: M01-QAR-INTEGRATOR-CLOSEOUT-001

Agent: sindo-main-codex
2026-07-14 12:01:49 +04:00
sindoring c8e99f28d6 Merge pull request 'chore(M01): remove local spawn capture logs' (#19) from work/sindo-main-codex/m01-server-spawn-renderer into master
Reviewed-on: #19
2026-07-14 10:58:37 +03:00
sindoring 48cc976ba0 docs(M01): mark server spawn renderer ready
Work-Package: M01-QAR-SERVER-SPAWN-RENDERER-001

Agent: sindo-main-codex

Tests: server spawn GUI cold/warm, unified renderer dry-run, domain and documentation gates
2026-07-14 11:56:39 +04:00
sindoring 318e6ae757 chore(M01): remove local spawn capture logs
Work-Package: M01-QAR-SERVER-SPAWN-RENDERER-001

Agent: sindo-main-codex

Tests: renderer dry-run, coordinate/domain gates, GUI cold/warm inspection
2026-07-14 11:55:05 +04:00
sindoring 747fae8e2e Merge pull request 'координаты' (#18) from work/sindo-main-codex/m01-server-spawn-renderer into master
Reviewed-on: #18
2026-07-14 10:48:53 +03:00
sindoring 24ddc99a7b координаты 2026-07-14 11:48:14 +04:00
35 changed files with 1203 additions and 77 deletions
+4
View File
@@ -35,3 +35,7 @@
# CMake build директория # CMake build директория
/src/native/build/ /src/native/build/
# Local root-level diagnostic capture logs
/*.stdout.log
/*.stderr.log
+16
View File
@@ -857,6 +857,22 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- Coordinate contract version 2, renderer radii, cache formats and visual - Coordinate contract version 2, renderer radii, cache formats and visual
quality profiles are unchanged. quality profiles are unchanged.
## 2026-07-13 Golden Server Spawn Renderer Evidence
- The pinned AzerothCore Human Warrior start at server position
`(-8949.95, -132.493, 83.5312)` maps through `CoordinateMapper` to Godot
`(17199.159666667, 83.5312, 26016.616666667)`, ADT `(32,48)` and MCNK `(3,12)`.
- `server_spawn_render_manifest.json` drives the ordinary Eastern Kingdoms
checkpoint capture pipeline at that location; no live database or production
spawn entity is introduced.
- A renderer-native emissive marker starts at the exact mapped origin and keeps
depth testing enabled so GUI evidence exposes its relationship to terrain.
- `verify_server_spawn_renderer.gd` rejects drift between the pinned fixture,
manifest target/player coordinates and expected ADT ownership. The unified
renderer baseline runs this contract check before capture.
- This closes a coordinate/renderer integration criterion only. It does not
claim TrinityCore row equivalence or visual parity with the original client.
## Practical Rule For Future Work ## Practical Rule For Future Work
If something improves quality but creates visible hitch, it is not done. Move it to bake/cache/background work, split finalization over frames, or prewarm it before the player can see it. If something improves quality but creates visible hitch, it is not done. Move it to bake/cache/background work, split finalization over frames, or prewarm it before the player can see it.
@@ -1,7 +1,7 @@
# M01-FND-COORD-BOUNDARY-GATE-001 — Coordinate conversion boundary gate # M01-FND-COORD-BOUNDARY-GATE-001 — Coordinate conversion boundary gate
<!-- OPENWC_CLAIM:M01-FND-COORD-BOUNDARY-GATE-001:sindo-main-codex:2026-07-15 --> <!-- OPENWC_CLAIM:M01-FND-COORD-BOUNDARY-GATE-001:sindo-main-codex:2026-07-15 -->
<!-- OPENWC_HANDOFF:READY:M01-FND-COORD-BOUNDARY-GATE-001:47dec6c --> <!-- OPENWC_INTEGRATION:ACCEPTED:M01-FND-COORD-BOUNDARY-GATE-001:c8e99f2 -->
## Ownership ## Ownership
@@ -1,7 +1,7 @@
# M01-FND-COORD-TILE-AXIS-002 — ADT filename axis correction # M01-FND-COORD-TILE-AXIS-002 — ADT filename axis correction
<!-- OPENWC_CLAIM:M01-FND-COORD-TILE-AXIS-002:sindo-main-codex:2026-07-15 --> <!-- OPENWC_CLAIM:M01-FND-COORD-TILE-AXIS-002:sindo-main-codex:2026-07-15 -->
<!-- OPENWC_HANDOFF:READY:M01-FND-COORD-TILE-AXIS-002:a7072e3 --> <!-- OPENWC_INTEGRATION:ACCEPTED:M01-FND-COORD-TILE-AXIS-002:c8e99f2 -->
## Ownership ## Ownership
+1 -1
View File
@@ -1,7 +1,7 @@
# M01-FND-COORDS-001 — Canonical coordinate contracts # M01-FND-COORDS-001 — Canonical coordinate contracts
<!-- OPENWC_CLAIM:M01-FND-COORDS-001:sindo-main-codex:2026-07-15 --> <!-- OPENWC_CLAIM:M01-FND-COORDS-001:sindo-main-codex:2026-07-15 -->
<!-- OPENWC_HANDOFF:READY:M01-FND-COORDS-001:f45695c --> <!-- OPENWC_INTEGRATION:ACCEPTED:M01-FND-COORDS-001:c8e99f2 -->
## Ownership ## Ownership
@@ -1,7 +1,7 @@
# M01-FND-DOMAIN-IDENTITIES-001 — Stable domain identity namespaces # M01-FND-DOMAIN-IDENTITIES-001 — Stable domain identity namespaces
<!-- OPENWC_CLAIM:M01-FND-DOMAIN-IDENTITIES-001:sindo-main-codex:2026-07-15 --> <!-- OPENWC_CLAIM:M01-FND-DOMAIN-IDENTITIES-001:sindo-main-codex:2026-07-15 -->
<!-- OPENWC_HANDOFF:READY:M01-FND-DOMAIN-IDENTITIES-001:c1dc07c --> <!-- OPENWC_INTEGRATION:ACCEPTED:M01-FND-DOMAIN-IDENTITIES-001:c8e99f2 -->
## Ownership ## Ownership
@@ -1,7 +1,7 @@
# M01-QAR-COORD-FIXTURES-001 — Coordinate golden fixtures # M01-QAR-COORD-FIXTURES-001 — Coordinate golden fixtures
<!-- OPENWC_CLAIM:M01-QAR-COORD-FIXTURES-001:sindo-main-codex:2026-07-15 --> <!-- OPENWC_CLAIM:M01-QAR-COORD-FIXTURES-001:sindo-main-codex:2026-07-15 -->
<!-- OPENWC_HANDOFF:READY:M01-QAR-COORD-FIXTURES-001:ce4f7ec --> <!-- OPENWC_INTEGRATION:ACCEPTED:M01-QAR-COORD-FIXTURES-001:c8e99f2 -->
## Ownership ## Ownership
@@ -0,0 +1,101 @@
# M01-QAR-INTEGRATOR-CLOSEOUT-001 — M01 milestone closeout
<!-- OPENWC_CLAIM:M01-QAR-INTEGRATOR-CLOSEOUT-001:sindo-main-codex:2026-07-16 -->
<!-- OPENWC_HANDOFF:READY:M01-QAR-INTEGRATOR-CLOSEOUT-001:dd412e6 -->
## Ownership
- Target: M01
- Program: QAR
- Owner/Agent ID: sindo-main-codex
- Branch: `work/sindo-main-codex/m01-integrator-closeout`
- Lease expires UTC: 2026-07-16
- Integrator: sindo-main-codex, continuing the user-directed milestone workflow on 2026-07-14
## Outcome
Audit every merged M01 work package on current `master`, record final evidence,
close M01 and activate M02 only if all target verification and documentation
criteria pass.
## Non-goals
- Change coordinate, identity, streaming or renderer behavior.
- Start M02 implementation.
- Add new compatibility or visual-parity claims.
- Commit proprietary assets, generated UIDs, caches or capture output.
## Paths
- Exclusive: M01/M02 target status, M01 final Evidence and M01 claim acceptance markers
- Shared/hotspots: `targets/README.md`, `targets/DEVELOPMENT_ROADMAP.md`, this claim
- Generated/ignored: local Godot imports, renderer corpus and `user://` reports
## Contracts and data
- Public APIs, schemas, cache formats and migrations: unchanged
- Coordinate contract remains version 2
- Fixture schema remains version 1
- Consumers: milestone sequence and M02 work packages
## Dependencies
- Requires: all seven M01 READY commits integrated through master `c8e99f2`
- Blocks: M02 activation
- External state: server-spawn GUI hashes and original-client numeric evidence are recorded; proprietary files remain outside Git
## Verification
- Commands: all M01 domain/fixture/boundary/focus/identity/spawn gates,
unified renderer dry-run, coordination/documentation checks and diff hygiene
- Fidelity evidence: five build-12340 points, raw ADT metadata, pinned
AzerothCore spawn and cache-complete GUI marker capture
- Performance: no runtime change; preserve recorded renderer cold/warm evidence
## Documentation deliverables
- M01 final target Evidence and checklist
- M01 claim integration-acceptance markers
- Target index and roadmap transition to M02
- API/module specifications and diagrams are audited, not changed unless a gap is found
## Simplicity and naming
- Important names introduced: none beyond the closeout work-package ID
- Simplest approach: evidence/status-only integrator patch
- Rejected complexity: new one-off closeout framework or behavioral changes
- Unavoidable complexity: none
## Status
- State: ready-for-review
- Done: accepted seven M01 packages, reran post-merge gates, recorded complete
target Evidence, closed M01 and activated M02
- Next: merge closeout commit before starting an M02 implementation claim
- Blocked by:
## Handoff
- Commit: `dd412e6`
- Branch: `work/sindo-main-codex/m01-integrator-closeout`
- Outcome: M01 is `DONE` with all seven checklist items and complete Evidence;
M02 is the only `ACTIVE` target and `targets/README.md` points to it.
- Integration audit: all seven M01 READY commits are ancestors of master
`c8e99f2`; their claims now carry `OPENWC_INTEGRATION:ACCEPTED` markers.
- Verification: unified renderer dry-run passed project load, material/dedupe/
shutdown gates, manifest `7/7/7`, calibration maximum error `0.000015`,
coordinate boundary (`84` files) and server-spawn renderer `(32,48)/(3,12)`;
coordinate mapper, golden fixtures, StreamingFocus and domain identity gates
passed. Coordination and documentation gates passed; coordination reports
only five already-expired M00 claim warnings.
- Fidelity: closeout preserves five build-12340 coordinate observations, raw ADT
evidence, pinned AzerothCore spawn mapping and cache-complete Northshire GUI
evidence without claiming original-client visual parity or TrinityCore row
equivalence.
- Rebuild/migration: none. Coordinate contract remains version 2; fixture schema
remains version 1; renderer cache formats are unchanged.
- Remaining risks: recorded in M01 Evidence—TrinityCore populated spawn,
placement Euler parity, native parser boundary and broader renderer parity.
- Documentation: M01 target Evidence/checklist, target index and roadmap current
state updated. Existing coordinate, identity and renderer module specs/API/
diagrams were audited and passed the documentation gate.
@@ -1,6 +1,7 @@
# M01-QAR-SERVER-SPAWN-RENDERER-001 — Golden server spawn renderer evidence # M01-QAR-SERVER-SPAWN-RENDERER-001 — Golden server spawn renderer evidence
<!-- OPENWC_CLAIM:M01-QAR-SERVER-SPAWN-RENDERER-001:sindo-main-codex:2026-07-15 --> <!-- OPENWC_CLAIM:M01-QAR-SERVER-SPAWN-RENDERER-001:sindo-main-codex:2026-07-15 -->
<!-- OPENWC_INTEGRATION:ACCEPTED:M01-QAR-SERVER-SPAWN-RENDERER-001:c8e99f2 -->
## Ownership ## Ownership
@@ -79,7 +80,56 @@ ADT tile/chunk.
## Status ## Status
- State: active - State: ready-for-review
- Done: merged-package and target evidence audit - Done: fixture-to-renderer manifest/verifier, renderer-native marker, unified
- Next: implement manifest/verifier/marker, capture GUI evidence and hand off baseline integration, cold/warm GUI evidence, documentation and log cleanup
- Next: integrator review, merge cleanup/handoff and M01 closeout audit
- Blocked by: - Blocked by:
## Handoff
- Commits: implementation `24ddc99`; required cleanup `318e6ae`
- Branch: `work/sindo-main-codex/m01-server-spawn-renderer`
- Outcome: the pinned AzerothCore Human Warrior start is routed through
`CoordinateMapper` into the ordinary Eastern Kingdoms capture scene. A
depth-tested magenta marker begins at the exact mapped spawn origin.
- Contract/schema: additive diagnostic manifest schema 1 and marker metadata;
coordinate contract version 2, production renderer APIs, cache formats and
`Blizzlike335` behavior are unchanged.
- Verification: `SERVER_SPAWN_RENDERER PASS spawn=1 checkpoint=1 tile=32_48
chunk=3_12 profile=Blizzlike335`; unified renderer dry-run passed project,
materials, M2 dedupe, shutdown, `7/7/7` manifest, five-point calibration,
coordinate-boundary and server-spawn steps; coordinate golden fixtures,
mapper, StreamingFocus, domain identities, documentation and whitespace gates
passed. Coordination passed with five expired M00-claim warnings only.
- GUI evidence: two `1280x900` cold/warm PNGs captured under
`user://server_spawn_renderer_m01_gui_run2`; focus tile `(32,48)`, terrain
quality `100/100`, missing cache `0`, queues drained, report results `2`.
Human inspection confirms the marker is on rendered terrain beside the road
at Northshire Abbey rather than below terrain or in an adjacent tile. PNG
hashes: cold `c08c43bcb75e5b3277105afb0d4e5a76caae9db91a7db64488770ffa03e32589`,
warm `1b75237a090ea163836372a8e39e778c909773f4508063adc3f70a3e12a85a62`.
- Fidelity: source remains AzerothCore revision
`68fcf0098b16388093989627f37be530fc91f07d`; server
`(-8949.95,-132.493,83.5312)` maps to Godot
`(17199.159666667,83.5312,26016.616666667)`, ADT `(32,48)`, MCNK `(3,12)`.
This proves coordinate/renderer integration, not original-client visual parity
or TrinityCore populated-row equivalence.
- Runtime notes: D3D12 descriptor-heap initialization fell back to Vulkan;
optional ignored character/ambient GLB imports were unavailable. Neither
affected terrain/marker evidence. The known anonymous ObjectDB teardown
warning remained; no Node, renderer resource or RID leak diagnostic occurred
in the cache-complete run.
- Repository hygiene: four accidentally committed local GUI logs are removed by
`318e6ae`; root `*.stdout.log`/`*.stderr.log` files are now ignored. Generated
PNG/JSON evidence and proprietary corpus remain outside Git.
- Remaining risks: the diagnostic marker is not a production network entity;
live server adapters belong to M06/M08. Exact original-client character spawn
presentation and TrinityCore database equivalence remain unverified.
- Documentation: `RENDER.md`, renderer-baseline procedure, coordinate-mapping
and world-renderer module API/input-output/data-flow/capability/source-map
sections updated. No lifecycle/state diagram changed because the verifier and
mapper path are synchronous; capture reuses the documented renderer lifecycle.
- Recommended merge order: `318e6ae` after master merge `747fae8`. Then the M01
milestone integrator may accept all M01 claims, update target Evidence/checks
and evaluate `OPENWC_TARGET_DONE`.
@@ -1,7 +1,7 @@
# M01-RND-STREAMING-FOCUS-001 — Explicit streaming focus contract # M01-RND-STREAMING-FOCUS-001 — Explicit streaming focus contract
<!-- OPENWC_CLAIM:M01-RND-STREAMING-FOCUS-001:sindo-main-codex:2026-07-15 --> <!-- OPENWC_CLAIM:M01-RND-STREAMING-FOCUS-001:sindo-main-codex:2026-07-15 -->
<!-- OPENWC_HANDOFF:READY:M01-RND-STREAMING-FOCUS-001:7815385 --> <!-- OPENWC_INTEGRATION:ACCEPTED:M01-RND-STREAMING-FOCUS-001:c8e99f2 -->
## Ownership ## Ownership
+112
View File
@@ -0,0 +1,112 @@
# M02-GMP-INPUT-001 — Player input intent seam
<!-- OPENWC_CLAIM:M02-GMP-INPUT-001:sindo-main-codex:2026-07-16 -->
<!-- OPENWC_HANDOFF:READY:M02-GMP-INPUT-001:6bd2e84 -->
## Ownership
- Target: M02
- Program: GMP/FND
- Owner/Agent ID: sindo-main-codex
- Branch: `work/sindo-main-codex/m02-player-input`
- Lease expires UTC: 2026-07-16
- Integrator: M02 milestone integrator
## Outcome
Replace direct keyboard polling in the runtime player with remappable Input Map
actions and an immutable `PlayerInputSource → MoveIntent` boundary while
preserving the current render-sandbox movement behavior.
## Non-goals
- Extract local movement, terrain query, camera rig or character presenters.
- Claim exact build-12340 movement/keybinding parity before original-client fixtures.
- Change movement speeds, camera geometry, terrain sampling or renderer behavior.
- Mark M02 complete or edit its checklist/Evidence.
## Paths
- Exclusive: `src/domain/input/`, `src/gameplay/input/`,
`src/tests/scenes/player_input_regression.tscn`,
`src/tools/verify_player_input.gd`, `docs/modules/player-input.md`, this claim
- Shared/hotspots: `project.godot`,
`src/scenes/player/third_person_wow_controller.gd`, `docs/modules/README.md`
- Generated/ignored: Godot import metadata, `.uid` files and local renderer corpus
## Contracts and data
- Public API: immutable `MoveIntent`; `PlayerInputSource.sample_move_intent`
- Configuration: additive `openwc_player_*` Input Map actions
- Persistence/schema/cache: unchanged
- Consumers: current sandbox player controller; later local movement controller
## Dependencies
- Requires: merged M01 coordinate/streaming-focus contracts on master `62eece9`
- Blocks: later M02 movement-controller extraction
- External state: none; tests use synthetic Input Map events
## Verification
- Commands: dedicated player-input verifier, headless project load, coordinate
boundary gate, coordination/documentation gates and diff hygiene
- Fixtures: synthetic action strengths and configured default bindings
- Fidelity evidence: current W/S/A/D, Shift, E/Q and Space sandbox behavior remains
mapped through actions; exact 3.3.5a semantics remain unverified
- Performance budget: one immutable intent allocation per physics tick; no I/O
## Documentation deliverables
- Inline API documentation for `MoveIntent`, `PlayerInputActions` and
`PlayerInputSource`
- Player-input module specification with inputs/outputs, data-flow and sequence diagrams
- Module registry/source map and honest capability status
## Simplicity and naming
- Important names: `MoveIntent`, `PlayerInputSource`, `PlayerInputActions`
- Simplest approach: immutable value, one input adapter and one controller consumer
- Rejected complexity: input provider hierarchy, event bus and generic command framework
- Unavoidable complexity: none
- Measured optimization evidence: not applicable
## Status
- State: ready-for-review
- Done: twelve remappable actions, immutable `MoveIntent`, stateless
`PlayerInputSource`, controller migration, asset-free regression scene,
dedicated verifier and module specification
- Next: M02 integrator reviews and merges before local movement extraction
- Blocked by:
## Handoff
- Commit: `6bd2e84`
- Branch: `work/sindo-main-codex/m02-player-input`
- Outcome: the runtime player no longer polls physical keyboard keys. Movement
action strengths cross an immutable `MoveIntent` seam; camera and immediate
sandbox-flight events use the same remappable action catalog.
- Public contract: additive `MoveIntent`, `PlayerInputActions` and
`PlayerInputSource`; no schema, cache, coordinate or renderer contract change.
- Verification: `PLAYER_INPUT PASS actions=12 intent_cases=6 controller=1
regression_scene=1`; asset-free scene smoke exited `0`; coordinate boundary
(`88` files) and StreamingFocus gates passed; unified renderer dry-run passed
all eight pre-capture gates and produced seven dry-run checkpoint results;
coordination/documentation/diff gates passed.
- Fidelity: W/S/A/D, Shift, E/Q, Space, right mouse, wheel and Escape remain the
default sandbox bindings. Regression drives forward movement, camera zoom and
immediate flight toggle through the real controller. This does not establish
exact build-12340 default bindings, turn/strafe policy or gameplay movement parity.
- Local verification inputs: the isolated worktree reused the existing ignored
native DLL and generated ADT resource scripts. Proprietary extracted data and
the character GLB were absent, so the renderer dry-run logged the expected
missing-corpus diagnostics while all contract steps returned success.
- Rebuild/migration: none. Action names are additive; persisted user-binding
migration does not exist yet and is documented as a gap.
- Remaining risks: production profile gating for sprint/free flight, exact
3.3.5a bindings, local movement/terrain/camera/presentation extraction and
broader movement/camera state tests remain M02 work.
- Documentation: inline API comments plus `docs/modules/player-input.md` with
inputs/outputs, data-flow and sequence diagrams, ownership, recovery,
capability status, known gaps and source map.
+19 -1
View File
@@ -10,7 +10,10 @@
.\tools\run_render_baseline.ps1 .\tools\run_render_baseline.ps1
``` ```
Скрипт последовательно проверяет headless-загрузку проекта, renderer materials, M2 unique-id dedupe, regression manifest и затем снимает checkpoint-ы. Для CI или быстрой проверки контракта без PNG: Скрипт последовательно проверяет headless-загрузку проекта, renderer materials,
M2 unique-id dedupe, regression manifest, coordinate boundaries и dedicated
server-spawn renderer contract, затем снимает checkpoint-ы. Для CI или быстрой
проверки контракта без PNG:
```powershell ```powershell
.\tools\run_render_baseline.ps1 -DryRun -WaitSeconds 0.1 -MeasureSeconds 0.1 .\tools\run_render_baseline.ps1 -DryRun -WaitSeconds 0.1 -MeasureSeconds 0.1
@@ -18,6 +21,21 @@
Если `godot` отсутствует в `PATH`, скрипт использует локальный executable, указанный в `RENDER.md`. Другой executable задаётся через `-GodotPath`. Если `godot` отсутствует в `PATH`, скрипт использует локальный executable, указанный в `RENDER.md`. Другой executable задаётся через `-GodotPath`.
Отдельный M01 server-spawn checkpoint использует тот же capture pipeline, но не
расширяет семичастный M00 visual baseline:
```powershell
godot --headless --path . --script res://src/tools/verify_server_spawn_renderer.gd
godot --path . --script res://src/tools/capture_render_checkpoints.gd -- `
--manifest res://src/tools/server_spawn_render_manifest.json `
--output user://server_spawn_renderer
```
Manifest связывает pinned AzerothCore Human Warrior spawn с Godot position,
ADT tile/chunk и renderer-native diagnostic marker. Маркер начинается точно в
spawn origin и проходит обычный depth test, поэтому screenshot показывает его
отношение к terrain; это integration evidence, а не заявление visual parity.
Результат по умолчанию находится в `user://render_baseline`: Результат по умолчанию находится в `user://render_baseline`:
- `report.json` — Godot, OS, CPU, GPU/backend, viewport, revision, cache inventory, cache versions, параметры checkpoint-ов и результаты; - `report.json` — Godot, OS, CPU, GPU/backend, viewport, revision, cache inventory, cache versions, параметры checkpoint-ов и результаты;
+1
View File
@@ -9,6 +9,7 @@
| Foundation/data | Planned/Partial | [`../../targets/roadmap/01-foundation-and-data.md`](../../targets/roadmap/01-foundation-and-data.md) | | Foundation/data | Planned/Partial | [`../../targets/roadmap/01-foundation-and-data.md`](../../targets/roadmap/01-foundation-and-data.md) |
| Coordinate mapping | Implemented | [`coordinate-mapping.md`](coordinate-mapping.md) | | Coordinate mapping | Implemented | [`coordinate-mapping.md`](coordinate-mapping.md) |
| Domain identities | Implemented contract | [`domain-identities.md`](domain-identities.md) | | Domain identities | Implemented contract | [`domain-identities.md`](domain-identities.md) |
| Player input | Partial | [`player-input.md`](player-input.md) |
| Renderer | Partial | [`world-renderer.md`](world-renderer.md), [`../../RENDER.md`](../../RENDER.md) | | Renderer | Partial | [`world-renderer.md`](world-renderer.md), [`../../RENDER.md`](../../RENDER.md) |
| Network/session | Planned | [`../../targets/roadmap/03-network-and-session.md`](../../targets/roadmap/03-network-and-session.md) | | Network/session | Planned | [`../../targets/roadmap/03-network-and-session.md`](../../targets/roadmap/03-network-and-session.md) |
| Gameplay | Prototype/Planned | [`../../targets/roadmap/04-gameplay-systems.md`](../../targets/roadmap/04-gameplay-systems.md) | | Gameplay | Prototype/Planned | [`../../targets/roadmap/04-gameplay-systems.md`](../../targets/roadmap/04-gameplay-systems.md) |
+14 -4
View File
@@ -5,9 +5,9 @@
| Field | Value | | Field | Value |
|---|---| |---|---|
| Status | Implemented | | Status | Implemented |
| Target/work package | `M01-FND-COORDS-001`, `M01-FND-COORD-TILE-AXIS-002`, `M01-QAR-COORD-FIXTURES-001`, `M01-FND-COORD-BOUNDARY-GATE-001` | | Target/work package | `M01-FND-COORDS-001`, `M01-FND-COORD-TILE-AXIS-002`, `M01-QAR-COORD-FIXTURES-001`, `M01-FND-COORD-BOUNDARY-GATE-001`, `M01-QAR-SERVER-SPAWN-RENDERER-001` |
| Owners | Foundation/domain | | Owners | Foundation/domain |
| Last verified | Worktree `work/sindo-main-codex/m01-coordinate-boundary-gate`, 2026-07-13 | | Last verified | Worktree `work/sindo-main-codex/m01-server-spawn-renderer`, 2026-07-13 |
| Profiles/capabilities | All profiles; contract version 2 | | Profiles/capabilities | All profiles; contract version 2 |
## Purpose ## Purpose
@@ -103,6 +103,10 @@ flowchart LR
RawADTFixture[Raw ADT numeric metadata] --> FixtureVerifier RawADTFixture[Raw ADT numeric metadata] --> FixtureVerifier
ClientFixture[Build 12340 observations] --> FixtureVerifier ClientFixture[Build 12340 observations] --> FixtureVerifier
FixtureVerifier --> Mapper FixtureVerifier --> Mapper
ServerFixture --> SpawnManifest[Server spawn render manifest]
SpawnManifest --> SpawnVerifier[Renderer checkpoint verifier]
SpawnVerifier --> Capture[Live renderer capture]
Capture --> Marker[Diagnostic marker at mapped spawn]
Server[ServerWorldPosition X,Y,Z] -->|identity components| Canonical[CanonicalWowWorldPosition] Server[ServerWorldPosition X,Y,Z] -->|identity components| Canonical[CanonicalWowWorldPosition]
ADT[AdtPlacementPosition X,height,Z] -->|half extent - horizontal| Canonical ADT[AdtPlacementPosition X,height,Z] -->|half extent - horizontal| Canonical
Canonical -->|tileX from Y; tileY from X| Tile[AdtTileCoordinate] Canonical -->|tileX from Y; tileY from X| Tile[AdtTileCoordinate]
@@ -179,8 +183,12 @@ raw three-number arrays are not an accepted new persistence contract.
- Unit/contract tests: `src/tools/verify_coordinate_mapper.gd`. - Unit/contract tests: `src/tools/verify_coordinate_mapper.gd`.
- Cross-source golden test: `src/tools/verify_coordinate_golden_fixtures.gd`. - Cross-source golden test: `src/tools/verify_coordinate_golden_fixtures.gd`.
- Boundary enforcement: `src/tools/verify_coordinate_conversion_boundaries.gd`. - Boundary enforcement: `src/tools/verify_coordinate_conversion_boundaries.gd`.
- Server-spawn renderer contract: `src/tools/verify_server_spawn_renderer.gd`
validates the dedicated manifest against the same pinned fixture before GUI capture.
- Integration/E2E: sky, player, streamer and terrain probe use mapper boundaries; - Integration/E2E: sky, player, streamer and terrain probe use mapper boundaries;
`StreamingFocus` provides typed renderer focus. `StreamingFocus` provides typed renderer focus, and the pinned AzerothCore
Human Warrior spawn is captured in the Eastern Kingdoms renderer at ADT
`(32,48)`, chunk `(3,12)`.
- Fidelity evidence: five build-12340 camera positions from the M00 manifest map - Fidelity evidence: five build-12340 camera positions from the M00 manifest map
within `0.002` yard; a pinned AzerothCore human-warrior spawn and one raw MCNK within `0.002` yard; a pinned AzerothCore human-warrior spawn and one raw MCNK
header observation independently confirm tile `(32,48)` and chunk `(3,12)`; header observation independently confirm tile `(32,48)` and chunk `(3,12)`;
@@ -207,7 +215,7 @@ raw three-number arrays are not an accepted new persistence contract.
| Scene-free production contract | Implemented | Source boundary, headless tests and repository-wide conversion gate | Maintain gate with new consumers | | Scene-free production contract | Implemented | Source boundary, headless tests and repository-wide conversion gate | Maintain gate with new consumers |
| Cross-source coordinate fixture | Implemented | Pinned AzerothCore spawn, raw ADT MCNK metadata and five build-12340 points pass one headless verifier | Add live adapter/renderer integration | | Cross-source coordinate fixture | Implemented | Pinned AzerothCore spawn, raw ADT MCNK metadata and five build-12340 points pass one headless verifier | Add live adapter/renderer integration |
| TrinityCore populated spawn equivalence | Not verified | The base development schema has no pinned populated row used by this fixture | Pin and verify a TDB snapshot before claiming parity | | TrinityCore populated spawn equivalence | Not verified | The base development schema has no pinned populated row used by this fixture | Pin and verify a TDB snapshot before claiming parity |
| Visual/server-spawn integration | Planned | M01 target criteria | Route the fixture through migrated adapters and renderer | | Visual/server-spawn integration | Implemented | Dedicated manifest/verifier plus cold/warm GUI capture of the mapped AzerothCore spawn | This is coordinate/renderer integration evidence, not original-client visual parity |
## Known gaps and risks ## Known gaps and risks
@@ -235,6 +243,8 @@ raw three-number arrays are not an accepted new persistence contract.
| `src/tests/fixtures/coordinate_golden_points.json` | Versioned cross-source values, provenance and tolerances | | `src/tests/fixtures/coordinate_golden_points.json` | Versioned cross-source values, provenance and tolerances |
| `src/tools/verify_coordinate_golden_fixtures.gd` | Cross-source fixture schema and mapper validation | | `src/tools/verify_coordinate_golden_fixtures.gd` | Cross-source fixture schema and mapper validation |
| `src/tools/verify_coordinate_conversion_boundaries.gd` | Repository-wide manual conversion and required-consumer gate | | `src/tools/verify_coordinate_conversion_boundaries.gd` | Repository-wide manual conversion and required-consumer gate |
| `src/tools/server_spawn_render_manifest.json` | Pinned server-spawn camera, player, marker and expected ADT ownership |
| `src/tools/verify_server_spawn_renderer.gd` | Fixture-to-renderer manifest contract verifier |
| `docs/adr/0001-canonical-world-coordinates.md` | Normative axes, units, boundaries and rollout decision | | `docs/adr/0001-canonical-world-coordinates.md` | Normative axes, units, boundaries and rollout decision |
## Related decisions and references ## Related decisions and references
+211
View File
@@ -0,0 +1,211 @@
# Player Input
## Metadata
| Field | Value |
|---|---|
| Status | Partial |
| Target/work package | M02 / M02-GMP-INPUT-001 |
| Owners | Gameplay input boundary; `sindo-main-codex` for current package |
| Last verified | `6bd2e84`, 2026-07-14 |
| Profiles/capabilities | Current render-sandbox defaults; `Blizzlike335` binding semantics not yet verified |
## Purpose
Convert remappable Godot Input Map actions into an immutable, scene-free
`MoveIntent` that can be consumed by the current player controller and a later
independently testable local movement controller.
## Non-goals
- Define authoritative server movement, prediction or reconciliation.
- Own character transform, terrain queries, camera state or animation state.
- Claim that the current W/S/A/D sandbox mapping exactly matches build 12340.
- Make sprint or free flight available to the production compatibility profile;
their profile gate remains a later M02 package.
## Context and boundaries
```mermaid
flowchart LR
Devices[Keyboard and mouse] --> InputMap[Godot Input Map]
InputMap --> Source[PlayerInputSource]
Source --> Intent[MoveIntent]
Intent --> Controller[ThirdPersonWowController]
Controller --> Movement[Current sandbox movement]
Controller --> Camera[Current camera adapter]
```
Allowed dependencies:
- `PlayerInputSource` may read Godot's process-wide `Input` adapter.
- The scene controller may consume `MoveIntent` and camera-specific action names.
- `MoveIntent` may contain scalar axes and request flags only.
Forbidden dependencies:
- `MoveIntent` must not read `Input`, inherit `Node`/`Resource`, or contain world coordinates.
- The input source must not mutate player transforms, camera nodes or animation state.
- Renderer, terrain parsers and network codecs must not read Input Map actions.
## Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `MoveIntent` | Immutable value | Carries normalized forward, strafe and vertical axes plus the sandbox sprint request | Created and consumed on the main physics tick; caller retains reference | Constructor clamps axes; no I/O failure |
| `MoveIntent.has_translation()` | Query | Reports whether any translation axis is non-zero | Any thread while value remains immutable | None |
| `PlayerInputActions` | Constants | Provides stable action names shared by configuration and adapters | Process lifetime | Missing project actions are detected by verification |
| `PlayerInputSource.sample_move_intent()` | Adapter method | Samples configured actions for one physics tick | Main thread; returned intent is independent of adapter lifetime | Missing actions resolve to zero strength and are a configuration defect |
| `PlayerInputSource.compose_move_intent(...)` | Pure factory | Clamps strengths, cancels opposites and normalizes diagonal planar input | Any thread with scalar inputs | None |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Movement action strengths | Godot `InputMap`/`Input` | `PlayerInputSource` | Engine-owned snapshot | Sampled on main physics tick |
| Input | Mouse action events | Godot `_unhandled_input` dispatch | Current scene controller | Engine-owned event | Current input dispatch only |
| Output | `MoveIntent` | `PlayerInputSource` | Current controller; later local movement controller | Immutable caller-held value | One physics tick |
| Output | Camera action names | `PlayerInputActions` | Current scene controller | Static constants | Process lifetime |
Side effects:
- `PlayerInputSource` has no side effects beyond reading current engine input.
- The existing controller still mutates player/camera transforms and mouse mode;
those responsibilities are intentionally not moved by this package.
- No filesystem, cache, network, database or renderer-resource mutation occurs.
## Data flow
```mermaid
flowchart LR
Project[project.godot action defaults] --> Map[InputMap]
Remap[User or editor remapping] --> Map
Map -->|movement strengths| Source[PlayerInputSource]
Source -->|clamp cancel normalize| Intent[MoveIntent]
Intent --> Consumer[ThirdPersonWowController]
```
## Lifecycle/state
The adapter is stateless. The scene composition root creates one
`PlayerInputSource` in `_ready`; each physics tick produces a new immutable
intent. Flight enabled/disabled state remains owned by the current sandbox
controller and is not hidden in the input adapter.
## Main sequence
```mermaid
sequenceDiagram
participant Engine as Godot Input
participant Source as PlayerInputSource
participant Intent as MoveIntent
participant Player as ThirdPersonWowController
Engine->>Source: movement action strengths
Source->>Intent: compose normalized axes and requests
Source-->>Player: sample_move_intent()
Player->>Player: update current sandbox movement state
```
## Ownership, threading and resources
- The controller owns the input-source reference for its scene lifetime.
- Each `MoveIntent` is immutable and retained only by its consumer.
- Sampling and controller mutation occur on the main physics thread.
- The module creates no Nodes, Resources, RIDs, workers or background jobs.
- Input Map definitions are project configuration; runtime remapping remains
engine-owned and does not mutate the domain value.
## Errors, cancellation and recovery
| Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Required action missing | Headless contract verifier | Godot returns zero input for that action | `PLAYER_INPUT` failure naming action | Restore action or user mapping |
| Conflicting opposite actions | Pure composition | Strengths cancel deterministically | Covered by contract test | Release/remap one action |
| Strength outside range | Pure composition | Value clamps to `[0, 1]` before axis calculation | Covered by contract test | Input adapter may be corrected independently |
| Controller disabled/unloaded | Scene lifecycle | Sampling stops with physics processing | Existing scene lifecycle | Re-enable or recreate scene |
There is no asynchronous operation to cancel and no persisted state to roll back.
## Configuration and capabilities
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|---|---|---|---|---|
| Forward/backward | Physical W/S | Current render sandbox | Yes, through Input Map | Produces signed forward axis |
| Strafe left/right | Physical A/D | Current render sandbox | Yes | Produces signed right axis |
| Camera rotate | Right mouse button | Current render sandbox | Yes | Captures/releases mouse for orbit |
| Camera zoom | Mouse wheel | Current render sandbox | Yes | Adjusts current camera distance |
| Debug sprint | Shift | Sandbox debug only by intent; profile gate pending | Yes | Requests existing speed multiplier |
| Debug flight up/down/toggle | E/Q/Space | Sandbox debug only by intent; profile gate pending | Yes | Requests existing free-flight controls |
## Persistence, cache and migration
The action names are additive project configuration. No saved binding schema,
cache or migration exists yet. Renaming an action is a contract change and must
provide a settings migration once user settings are persisted.
## Diagnostics and observability
- Logs: dedicated verifier emits `PLAYER_INPUT PASS` or one failure per invariant.
- Metrics: none; sampling cost is one small value allocation per physics tick.
- Debug views: none.
- Correlation IDs: not applicable to local synchronous input.
## Verification
- Unit/contract tests: `src/tools/verify_player_input.gd` validates all actions,
default bindings, axis cancellation/clamping/normalization and debug request flags.
- Integration/E2E: `src/tests/scenes/player_input_regression.tscn` is loaded by
the verifier, which drives forward, camera-zoom and immediate sandbox-flight
actions through the real scene controller and also rejects direct
physical-key polling.
- Fidelity evidence: current sandbox defaults retain W/S/A/D, E/Q, Shift, Space,
right mouse and wheel bindings. This is observable-regression coverage only.
- Performance budgets: no I/O, jobs or scene lookup per sample; one immutable
intent allocation per physics tick.
- Manual diagnostics: run the Eastern Kingdoms scene and remap one movement
action in Project Settings before checking movement.
## Extension points
- A gamepad/accessibility adapter may call `compose_move_intent` without changing
movement consumers.
- A later input-context owner may suppress gameplay actions while preserving the
same intent contract.
- The local movement controller may replace the current scene consumer without
changing `PlayerInputSource`.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Remappable current sandbox controls | Implemented | `verify_player_input.gd` action/default checks | Add persisted user binding settings later |
| `PlayerInputSource → MoveIntent` seam | Implemented | Pure composition and controller boundary checks | Extract local movement controller |
| Exact 3.3.5a default semantics | Unknown | No original-client binding fixture in this package | Capture build-12340 defaults and mouse-turn/strafe policy |
| Production profile exclusion of sprint/flight | Partial | Debug actions are named explicitly | Add typed profile/capability gate in M02 |
| Jump/fall/swim/fly gameplay semantics | Planned | M02 target and gameplay roadmap | Implement after terrain/movement state extraction |
## Known gaps and risks
- The defaults preserve the pre-M02 sandbox behavior, not verified original-client semantics.
- Sprint and free flight are still executable by the sandbox controller until a
typed build-profile gate is introduced.
- Camera input is action-mapped but camera state/collision remains in the monolithic controller.
- There is no persisted user keybinding format or UI yet.
## Source map
| Path | Responsibility |
|---|---|
| `project.godot` | Additive default Input Map bindings |
| `src/domain/input/move_intent.gd` | Immutable locomotion request contract |
| `src/gameplay/input/player_input_actions.gd` | Stable project action names |
| `src/gameplay/input/player_input_source.gd` | Engine Input Map adapter and pure composition |
| `src/scenes/player/third_person_wow_controller.gd` | Current consumer and camera-event adapter |
| `src/tests/scenes/player_input_regression.tscn` | Asset-free controller movement/camera regression fixture |
| `src/tools/verify_player_input.gd` | Headless contract/integration regression |
## Related decisions and references
- ADR: none; this implements the existing architecture and M02 seam.
- Upstream/reference: `targets/02-player-decomposition.md`,
`targets/roadmap/04-gameplay-systems.md`, `docs/ARCHITECTURE.md`.
+15 -3
View File
@@ -5,9 +5,9 @@
| Field | Value | | Field | Value |
|---|---| |---|---|
| Status | Partial | | Status | Partial |
| Target/work package | M00 baseline; `M01-RND-STREAMING-FOCUS-001`; декомпозиция M02–M03 | | Target/work package | M00 baseline; `M01-RND-STREAMING-FOCUS-001`; `M01-QAR-SERVER-SPAWN-RENDERER-001`; декомпозиция M02–M03 |
| Owners | Renderer workstream / milestone integrator | | Owners | Renderer workstream / milestone integrator |
| Last verified | Worktree `work/sindo-main-codex/m01-streaming-focus`, 2026-07-13 | | Last verified | Worktree `work/sindo-main-codex/m01-server-spawn-renderer`, 2026-07-13 |
| Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete | | Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete |
## Purpose ## Purpose
@@ -29,6 +29,8 @@ flowchart LR
Player[Player or spectator Node3D] --> Adapter[Explicit source adapter] Player[Player or spectator Node3D] --> Adapter[Explicit source adapter]
Editor[Editor viewport adapter] --> Focus[StreamingFocus] Editor[Editor viewport adapter] --> Focus[StreamingFocus]
Capture[Capture tool] --> Adapter Capture[Capture tool] --> Adapter
SpawnFixture[Pinned server spawn fixture] --> SpawnManifest[Spawn render manifest]
SpawnManifest --> Capture
Adapter --> Focus Adapter --> Focus
Focus --> Loader[StreamingWorldLoader] Focus --> Loader[StreamingWorldLoader]
Assets[Extracted WDT/ADT/M2/WMO/BLP] --> Native[Native loaders] Assets[Extracted WDT/ADT/M2/WMO/BLP] --> Native[Native loaders]
@@ -84,9 +86,11 @@ Forbidden dependencies:
| Input | Map/configuration | Scene/app/editor | `StreamingWorldLoader` | Caller config, copied/read | Main thread/session | | Input | Map/configuration | Scene/app/editor | `StreamingWorldLoader` | Caller config, copied/read | Main thread/session |
| Input | WDT/ADT/M2/WMO/BLP bytes | Extracted asset repository | Native loaders/builders | Loader result owns parsed data | Worker or controlled load | | Input | WDT/ADT/M2/WMO/BLP bytes | Extracted asset repository | Native loaders/builders | Loader result owns parsed data | Worker or controlled load |
| Input | Baked resources | Cache build tools | Streamer/builders | Shared immutable resource/cache entry | Session/cache lifetime | | Input | Baked resources | Cache build tools | Streamer/builders | Shared immutable resource/cache entry | Session/cache lifetime |
| Test input | Server-spawn render manifest | Coordinate fixture/verifier | Checkpoint capture tool | Repository-owned diagnostic data | Test-process lifetime |
| Output | Desired tile/detail operations | Streaming planner inside loader | Finalize queues | Loader-owned | Cross-frame | | Output | Desired tile/detail operations | Streaming planner inside loader | Finalize queues | Loader-owned | Cross-frame |
| Output | Terrain/M2/WMO/liquid instances | Loader/builders | Godot world/renderer | Loader/world owner | Main-thread attach | | Output | Terrain/M2/WMO/liquid instances | Loader/builders | Godot world/renderer | Loader/world owner | Main-thread attach |
| Output | Metrics/diagnostics | Hitch/stat instrumentation | Logs/baseline tools | Immutable records | Periodic/frame | | Output | Metrics/diagnostics | Hitch/stat instrumentation | Logs/baseline tools | Immutable records | Periodic/frame |
| Test output | Diagnostic spawn marker and cold/warm PNGs | Checkpoint capture tool | Human/integrator review | SceneTree marker; `user://` files | One capture process |
Side effects: Side effects:
@@ -102,6 +106,9 @@ Side effects:
flowchart TD flowchart TD
P[Player/spectator Node3D] --> A[Explicit scene adapter] P[Player/spectator Node3D] --> A[Explicit scene adapter]
C[Capture camera] --> A C[Capture camera] --> A
SF[Pinned server spawn] --> SM[Validated spawn manifest]
SM --> C
SM --> Marker[Renderer-native origin marker]
E[Editor viewport] --> EA[Editor adapter] E[Editor viewport] --> EA[Editor adapter]
A --> F[StreamingFocus] A --> F[StreamingFocus]
EA --> F EA --> F
@@ -119,6 +126,7 @@ flowchart TD
M2 --> World M2 --> World
WMO --> World WMO --> World
Liquid --> World Liquid --> World
Marker --> World
F --> E[Eviction/retention decisions] F --> E[Eviction/retention decisions]
E --> World E --> World
``` ```
@@ -224,7 +232,8 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
## Verification ## Verification
- Unit/contract tests: streaming-focus contract/wiring, material mapping, unique-ID dedupe, placement probes, baseline manifest, five-point coordinate calibration, synthetic perceptual checkpoint diff, camera-pose grid plan. - Unit/contract tests: streaming-focus contract/wiring, material mapping, unique-ID dedupe, placement probes, baseline manifest, five-point coordinate calibration, synthetic perceptual checkpoint diff, camera-pose grid plan.
- Integration/E2E: Eastern Kingdoms/Kalimdor streaming scenes and seven cold/warm checkpoints. - Integration/E2E: Eastern Kingdoms/Kalimdor streaming scenes, seven M00
cold/warm checkpoints and one dedicated mapped server-spawn checkpoint.
- Fidelity evidence: семь локальных build 12340 reference JPG покрывают terrain/ADT/M2/WMO/liquid/sky и реальный `GryphonRoost01`; automated cold/warm metrics имеют `compared=14`, `missing=0`. Sky использует приблизительно paired camera, а animated-M2 остаётся asset-paired/placement-unpaired из-за synthetic probe. - Fidelity evidence: семь локальных build 12340 reference JPG покрывают terrain/ADT/M2/WMO/liquid/sky и реальный `GryphonRoost01`; automated cold/warm metrics имеют `compared=14`, `missing=0`. Sky использует приблизительно paired camera, а animated-M2 остаётся asset-paired/placement-unpaired из-за synthetic probe.
- Performance budgets: M00 report records cold/warm p95 and max hitch; no final acceptance threshold yet. - Performance budgets: M00 report records cold/warm p95 and max hitch; no final acceptance threshold yet.
- Manual diagnostics: [`../RENDER_BASELINE.md`](../RENDER_BASELINE.md) and [`../../RENDER.md`](../../RENDER.md). - Manual diagnostics: [`../RENDER_BASELINE.md`](../RENDER_BASELINE.md) and [`../../RENDER.md`](../../RENDER.md).
@@ -247,6 +256,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| Sky/light | Partial | DBC controller/logs | Zone transition paired validation | | Sky/light | Partial | DBC controller/logs | Zone transition paired validation |
| Character presentation | Prototype | Character creator/player experiment | Full customization/equipment states | | Character presentation | Prototype | Character creator/player experiment | Full customization/equipment states |
| Camera-independent streaming focus | Implemented | Typed contract, two player-focused runtime scenes and three explicitly camera-focused diagnostic tools | Multi-focus/direction/velocity belong to planner extraction | | Camera-independent streaming focus | Implemented | Typed contract, two player-focused runtime scenes and three explicitly camera-focused diagnostic tools | Multi-focus/direction/velocity belong to planner extraction |
| Golden server-spawn visualization | Implemented | Pinned AzerothCore fixture, validated renderer manifest and diagnostic origin-marker capture at ADT `(32,48)`/MCNK `(3,12)` | No TrinityCore row or original-client visual-parity claim |
| Stable renderer facade | Planned | M03 | Current caller uses monolithic Node API | | Stable renderer facade | Planned | M03 | Current caller uses monolithic Node API |
## Known gaps and risks ## Known gaps and risks
@@ -287,6 +297,8 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `src/tools/compare_render_checkpoints.gd` | Offline JPG/PNG paired-image perceptual metrics and JSON pass/fail report | | `src/tools/compare_render_checkpoints.gd` | Offline JPG/PNG paired-image perceptual metrics and JSON pass/fail report |
| `src/tools/verify_render_runtime_cache_shutdown.gd` | Headless ownership regression for detached runtime prototypes, resource caches and empty liquid roots | | `src/tools/verify_render_runtime_cache_shutdown.gd` | Headless ownership regression for detached runtime prototypes, resource caches and empty liquid roots |
| `src/tools/capture_render_checkpoints.gd` | Deterministic no-roll checkpoint camera, performance and visual capture | | `src/tools/capture_render_checkpoints.gd` | Deterministic no-roll checkpoint camera, performance and visual capture |
| `src/tools/server_spawn_render_manifest.json` | Dedicated mapped AzerothCore spawn checkpoint and diagnostic marker configuration |
| `src/tools/verify_server_spawn_renderer.gd` | Fixture-to-renderer checkpoint contract verification |
| `tools/sweep_render_checkpoint_camera_pose.ps1` | Offline yaw/pitch capture grid and paired-error ranking | | `tools/sweep_render_checkpoint_camera_pose.ps1` | Offline yaw/pitch capture grid and paired-error ranking |
| `src/tools/verify_render_coordinate_calibration.gd` | Build 12340 camera-coordinate golden point round-trip diagnostic | | `src/tools/verify_render_coordinate_calibration.gd` | Build 12340 camera-coordinate golden point round-trip diagnostic |
| `src/tools/probe_render_terrain_height.gd` | Offline active-mesh terrain height and camera-clearance report | | `src/tools/probe_render_terrain_height.gd` | Offline active-mesh terrain height and camera-clearance report |
+63
View File
@@ -19,6 +19,69 @@ config/icon="res://icon.svg"
enabled=PackedStringArray("res://addons/mpq_extractor/plugin.cfg") enabled=PackedStringArray("res://addons/mpq_extractor/plugin.cfg")
[input]
openwc_player_move_forward={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
]
}
openwc_player_move_backward={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
]
}
openwc_player_strafe_left={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
]
}
openwc_player_strafe_right={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
]
}
openwc_player_debug_fly_up={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
]
}
openwc_player_debug_fly_down={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":81,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null)
]
}
openwc_player_debug_sprint={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194325,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
openwc_player_debug_toggle_flight={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":32,"key_label":32,"unicode":32,"location":0,"echo":false,"script":null)
]
}
openwc_player_camera_rotate={
"deadzone": 0.5,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":2,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
openwc_player_camera_zoom_in={
"deadzone": 0.5,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":4,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
openwc_player_camera_zoom_out={
"deadzone": 0.5,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":5,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
openwc_player_release_cursor={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194305,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
[physics] [physics]
3d/physics_engine="Jolt Physics" 3d/physics_engine="Jolt Physics"
+47
View File
@@ -0,0 +1,47 @@
class_name MoveIntent
extends RefCounted
## Immutable player locomotion request expressed in character-local axes.
## Axis values are normalized to `[-1, 1]`: forward/right/up are positive.
## The intent contains no world coordinates, engine input events or presentation state.
var forward_axis: float:
get:
return _forward_axis
var strafe_axis: float:
get:
return _strafe_axis
var vertical_axis: float:
get:
return _vertical_axis
var is_sprint_requested: bool:
get:
return _is_sprint_requested
var _forward_axis: float
var _strafe_axis: float
var _vertical_axis: float
var _is_sprint_requested: bool
## Creates one frame's movement request without retaining mutable input state.
func _init(
forward_axis_value: float = 0.0,
strafe_axis_value: float = 0.0,
vertical_axis_value: float = 0.0,
sprint_requested: bool = false
) -> void:
_forward_axis = clampf(forward_axis_value, -1.0, 1.0)
_strafe_axis = clampf(strafe_axis_value, -1.0, 1.0)
_vertical_axis = clampf(vertical_axis_value, -1.0, 1.0)
_is_sprint_requested = sprint_requested
## Returns true when the intent requests planar or vertical translation.
func has_translation() -> bool:
return not is_zero_approx(_forward_axis) \
or not is_zero_approx(_strafe_axis) \
or not is_zero_approx(_vertical_axis)
+1
View File
@@ -0,0 +1 @@
uid://btk7brcavbeec
@@ -0,0 +1,34 @@
class_name PlayerInputActions
extends RefCounted
## Stable Input Map action names consumed by the runtime player input adapter.
## Defaults preserve the current render-sandbox controls; users may remap every
## action through Godot's Input Map without changing gameplay code.
const MOVE_FORWARD := &"openwc_player_move_forward"
const MOVE_BACKWARD := &"openwc_player_move_backward"
const STRAFE_LEFT := &"openwc_player_strafe_left"
const STRAFE_RIGHT := &"openwc_player_strafe_right"
const DEBUG_FLY_UP := &"openwc_player_debug_fly_up"
const DEBUG_FLY_DOWN := &"openwc_player_debug_fly_down"
const DEBUG_SPRINT := &"openwc_player_debug_sprint"
const DEBUG_TOGGLE_FLIGHT := &"openwc_player_debug_toggle_flight"
const CAMERA_ROTATE := &"openwc_player_camera_rotate"
const CAMERA_ZOOM_IN := &"openwc_player_camera_zoom_in"
const CAMERA_ZOOM_OUT := &"openwc_player_camera_zoom_out"
const RELEASE_CURSOR := &"openwc_player_release_cursor"
const REQUIRED_ACTIONS: Array[StringName] = [
MOVE_FORWARD,
MOVE_BACKWARD,
STRAFE_LEFT,
STRAFE_RIGHT,
DEBUG_FLY_UP,
DEBUG_FLY_DOWN,
DEBUG_SPRINT,
DEBUG_TOGGLE_FLIGHT,
CAMERA_ROTATE,
CAMERA_ZOOM_IN,
CAMERA_ZOOM_OUT,
RELEASE_CURSOR,
]
@@ -0,0 +1 @@
uid://bbhtyxm428bkm
+45
View File
@@ -0,0 +1,45 @@
class_name PlayerInputSource
extends RefCounted
## Converts remappable Godot Input Map actions into immutable [MoveIntent] values.
## This adapter owns no movement state and may be replaced independently of the
## local movement controller.
## Samples the current Input singleton state for one physics tick.
func sample_move_intent() -> MoveIntent:
return compose_move_intent(
Input.get_action_strength(PlayerInputActions.MOVE_FORWARD),
Input.get_action_strength(PlayerInputActions.MOVE_BACKWARD),
Input.get_action_strength(PlayerInputActions.STRAFE_LEFT),
Input.get_action_strength(PlayerInputActions.STRAFE_RIGHT),
Input.get_action_strength(PlayerInputActions.DEBUG_FLY_UP),
Input.get_action_strength(PlayerInputActions.DEBUG_FLY_DOWN),
Input.is_action_pressed(PlayerInputActions.DEBUG_SPRINT)
)
## Builds a normalized intent from action strengths for deterministic tests and
## alternative input adapters. Strengths outside `[0, 1]` are clamped.
static func compose_move_intent(
forward_strength: float,
backward_strength: float,
strafe_left_strength: float,
strafe_right_strength: float,
fly_up_strength: float,
fly_down_strength: float,
sprint_requested: bool
) -> MoveIntent:
var planar_axis := Vector2(
clampf(strafe_right_strength, 0.0, 1.0) - clampf(strafe_left_strength, 0.0, 1.0),
clampf(forward_strength, 0.0, 1.0) - clampf(backward_strength, 0.0, 1.0)
)
if planar_axis.length_squared() > 1.0:
planar_axis = planar_axis.normalized()
var vertical_axis := clampf(fly_up_strength, 0.0, 1.0) - clampf(fly_down_strength, 0.0, 1.0)
return MoveIntent.new(
planar_axis.y,
planar_axis.x,
vertical_axis,
sprint_requested
)
@@ -0,0 +1 @@
uid://b050i2lnei8qq
@@ -7,6 +7,8 @@ const COORDINATE_MAPPER_SCRIPT := preload("res://src/domain/coordinates/coordina
const GODOT_WORLD_POSITION_SCRIPT := preload("res://src/domain/coordinates/godot_world_position.gd") const GODOT_WORLD_POSITION_SCRIPT := preload("res://src/domain/coordinates/godot_world_position.gd")
const ADT_TILE_COORDINATE_SCRIPT := preload("res://src/domain/coordinates/adt_tile_coordinate.gd") const ADT_TILE_COORDINATE_SCRIPT := preload("res://src/domain/coordinates/adt_tile_coordinate.gd")
const ADT_TILE_LOCAL_POSITION_SCRIPT := preload("res://src/domain/coordinates/adt_tile_local_position.gd") const ADT_TILE_LOCAL_POSITION_SCRIPT := preload("res://src/domain/coordinates/adt_tile_local_position.gd")
const PLAYER_INPUT_SOURCE_SCRIPT := preload("res://src/gameplay/input/player_input_source.gd")
const PLAYER_INPUT_ACTIONS_SCRIPT := preload("res://src/gameplay/input/player_input_actions.gd")
const CHUNK_SIZE := COORDINATE_MAPPER_SCRIPT.ADT_CHUNK_SIZE_YARDS const CHUNK_SIZE := COORDINATE_MAPPER_SCRIPT.ADT_CHUNK_SIZE_YARDS
const UNIT_SIZE := CHUNK_SIZE / 8.0 const UNIT_SIZE := CHUNK_SIZE / 8.0
@@ -55,9 +57,11 @@ var _pitch := deg_to_rad(-18.0)
var _horizontal_velocity := Vector3.ZERO var _horizontal_velocity := Vector3.ZERO
var _flight_enabled := false var _flight_enabled := false
var _adt_cache: Dictionary = {} var _adt_cache: Dictionary = {}
var _player_input_source: PlayerInputSource
func _ready() -> void: func _ready() -> void:
_player_input_source = PLAYER_INPUT_SOURCE_SCRIPT.new()
_camera_pivot = get_node_or_null(camera_pivot_path) as Node3D _camera_pivot = get_node_or_null(camera_pivot_path) as Node3D
_camera = get_node_or_null(camera_path) as Camera3D _camera = get_node_or_null(camera_path) as Camera3D
_visual = get_node_or_null(visual_path) as Node3D _visual = get_node_or_null(visual_path) as Node3D
@@ -79,19 +83,22 @@ func _ready() -> void:
func _unhandled_input(event: InputEvent) -> void: func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT: if event.is_action_pressed(PLAYER_INPUT_ACTIONS_SCRIPT.CAMERA_ROTATE):
_captured = event.pressed _captured = true
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED if _captured else Input.MOUSE_MODE_VISIBLE Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
elif event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_WHEEL_UP: elif event.is_action_released(PLAYER_INPUT_ACTIONS_SCRIPT.CAMERA_ROTATE):
camera_distance = clampf(camera_distance - camera_zoom_step, camera_min_distance, camera_max_distance)
_apply_camera_transform()
elif event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
camera_distance = clampf(camera_distance + camera_zoom_step, camera_min_distance, camera_max_distance)
_apply_camera_transform()
elif event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
_captured = false _captured = false
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
elif event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_SPACE: elif event.is_action_pressed(PLAYER_INPUT_ACTIONS_SCRIPT.CAMERA_ZOOM_IN):
camera_distance = clampf(camera_distance - camera_zoom_step, camera_min_distance, camera_max_distance)
_apply_camera_transform()
elif event.is_action_pressed(PLAYER_INPUT_ACTIONS_SCRIPT.CAMERA_ZOOM_OUT):
camera_distance = clampf(camera_distance + camera_zoom_step, camera_min_distance, camera_max_distance)
_apply_camera_transform()
elif event.is_action_pressed(PLAYER_INPUT_ACTIONS_SCRIPT.RELEASE_CURSOR):
_captured = false
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED if _captured else Input.MOUSE_MODE_VISIBLE
elif event.is_action_pressed(PLAYER_INPUT_ACTIONS_SCRIPT.DEBUG_TOGGLE_FLIGHT) and not event.is_echo():
_flight_enabled = not _flight_enabled _flight_enabled = not _flight_enabled
_horizontal_velocity = Vector3.ZERO _horizontal_velocity = Vector3.ZERO
@@ -103,12 +110,12 @@ func _unhandled_input(event: InputEvent) -> void:
func _physics_process(delta: float) -> void: func _physics_process(delta: float) -> void:
var input_dir := _get_input_dir() var move_intent := _player_input_source.sample_move_intent()
var target_velocity := Vector3.ZERO var target_velocity := Vector3.ZERO
if input_dir.length_squared() > 0.0: if move_intent.has_translation():
target_velocity = _movement_vector(input_dir) target_velocity = _movement_vector(move_intent)
if _flight_enabled: if _flight_enabled:
target_velocity.y += _flight_vertical_velocity() target_velocity.y += move_intent.vertical_axis * flight_vertical_speed * _debug_speed_multiplier(move_intent)
_horizontal_velocity = _horizontal_velocity.move_toward(target_velocity, acceleration * delta) _horizontal_velocity = _horizontal_velocity.move_toward(target_velocity, acceleration * delta)
global_position += _horizontal_velocity * delta global_position += _horizontal_velocity * delta
@@ -128,20 +135,7 @@ func _physics_process(delta: float) -> void:
_apply_camera_transform() _apply_camera_transform()
func _get_input_dir() -> Vector2: func _movement_vector(move_intent: MoveIntent) -> Vector3:
var dir := Vector2.ZERO
if Input.is_key_pressed(KEY_W):
dir.y -= 1.0
if Input.is_key_pressed(KEY_S):
dir.y += 1.0
if Input.is_key_pressed(KEY_A):
dir.x -= 1.0
if Input.is_key_pressed(KEY_D):
dir.x += 1.0
return dir.normalized()
func _movement_vector(input_dir: Vector2) -> Vector3:
var forward := -global_basis.z var forward := -global_basis.z
var right := global_basis.x var right := global_basis.x
if _flight_enabled and _camera_pivot: if _flight_enabled and _camera_pivot:
@@ -153,20 +147,16 @@ func _movement_vector(input_dir: Vector2) -> Vector3:
forward = forward.normalized() forward = forward.normalized()
right = right.normalized() right = right.normalized()
var speed_z := run_speed if input_dir.y < 0.0 else backward_speed var speed_z := run_speed if move_intent.forward_axis > 0.0 else backward_speed
var speed_x := strafe_speed var speed_x := strafe_speed
var sprint := sprint_multiplier if Input.is_key_pressed(KEY_SHIFT) else 1.0 return (
return (forward * -input_dir.y * speed_z + right * input_dir.x * speed_x) * sprint forward * move_intent.forward_axis * speed_z
+ right * move_intent.strafe_axis * speed_x
) * _debug_speed_multiplier(move_intent)
func _flight_vertical_velocity() -> float: func _debug_speed_multiplier(move_intent: MoveIntent) -> float:
var dir := 0.0 return sprint_multiplier if move_intent.is_sprint_requested else 1.0
if Input.is_key_pressed(KEY_E):
dir += 1.0
if Input.is_key_pressed(KEY_Q):
dir -= 1.0
var sprint := sprint_multiplier if Input.is_key_pressed(KEY_SHIFT) else 1.0
return dir * flight_vertical_speed * sprint
func _load_character_visual() -> void: func _load_character_visual() -> void:
@@ -0,0 +1,14 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://src/scenes/player/third_person_wow_controller.gd" id="1_player"]
[node name="PlayerInputRegression" type="CharacterBody3D"]
script = ExtResource("1_player")
spawn_at_tile_center = false
character_model_path = ""
[node name="Visual" type="Node3D" parent="."]
[node name="CameraPivot" type="Node3D" parent="."]
[node name="Camera3D" type="Camera3D" parent="CameraPivot"]
+39
View File
@@ -246,6 +246,8 @@ func _result_record(checkpoint: Dictionary, pass_name: String, load_time_ms: flo
func _create_probe(checkpoint: Dictionary, dry_run: bool) -> Node3D: func _create_probe(checkpoint: Dictionary, dry_run: bool) -> Node3D:
if bool(checkpoint.get("diagnostic_spawn_marker", false)):
return null if dry_run else _create_diagnostic_spawn_marker(checkpoint)
var rel_path := String(checkpoint.get("probe_model", "")) var rel_path := String(checkpoint.get("probe_model", ""))
if rel_path.is_empty() or dry_run: if rel_path.is_empty() or dry_run:
return null return null
@@ -265,6 +267,43 @@ func _create_probe(checkpoint: Dictionary, dry_run: bool) -> Node3D:
return M2_NATIVE_ANIMATED_BUILDER.build(data, extracted_dir) return M2_NATIVE_ANIMATED_BUILDER.build(data, extracted_dir)
## Creates a renderer-native marker whose origin is the exact mapped server
## spawn. The vertical mast grows upward from that origin, so terrain occlusion
## remains visible instead of being hidden by an always-on-top overlay.
func _create_diagnostic_spawn_marker(checkpoint: Dictionary) -> Node3D:
var marker_root := Node3D.new()
marker_root.name = "DiagnosticServerSpawnMarker"
var marker_height := maxf(float(checkpoint.get("diagnostic_marker_height", 6.0)), 0.5)
var marker_radius := maxf(float(checkpoint.get("diagnostic_marker_radius", 0.35)), 0.05)
var marker_material := StandardMaterial3D.new()
marker_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
marker_material.albedo_color = Color(1.0, 0.05, 0.7, 1.0)
marker_material.emission_enabled = true
marker_material.emission = Color(1.0, 0.01, 0.35, 1.0)
var mast_mesh := CylinderMesh.new()
mast_mesh.height = marker_height
mast_mesh.top_radius = marker_radius
mast_mesh.bottom_radius = marker_radius
mast_mesh.material = marker_material
var mast := MeshInstance3D.new()
mast.name = "SpawnOriginMast"
mast.mesh = mast_mesh
mast.position.y = marker_height * 0.5
marker_root.add_child(mast)
var origin_mesh := SphereMesh.new()
origin_mesh.radius = marker_radius * 1.8
origin_mesh.height = marker_radius * 3.6
origin_mesh.material = marker_material
var origin := MeshInstance3D.new()
origin.name = "SpawnOrigin"
origin.mesh = origin_mesh
marker_root.add_child(origin)
return marker_root
func _set_sky_time(world: Node, time_hours: float) -> void: func _set_sky_time(world: Node, time_hours: float) -> void:
var sky := world.get_node_or_null("WowSkyController") var sky := world.get_node_or_null("WowSkyController")
if sky != null: if sky != null:
@@ -0,0 +1,29 @@
{
"schema_version": 1,
"profile": "Blizzlike335",
"scene": "res://src/scenes/streaming/eastern_kingdoms_streaming.tscn",
"map": "Azeroth",
"quality_preset": "High",
"viewport": [1280, 900],
"camera_fov": 62.0,
"default_wait_seconds": 10.0,
"default_measure_seconds": 1.0,
"source_fixture": "res://src/tests/fixtures/coordinate_golden_points.json",
"source_spawn_name": "azerothcore_human_warrior_start",
"required_coverage": ["server_spawn"],
"checkpoints": [
{
"name": "azerothcore_human_warrior_start",
"coverage": ["server_spawn"],
"camera": [17168.0, 105.0, 25984.0],
"target": [17199.159666667, 83.5312, 26016.616666667],
"player": [17199.159666667, 83.5312, 26016.616666667],
"time_hours": 13.0,
"diagnostic_spawn_marker": true,
"diagnostic_marker_height": 6.0,
"diagnostic_marker_radius": 0.35,
"expected_adt_tile": [32, 48],
"expected_adt_chunk": [3, 12]
}
]
}
+161
View File
@@ -0,0 +1,161 @@
extends SceneTree
## Headless M02 contract regression for remappable actions and MoveIntent composition.
const MOVE_INTENT_PATH := "res://src/domain/input/move_intent.gd"
const PLAYER_INPUT_SOURCE_PATH := "res://src/gameplay/input/player_input_source.gd"
const PLAYER_CONTROLLER_PATH := "res://src/scenes/player/third_person_wow_controller.gd"
const REGRESSION_SCENE_PATH := "res://src/tests/scenes/player_input_regression.tscn"
func _initialize() -> void:
_run_verification.call_deferred()
func _run_verification() -> void:
var failures: Array[String] = []
_verify_default_input_actions(failures)
_verify_move_intent_composition(failures)
_verify_controller_boundary(failures)
_verify_regression_scene(failures)
if not failures.is_empty():
for failure in failures:
push_error("PLAYER_INPUT: %s" % failure)
quit(1)
return
print("PLAYER_INPUT PASS actions=%d intent_cases=6 controller=1 regression_scene=1" % PlayerInputActions.REQUIRED_ACTIONS.size())
quit(0)
func _verify_default_input_actions(failures: Array[String]) -> void:
for action_name in PlayerInputActions.REQUIRED_ACTIONS:
_expect_true(InputMap.has_action(action_name), "Input Map contains %s" % action_name, failures)
_expect_key_binding(PlayerInputActions.MOVE_FORWARD, KEY_W, true, failures)
_expect_key_binding(PlayerInputActions.MOVE_BACKWARD, KEY_S, true, failures)
_expect_key_binding(PlayerInputActions.STRAFE_LEFT, KEY_A, true, failures)
_expect_key_binding(PlayerInputActions.STRAFE_RIGHT, KEY_D, true, failures)
_expect_key_binding(PlayerInputActions.DEBUG_FLY_UP, KEY_E, true, failures)
_expect_key_binding(PlayerInputActions.DEBUG_FLY_DOWN, KEY_Q, true, failures)
_expect_key_binding(PlayerInputActions.DEBUG_SPRINT, KEY_SHIFT, false, failures)
_expect_key_binding(PlayerInputActions.DEBUG_TOGGLE_FLIGHT, KEY_SPACE, true, failures)
_expect_key_binding(PlayerInputActions.RELEASE_CURSOR, KEY_ESCAPE, false, failures)
_expect_mouse_binding(PlayerInputActions.CAMERA_ROTATE, MOUSE_BUTTON_RIGHT, failures)
_expect_mouse_binding(PlayerInputActions.CAMERA_ZOOM_IN, MOUSE_BUTTON_WHEEL_UP, failures)
_expect_mouse_binding(PlayerInputActions.CAMERA_ZOOM_OUT, MOUSE_BUTTON_WHEEL_DOWN, failures)
func _verify_move_intent_composition(failures: Array[String]) -> void:
var neutral := PlayerInputSource.compose_move_intent(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)
_expect_near(neutral.forward_axis, 0.0, "neutral forward", failures)
_expect_true(not neutral.has_translation(), "neutral has no translation", failures)
var forward := PlayerInputSource.compose_move_intent(1.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)
_expect_near(forward.forward_axis, 1.0, "forward axis", failures)
var canceled := PlayerInputSource.compose_move_intent(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, false)
_expect_true(not canceled.has_translation(), "opposite actions cancel", failures)
var diagonal := PlayerInputSource.compose_move_intent(1.0, 0.0, 0.0, 1.0, 0.0, 0.0, false)
_expect_near(Vector2(diagonal.strafe_axis, diagonal.forward_axis).length(), 1.0, "diagonal normalized", failures)
var vertical := PlayerInputSource.compose_move_intent(0.0, 0.0, 0.0, 0.0, 1.5, -0.5, false)
_expect_near(vertical.vertical_axis, 1.0, "vertical strengths clamped", failures)
var debug_requests := PlayerInputSource.compose_move_intent(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, true)
_expect_true(debug_requests.is_sprint_requested, "sprint request retained", failures)
func _verify_controller_boundary(failures: Array[String]) -> void:
var controller_source := _read_text(PLAYER_CONTROLLER_PATH, failures)
_expect_true(controller_source.contains("sample_move_intent()"), "controller samples PlayerInputSource", failures)
_expect_true(not controller_source.contains("Input.is_key_pressed"), "controller omits direct keyboard polling", failures)
_expect_true(not controller_source.contains("KEY_"), "controller omits hardcoded key constants", failures)
var move_intent_source := _read_text(MOVE_INTENT_PATH, failures)
_expect_true(not move_intent_source.contains("extends Node"), "MoveIntent does not inherit Node", failures)
_expect_true(not move_intent_source.contains("extends Resource"), "MoveIntent does not inherit Resource", failures)
_expect_true(not move_intent_source.contains("Input."), "MoveIntent does not read engine input", failures)
var input_source := _read_text(PLAYER_INPUT_SOURCE_PATH, failures)
_expect_true(input_source.contains("Input.get_action_strength"), "input source reads action strengths", failures)
_expect_true(not input_source.contains("is_key_pressed"), "input source omits physical-key polling", failures)
func _verify_regression_scene(failures: Array[String]) -> void:
var packed_scene := load(REGRESSION_SCENE_PATH) as PackedScene
_expect_true(packed_scene != null, "regression scene loads", failures)
if packed_scene == null:
return
var player := packed_scene.instantiate() as CharacterBody3D
_expect_true(player != null, "regression player instantiates", failures)
if player == null:
return
root.add_child(player)
player.set_physics_process(false)
Input.action_press(PlayerInputActions.MOVE_FORWARD)
player.call("_physics_process", 1.0)
Input.action_release(PlayerInputActions.MOVE_FORWARD)
_expect_near(player.position.z, -7.0, "forward action moves current sandbox controller", failures)
var camera_distance_before: float = player.get("camera_distance")
var zoom_event := InputEventMouseButton.new()
zoom_event.button_index = MOUSE_BUTTON_WHEEL_UP
zoom_event.pressed = true
player.call("_unhandled_input", zoom_event)
_expect_near(player.get("camera_distance"), camera_distance_before - 1.0, "zoom action updates camera distance", failures)
var flight_toggle_event := InputEventAction.new()
flight_toggle_event.action = PlayerInputActions.DEBUG_TOGGLE_FLIGHT
flight_toggle_event.pressed = true
player.call("_unhandled_input", flight_toggle_event)
_expect_true(player.get("_flight_enabled"), "flight action preserves immediate sandbox toggle", failures)
player.free()
func _expect_key_binding(
action_name: StringName,
expected_keycode: Key,
uses_physical_keycode: bool,
failures: Array[String]
) -> void:
for input_event in InputMap.action_get_events(action_name):
if input_event is InputEventKey:
var key_event := input_event as InputEventKey
var configured_keycode := key_event.physical_keycode if uses_physical_keycode else key_event.keycode
if configured_keycode == expected_keycode:
return
failures.append("%s missing expected key binding %s" % [action_name, expected_keycode])
func _expect_mouse_binding(
action_name: StringName,
expected_button: MouseButton,
failures: Array[String]
) -> void:
for input_event in InputMap.action_get_events(action_name):
if input_event is InputEventMouseButton and input_event.button_index == expected_button:
return
failures.append("%s missing expected mouse binding %s" % [action_name, expected_button])
func _read_text(path: String, failures: Array[String]) -> String:
var file := FileAccess.open(path, FileAccess.READ)
if file == null:
failures.append("cannot open %s" % path)
return ""
return file.get_as_text()
func _expect_near(actual_value: float, expected_value: float, label: String, failures: Array[String]) -> void:
if absf(actual_value - expected_value) > 0.000001:
failures.append("%s expected %.6f, got %.6f" % [label, expected_value, actual_value])
func _expect_true(actual_value: bool, label: String, failures: Array[String]) -> void:
if not actual_value:
failures.append("%s expected true" % label)
+138
View File
@@ -0,0 +1,138 @@
extends SceneTree
## Verifies that the pinned AzerothCore spawn is routed through the coordinate
## contract into an executable renderer checkpoint. GUI capture is performed by
## capture_render_checkpoints.gd with the validated dedicated manifest.
const COORDINATE_MAPPER_SCRIPT := preload("res://src/domain/coordinates/coordinate_mapper.gd")
const SERVER_WORLD_POSITION_SCRIPT := preload("res://src/domain/coordinates/server_world_position.gd")
const MANIFEST_PATH := "res://src/tools/server_spawn_render_manifest.json"
const EXPECTED_FIXTURE_PATH := "res://src/tests/fixtures/coordinate_golden_points.json"
const EXPECTED_SPAWN_NAME := "azerothcore_human_warrior_start"
const EXPECTED_PROFILE := "Blizzlike335"
const POSITION_TOLERANCE_YARDS := 0.002
func _initialize() -> void:
var failures: Array[String] = []
var manifest := _load_json_object(MANIFEST_PATH, failures)
var fixture_path := String(manifest.get("source_fixture", ""))
_expect_equal(fixture_path, EXPECTED_FIXTURE_PATH, "source fixture path", failures)
var fixture := _load_json_object(fixture_path, failures) if not fixture_path.is_empty() else {}
if manifest.is_empty() or fixture.is_empty():
_report_and_quit(failures)
return
_expect_equal(int(manifest.get("schema_version", -1)), 1, "manifest schema version", failures)
_expect_equal(String(manifest.get("profile", "")), EXPECTED_PROFILE, "renderer profile", failures)
_expect_equal(String(manifest.get("map", "")), "Azeroth", "renderer map", failures)
_expect_true(ResourceLoader.exists(String(manifest.get("scene", ""))), "renderer scene exists", failures)
_expect_equal(String(manifest.get("source_spawn_name", "")), EXPECTED_SPAWN_NAME, "source spawn name", failures)
var spawn := _find_named_dictionary(fixture.get("server_spawns", []), EXPECTED_SPAWN_NAME)
_expect_true(not spawn.is_empty(), "pinned server spawn exists", failures)
var checkpoints: Array = manifest.get("checkpoints", [])
_expect_equal(checkpoints.size(), 1, "renderer checkpoint count", failures)
if spawn.is_empty() or checkpoints.size() != 1 or not (checkpoints[0] is Dictionary):
_report_and_quit(failures)
return
var checkpoint: Dictionary = checkpoints[0]
var server_values := _three_floats(spawn.get("server_position", null), "server position", failures)
if server_values.is_empty():
_report_and_quit(failures)
return
var server_position = SERVER_WORLD_POSITION_SCRIPT.new(server_values[0], server_values[1], server_values[2])
var canonical_position = COORDINATE_MAPPER_SCRIPT.server_to_canonical(server_position)
var godot_position = COORDINATE_MAPPER_SCRIPT.canonical_to_godot(canonical_position)
var tile_coordinate = COORDINATE_MAPPER_SCRIPT.canonical_to_adt_tile(canonical_position)
var tile_local_position = COORDINATE_MAPPER_SCRIPT.canonical_to_adt_tile_local(canonical_position)
var chunk_coordinate = COORDINATE_MAPPER_SCRIPT.adt_tile_local_to_chunk(tile_coordinate, tile_local_position)
var mapped_position := [godot_position.x_units, godot_position.y_units, godot_position.z_units]
_expect_equal(String(checkpoint.get("name", "")), EXPECTED_SPAWN_NAME, "checkpoint name", failures)
_expect_true("server_spawn" in checkpoint.get("coverage", []), "server-spawn coverage", failures)
_expect_true(bool(checkpoint.get("diagnostic_spawn_marker", false)), "diagnostic spawn marker enabled", failures)
_expect_three_near(checkpoint.get("target", null), mapped_position, "checkpoint target", failures)
_expect_three_near(checkpoint.get("player", null), mapped_position, "checkpoint player", failures)
_expect_pair(checkpoint.get("expected_adt_tile", null), [tile_coordinate.tile_x, tile_coordinate.tile_y], "ADT tile", failures)
_expect_pair(checkpoint.get("expected_adt_chunk", null), [chunk_coordinate.chunk_x, chunk_coordinate.chunk_y], "ADT chunk", failures)
_expect_three_near(spawn.get("expected_godot_position", null), mapped_position, "fixture Godot position", failures)
var camera_values := _three_floats(checkpoint.get("camera", null), "checkpoint camera", failures)
if not camera_values.is_empty():
_expect_true(camera_values[1] > godot_position.y_units, "checkpoint camera is above spawn", failures)
_expect_true(Vector3(camera_values[0], camera_values[1], camera_values[2]).distance_to(Vector3(mapped_position[0], mapped_position[1], mapped_position[2])) > 1.0, "checkpoint camera is distinct from spawn", failures)
if not failures.is_empty():
_report_and_quit(failures)
return
print("SERVER_SPAWN_RENDERER PASS spawn=1 checkpoint=1 tile=%d_%d chunk=%d_%d profile=%s" % [
tile_coordinate.tile_x,
tile_coordinate.tile_y,
chunk_coordinate.chunk_x,
chunk_coordinate.chunk_y,
EXPECTED_PROFILE,
])
quit(0)
func _find_named_dictionary(entries: Array, expected_name: String) -> Dictionary:
for entry_variant in entries:
if entry_variant is Dictionary and String(entry_variant.get("name", "")) == expected_name:
return entry_variant
return {}
func _load_json_object(path: String, failures: Array[String]) -> Dictionary:
var file := FileAccess.open(path, FileAccess.READ)
if file == null:
failures.append("cannot open JSON: %s" % path)
return {}
var parsed = JSON.parse_string(file.get_as_text())
if not (parsed is Dictionary):
failures.append("JSON is not an object: %s" % path)
return {}
return parsed
func _three_floats(value_variant, label: String, failures: Array[String]) -> Array[float]:
if not (value_variant is Array) or value_variant.size() != 3:
failures.append("%s must contain three numbers" % label)
return []
return [float(value_variant[0]), float(value_variant[1]), float(value_variant[2])]
func _expect_three_near(actual_variant, expected_values: Array, label: String, failures: Array[String]) -> void:
var actual_values := _three_floats(actual_variant, label, failures)
if actual_values.is_empty() or expected_values.size() != 3:
return
for component_index in range(3):
if absf(actual_values[component_index] - float(expected_values[component_index])) > POSITION_TOLERANCE_YARDS:
failures.append("%s[%d] expected %.9f, got %.9f" % [label, component_index, expected_values[component_index], actual_values[component_index]])
func _expect_pair(actual_variant, expected_values: Array, label: String, failures: Array[String]) -> void:
if not (actual_variant is Array) or actual_variant.size() != 2:
failures.append("%s must contain two integers" % label)
return
for component_index in range(2):
_expect_equal(int(actual_variant[component_index]), int(expected_values[component_index]), "%s[%d]" % [label, component_index], failures)
func _expect_equal(actual_value, expected_value, label: String, failures: Array[String]) -> void:
if actual_value != expected_value:
failures.append("%s expected %s, got %s" % [label, expected_value, actual_value])
func _expect_true(actual_value: bool, label: String, failures: Array[String]) -> void:
if not actual_value:
failures.append("%s expected true" % label)
func _report_and_quit(failures: Array[String]) -> void:
if failures.is_empty():
failures.append("verification stopped without a diagnostic")
for failure in failures:
push_error("SERVER_SPAWN_RENDERER: %s" % failure)
quit(1)
@@ -0,0 +1 @@
uid://c7f2rpfey0vrs
+41 -15
View File
@@ -1,6 +1,7 @@
# M01 — Coordinates and Architecture Seams # M01 — Coordinates and Architecture Seams
<!-- OPENWC_TARGET:ACTIVE --> <!-- OPENWC_TARGET:DONE -->
<!-- OPENWC_TARGET_DONE:M01:work/sindo-main-codex/m01-integrator-closeout:2026-07-14 -->
## Outcome ## Outcome
@@ -8,13 +9,13 @@
## Steps ## Steps
- [ ] Создать domain/value-object слой без `Node`/`Resource` dependencies. - [x] Создать domain/value-object слой без `Node`/`Resource` dependencies.
- [ ] Определить canonical WoW, Godot, ADT tile/chunk и local coordinate types. - [x] Определить canonical WoW, Godot, ADT tile/chunk и local coordinate types.
- [ ] Реализовать единственный `CoordinateMapper` для position и orientation. - [x] Реализовать единственный `CoordinateMapper` для position и orientation.
- [ ] Собрать golden points из ADT, server spawn и оригинального клиента. - [x] Собрать golden points из ADT, server spawn и оригинального клиента.
- [ ] Ввести `StreamingFocus` вместо неявной зависимости от active Camera3D. - [x] Ввести `StreamingFocus` вместо неявной зависимости от active Camera3D.
- [ ] Ввести stable `ContentId`, `EntityId` и отличить их от WoW GUID/server entry. - [x] Ввести stable `ContentId`, `EntityId` и отличить их от WoW GUID/server entry.
- [ ] Запретить ручные axis conversion через code review rule/test search. - [x] Запретить ручные axis conversion через code review rule/test search.
## Verification ## Verification
@@ -28,10 +29,35 @@ Renderer/test scene получают focus через контракт; коор
## Evidence ## Evidence
- Date: - Date: 2026-07-14
- Revision/worktree: - Revision/worktree: merged master `c8e99f2`; closeout
- Commands: `work/sindo-main-codex/m01-integrator-closeout`
- Results: - Commands: `tools/run_render_baseline.ps1 -DryRun -WaitSeconds 0.1
- Fidelity comparison: -MeasureSeconds 0.1`; `verify_coordinate_mapper.gd`;
- Changed files: `verify_coordinate_golden_fixtures.gd`; `verify_streaming_focus.gd`;
- Remaining risks: `verify_domain_identities.gd`; coordination/documentation/diff gates.
- Results: CoordinateMapper PASS (`5` golden points, `8` tile boundaries,
`2` raw MCNK, `5` yaw); golden fixtures PASS (`1` server spawn, `1` raw ADT,
`5` original-client points); StreamingFocus PASS (`1` contract, `2` runtime
scenes, `3` capture tools); identities PASS (`5/5/6/5`); conversion boundary
PASS (`84` files, one native boundary, one oracle, four consumers, six
classifiers); server-spawn renderer PASS at ADT `(32,48)`, MCNK `(3,12)`;
renderer manifest PASS (`7/7/7`) and calibration maximum error `0.000015`.
- Fidelity comparison: five build-12340 camera observations, pinned AzerothCore
Human Warrior spawn and private raw ADT metadata agree through coordinate
contract version 2. Cache-complete cold/warm GUI capture places the mapped
spawn marker on rendered Northshire terrain with focus `(32,48)`, quality
`100/100`, missing cache `0`; PNG SHA-256 values are recorded in the server
spawn handoff. The final ADT-boundary regression passed `2/2`; the full M01
regression captured `14/14`, with `11` strict passes and three low-mean
timing/animation-phase differences rather than coordinate drift.
- Changed files: `src/domain/coordinates/`, `src/domain/identity/`,
`src/domain/streaming/`, migrated sky/player/streaming/terrain consumers,
coordinate/renderer fixtures and tools, runtime scenes, ADR 0001/0002,
coordinate/domain/renderer module specs, coding/render documentation and
M01 coordination claims.
- Remaining risks: TrinityCore populated spawn equivalence is not verified;
native parser conversion remains one separately calibrated boundary; MDDF/
MODF placement Euler parity is outside world-yaw scope; renderer visual parity
gaps and the known anonymous ObjectDB teardown warning continue. No `1:1`
compatibility claim is made by M01.
+1 -1
View File
@@ -1,6 +1,6 @@
# M02 — Player Decomposition # M02 — Player Decomposition
<!-- OPENWC_TARGET:OPEN --> <!-- OPENWC_TARGET:ACTIVE -->
## Outcome ## Outcome
+1 -1
View File
@@ -21,7 +21,7 @@ OpenWC должен предоставить четыре согласованн
- MPQ/BLP/ADT/WDT/M2/WMO native parsing и renderer vertical slice уже существуют. - MPQ/BLP/ADT/WDT/M2/WMO native parsing и renderer vertical slice уже существуют.
- Runtime streaming, terrain quality, MultiMesh M2, WMO caches, liquids, sky и character experiments находятся в рабочем состоянии, но сосредоточены в крупных orchestration scripts. - Runtime streaming, terrain quality, MultiMesh M2, WMO caches, liquids, sky и character experiments находятся в рабочем состоянии, но сосредоточены в крупных orchestration scripts.
- Renderer baseline M00 завершён: paired comparison с оригинальным клиентом записывает измеренные gaps без заявления parity, а обязательные diagnostic gaps закрыты или явно классифицированы. Текущая работа M01 вводит coordinate и architecture seams. - Renderer baseline M00 завершён: paired comparison с оригинальным клиентом записывает измеренные gaps без заявления parity, а обязательные diagnostic gaps закрыты или явно классифицированы. M01 завершил coordinate, identity и streaming-focus seams; текущая работа M02 декомпозирует player input, movement, terrain query, camera и presentation без observable regression.
- Gameplay domain, network protocol, production UI/Lua, audio orchestration и server adapters в основном предстоит реализовать. - Gameplay domain, network protocol, production UI/Lua, audio orchestration и server adapters в основном предстоит реализовать.
- Editor plugin пока решает extraction/preview задачи, но не является полноценной authoring platform. - Editor plugin пока решает extraction/preview задачи, но не является полноценной authoring platform.
+3 -3
View File
@@ -10,7 +10,7 @@
## Current target ## Current target
`M01` — [01-coordinates-and-seams.md](01-coordinates-and-seams.md) `M02` — [02-player-decomposition.md](02-player-decomposition.md)
Одновременно `ACTIVE` может быть только одна цель. Следующая цель становится `ACTIVE` после появления валидной `OPENWC_TARGET_DONE` метки у предыдущей. Одновременно `ACTIVE` может быть только одна цель. Следующая цель становится `ACTIVE` после появления валидной `OPENWC_TARGET_DONE` метки у предыдущей.
@@ -34,8 +34,8 @@
| ID | Цель | Зависит от | Статус | | ID | Цель | Зависит от | Статус |
|---|---|---|---| |---|---|---|---|
| M00 | [Renderer baseline](00-render-baseline.md) | — | DONE | | M00 | [Renderer baseline](00-render-baseline.md) | — | DONE |
| M01 | [Coordinates and architecture seams](01-coordinates-and-seams.md) | M00 | ACTIVE | | M01 | [Coordinates and architecture seams](01-coordinates-and-seams.md) | M00 | DONE |
| M02 | [Player decomposition](02-player-decomposition.md) | M01 | OPEN | | M02 | [Player decomposition](02-player-decomposition.md) | M01 | ACTIVE |
| M03 | [Renderer facade and extraction](03-renderer-facade.md) | M02 | OPEN | | M03 | [Renderer facade and extraction](03-renderer-facade.md) | M02 | OPEN |
| M04 | [Godot Editor shell](04-editor-shell.md) | M03 | OPEN | | M04 | [Godot Editor shell](04-editor-shell.md) | M03 | OPEN |
| M05 | [Content Project](05-content-project.md) | M04 | OPEN | | M05 | [Content Project](05-content-project.md) | M04 | OPEN |
+1
View File
@@ -58,6 +58,7 @@ try {
Invoke-GodotStep "baseline-manifest" @("--headless", "--path", ".", "--script", "res://src/tools/verify_render_baseline_manifest.gd") Invoke-GodotStep "baseline-manifest" @("--headless", "--path", ".", "--script", "res://src/tools/verify_render_baseline_manifest.gd")
Invoke-GodotStep "coordinate-calibration" @("--headless", "--path", ".", "--script", "res://src/tools/verify_render_coordinate_calibration.gd") Invoke-GodotStep "coordinate-calibration" @("--headless", "--path", ".", "--script", "res://src/tools/verify_render_coordinate_calibration.gd")
Invoke-GodotStep "coordinate-boundaries" @("--headless", "--path", ".", "--script", "res://src/tools/verify_coordinate_conversion_boundaries.gd") Invoke-GodotStep "coordinate-boundaries" @("--headless", "--path", ".", "--script", "res://src/tools/verify_coordinate_conversion_boundaries.gd")
Invoke-GodotStep "server-spawn-renderer" @("--headless", "--path", ".", "--script", "res://src/tools/verify_server_spawn_renderer.gd")
$captureArgs = @( $captureArgs = @(
"--path", ".", "--path", ".",