Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e794764269 | |||
| 6bd2e84048 | |||
| 62eece991c | |||
| fc81059dfa | |||
| dd412e6d0d | |||
| 07f08044f4 | |||
| c8e99f28d6 | |||
| 48cc976ba0 | |||
| 318e6ae757 | |||
| 747fae8e2e | |||
| 24ddc99a7b | |||
| 52b6fa2ebb | |||
| 3933dbfc47 |
@@ -35,3 +35,7 @@
|
|||||||
|
|
||||||
# CMake build директория
|
# CMake build директория
|
||||||
/src/native/build/
|
/src/native/build/
|
||||||
|
|
||||||
|
# Local root-level diagnostic capture logs
|
||||||
|
/*.stdout.log
|
||||||
|
/*.stderr.log
|
||||||
|
|||||||
@@ -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,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.
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
# 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_INTEGRATION:ACCEPTED:M01-QAR-SERVER-SPAWN-RENDERER-001:c8e99f2 -->
|
||||||
|
|
||||||
|
## Ownership
|
||||||
|
|
||||||
|
- Target: M01
|
||||||
|
- Program: QAR/RND
|
||||||
|
- Owner/Agent ID: sindo-main-codex
|
||||||
|
- Branch: `work/sindo-main-codex/m01-server-spawn-renderer`
|
||||||
|
- Lease expires UTC: 2026-07-15
|
||||||
|
- Integrator: M01 milestone integrator
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
|
||||||
|
Route the pinned AzerothCore Human Warrior spawn fixture through
|
||||||
|
`CoordinateMapper` into the live Eastern Kingdoms renderer and produce
|
||||||
|
repeatable headless contract checks plus GUI screenshot evidence at the mapped
|
||||||
|
ADT tile/chunk.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- Connecting to a live AzerothCore or TrinityCore database.
|
||||||
|
- Claiming TrinityCore spawn equivalence without a pinned populated TDB row.
|
||||||
|
- Changing coordinate formulas, streaming radii, terrain, player movement or
|
||||||
|
production character presentation.
|
||||||
|
- Adding proprietary assets or screenshots to Git.
|
||||||
|
|
||||||
|
## Paths
|
||||||
|
|
||||||
|
- Exclusive: `src/tools/server_spawn_render_manifest.json`,
|
||||||
|
`src/tools/verify_server_spawn_renderer.gd`
|
||||||
|
- Shared/hotspots: `src/tools/capture_render_checkpoints.gd`, renderer runner,
|
||||||
|
coordinate/world-renderer module documentation, `RENDER.md`, this claim
|
||||||
|
- Generated/ignored: Godot UIDs/import metadata and `user://` PNG/JSON evidence
|
||||||
|
|
||||||
|
## Contracts and data
|
||||||
|
|
||||||
|
- Fixture schema and coordinate contract version remain unchanged.
|
||||||
|
- Additive diagnostic checkpoint metadata may request a renderer-native marker;
|
||||||
|
existing M00 checkpoints remain byte-for-byte compatible.
|
||||||
|
- The spawn remains sourced from pinned AzerothCore revision
|
||||||
|
`68fcf0098b16388093989627f37be530fc91f07d`.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- Requires: merged M01 packages through `a07a487`
|
||||||
|
- Blocks: M01 golden server-spawn renderer criterion and milestone closeout
|
||||||
|
- External state: GUI capture requires the existing local extracted/cached
|
||||||
|
renderer corpus; headless contract verification remains non-proprietary
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- Commands: dedicated spawn verifier, coordinate fixture/mapper/boundary gates,
|
||||||
|
renderer baseline dry-run, GUI checkpoint capture and human inspection
|
||||||
|
- Fixture: AzerothCore race `1`, class `1`, map `0`, server position
|
||||||
|
`(-8949.95, -132.493, 83.5312)`
|
||||||
|
- Expected renderer result: Godot position
|
||||||
|
`(17199.159666667, 83.5312, 26016.616666667)`, ADT `(32,48)`, MCNK `(3,12)`
|
||||||
|
- Fidelity classification: server/coordinate integration evidence only; it does
|
||||||
|
not establish full original-client visual parity
|
||||||
|
|
||||||
|
## Documentation deliverables
|
||||||
|
|
||||||
|
- Inline API/tool documentation for new verifier and marker metadata
|
||||||
|
- Coordinate-mapping module capability/evidence update
|
||||||
|
- World-renderer checkpoint input/output/source-map update when marker support is added
|
||||||
|
- Existing synchronous coordinate data-flow diagram updated only if flow changes
|
||||||
|
|
||||||
|
## Simplicity and naming
|
||||||
|
|
||||||
|
- Important names: `server_spawn_render_manifest`,
|
||||||
|
`verify_server_spawn_renderer`, `diagnostic_spawn_marker`
|
||||||
|
- Simplest approach: reuse the existing checkpoint capture pipeline with one
|
||||||
|
dedicated manifest and a renderer-native diagnostic marker
|
||||||
|
- Rejected complexity: live DB adapter, new scene-test framework and production
|
||||||
|
spawn entity implementation before the network/gameplay milestones
|
||||||
|
- Performance: one diagnostic checkpoint outside production runtime
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
- State: ready-for-review
|
||||||
|
- Done: fixture-to-renderer manifest/verifier, renderer-native marker, unified
|
||||||
|
baseline integration, cold/warm GUI evidence, documentation and log cleanup
|
||||||
|
- Next: integrator review, merge cleanup/handoff and M01 closeout audit
|
||||||
|
- 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
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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-ов и результаты;
|
||||||
|
|||||||
@@ -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) |
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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`.
|
||||||
@@ -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 |
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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"]
|
||||||
@@ -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]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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,6 +1,6 @@
|
|||||||
# M02 — Player Decomposition
|
# M02 — Player Decomposition
|
||||||
|
|
||||||
<!-- OPENWC_TARGET:OPEN -->
|
<!-- OPENWC_TARGET:ACTIVE -->
|
||||||
|
|
||||||
## Outcome
|
## Outcome
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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 |
|
||||||
|
|||||||
@@ -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", ".",
|
||||||
|
|||||||
Reference in New Issue
Block a user