Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a10206b797 | |||
| 3172f41ee5 | |||
| 5a957d2ebb | |||
| 5092995d0c | |||
| 4908eb2e31 | |||
| eca76c5ed1 | |||
| 03891edeb2 | |||
| 657a1d888a | |||
| b48195d08d | |||
| 8d4641a43b | |||
| ad9ba7af4e | |||
| f54850718a | |||
| 5103eed014 | |||
| 9d34a47765 | |||
| aea7787b9b | |||
| 230620089f | |||
| 42fdf40282 | |||
| f8538ba2cf | |||
| d233a41ce8 | |||
| dfc10312f8 | |||
| 8e8ea32ba3 | |||
| 19df5b7968 | |||
| d467ffee7f | |||
| a4f60dcb06 | |||
| fbe22f6a94 | |||
| 0eef72a2c2 |
@@ -12,10 +12,14 @@
|
||||
|
||||
1. Прочитать [`targets/README.md`](targets/README.md).
|
||||
2. Прочитать файл текущей цели, указанный там как `Current target`.
|
||||
3. Прочитать [`docs/README.md`](docs/README.md) и связанные с задачей нормативные документы.
|
||||
4. Для renderer-задач прочитать [`RENDER.md`](RENDER.md).
|
||||
5. Проверить рабочее дерево и не перезаписывать чужие изменения.
|
||||
6. Перед созданием сложной подсистемы или добавлением dependency проверить [`docs/TOOLING_CATALOG.md`](docs/TOOLING_CATALOG.md), зарегистрировать найденные готовые решения и провести bounded evaluation вместо немедленного вендоринга.
|
||||
3. Прочитать [`targets/DEVELOPMENT_ROADMAP.md`](targets/DEVELOPMENT_ROADMAP.md) и соответствующий subsystem plan из `targets/roadmap/`.
|
||||
4. Прочитать [`docs/README.md`](docs/README.md) и связанные с задачей нормативные документы.
|
||||
5. Прочитать обязательный [`docs/CODING_STANDARD.md`](docs/CODING_STANDARD.md): явные имена и KISS имеют приоритет.
|
||||
6. Прочитать обязательный [`docs/DOCUMENTATION_STANDARD.md`](docs/DOCUMENTATION_STANDARD.md) и определить, какие API docs, module spec и diagrams затрагивает задача.
|
||||
7. Для renderer-задач прочитать [`RENDER.md`](RENDER.md).
|
||||
8. Проверить рабочее дерево и не перезаписывать чужие изменения.
|
||||
9. Перед созданием сложной подсистемы или добавлением dependency проверить [`docs/TOOLING_CATALOG.md`](docs/TOOLING_CATALOG.md), зарегистрировать найденные готовые решения и провести bounded evaluation вместо немедленного вендоринга.
|
||||
10. При командной работе прочитать [`docs/TEAM_WORKFLOW.md`](docs/TEAM_WORKFLOW.md), проверить активные claims/draft PRs и объявить стабильный Agent ID до изменений.
|
||||
|
||||
Если задача пользователя противоречит target-порядку, прямое указание пользователя имеет приоритет, но агент обязан сохранить архитектурные границы и отметить влияние на roadmap.
|
||||
|
||||
@@ -30,10 +34,19 @@
|
||||
- Любой новый формат, публичный контракт или несовместимое решение требует обновления документации и при необходимости ADR.
|
||||
- Исправление дефекта должно получать regression test, когда это технически возможно.
|
||||
- Не писать крупную подсистему с нуля до поиска готовых libraries, reference implementations, fixtures и tooling; результат исследования фиксируется в `docs/TOOLING_CATALOG.md`.
|
||||
- Не начинать claimed work package другого агента и не редактировать его exclusive paths без явного handoff.
|
||||
- Не работать двум агентам в одном worktree. Каждый агент использует собственную ветку и clone/worktree.
|
||||
- Статус milestone, Evidence и `OPENWC_TARGET_DONE` изменяет только назначенный интегратор.
|
||||
- Каждый новый/существенно изменённый модуль обязан иметь актуальную specification в `docs/modules/`, inline documentation публичного API и data-flow diagram по `docs/DOCUMENTATION_STANDARD.md`.
|
||||
- Stateful/async/cross-layer работа обязана документировать state/sequence diagram, ownership, threading, failure и recovery paths.
|
||||
- Документация обновляется в том же work package, что код. Handoff без обязательной документации имеет статус только `PARTIAL`.
|
||||
- Не сокращать имена ради краткости. Variable/function/type должен явно описывать назначение, domain context, units и coordinate space, когда они неоднозначны.
|
||||
- Следовать KISS: использовать самое простое корректное решение, не вводить premature abstractions/frameworks/optimizations и изолировать неизбежную сложность.
|
||||
- Имена `manager`, `helper`, `utils`, `data`, `info`, `process`, `handle` без точного контекста считаются design smell и требуют переименования или обоснования.
|
||||
|
||||
## Метки состояния
|
||||
|
||||
В каждом `targets/*.md` присутствует ровно одна метка:
|
||||
В каждом исполняемом target-файле `targets/[0-9][0-9]-*.md` присутствует ровно одна метка. Справочные документы `targets/DEVELOPMENT_ROADMAP.md` и `targets/roadmap/*.md` меток состояния не имеют:
|
||||
|
||||
- `<!-- OPENWC_TARGET:OPEN -->` — цель ещё не начата.
|
||||
- `<!-- OPENWC_TARGET:ACTIVE -->` — единственная текущая цель.
|
||||
@@ -53,6 +66,8 @@
|
||||
3. В Evidence записаны команды, результаты и изменённые файлы.
|
||||
4. Не осталось обязательных незакрытых пунктов или скрытых заглушек.
|
||||
5. Обновлены `targets/README.md` и матрица функций, если применимо.
|
||||
6. Публичный API, module spec, input/output tables и обязательные diagrams соответствуют фактической реализации.
|
||||
7. Код соответствует `docs/CODING_STANDARD.md`: понятные имена, простой data flow и обоснованная сложность.
|
||||
|
||||
Агент НЕ ДОЛЖЕН ставить `DONE` только потому, что код компилируется. Если пользователь явно принял частичный результат, статус остаётся `ACTIVE`, а принятое ограничение записывается в Evidence.
|
||||
|
||||
@@ -64,4 +79,5 @@
|
||||
- что изменено;
|
||||
- какие проверки выполнены;
|
||||
- fidelity evidence для поведения 3.3.5a;
|
||||
- оставшиеся риски или следующий незакрытый checklist item.
|
||||
- оставшиеся риски или следующий незакрытый checklist item;
|
||||
- какие API/module docs и diagrams созданы или обновлены.
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
Воспроизводимый renderer baseline для текущей цели M00, regression manifest, метрики и правила сравнения описаны в [`docs/RENDER_BASELINE.md`](docs/RENDER_BASELINE.md). Baseline фиксирует текущее поведение и известные расхождения, но не заявляет parity с WoW 3.3.5a.
|
||||
|
||||
Парные checkpoint PNG можно автоматически проверить через `src/tools/compare_render_checkpoints.gd`; reference-снимки оригинального клиента остаются вне Git, а автоматический tolerance не заменяет human fidelity approval.
|
||||
|
||||
Paired run 2026-07-11 подтвердил крупный coordinate/placement gap: некоторые server-derived camera positions оказываются под terrain или внутри WMO/rocks OpenWC. До исправления этого расхождения perceptual metrics измеряют также несовпадение композиции, а не только материалы и свет.
|
||||
|
||||
Цель renderer-работы в этом проекте: добиться ощущения производительности оригинального клиента WoW 3.3.5a в Godot, без видимых фризов при переходе ADT -> ADT и без постоянного отката видимых участков к низкому качеству.
|
||||
|
||||
Этот документ фиксирует текущее состояние рендера, сделанные оптимизации и практические правила дальнейшей работы.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# OpenWC Coordination
|
||||
|
||||
Основные правила находятся в [`../docs/TEAM_WORKFLOW.md`](../docs/TEAM_WORKFLOW.md).
|
||||
|
||||
Предпочтительно вести claims в issue и draft PR/MR общего Git forge. Каталог `claims/` — fallback, если issue tracker недоступен. Один claim — один файл; не использовать общий изменяемый board.
|
||||
|
||||
## Создание fallback claim
|
||||
|
||||
1. Скопировать [`TASK_TEMPLATE.md`](TASK_TEMPLATE.md) в `claims/<work-package-id>.md`.
|
||||
2. Заполнить owner, lease, paths, contracts и tests.
|
||||
3. Опубликовать claim до production edits.
|
||||
4. После интеграции заменить состояние на `accepted` или удалить claim отдельным coordination commit, сохранив историю в Git.
|
||||
|
||||
Claim-файлы не заменяют target Evidence и не меняют milestone status.
|
||||
@@ -0,0 +1,74 @@
|
||||
# <WORK-PACKAGE-ID> — <Title>
|
||||
|
||||
<!-- OPENWC_CLAIM:<WORK-PACKAGE-ID>:<AGENT-ID>:<YYYY-MM-DD> -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target:
|
||||
- Program:
|
||||
- Owner/Agent ID:
|
||||
- Branch:
|
||||
- Lease expires UTC:
|
||||
- Integrator:
|
||||
|
||||
## Outcome
|
||||
|
||||
|
||||
## Non-goals
|
||||
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive:
|
||||
- Shared/hotspots:
|
||||
- Generated/ignored:
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Public API/events:
|
||||
- Schema/format version:
|
||||
- Migration/compatibility:
|
||||
- Consumers:
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires:
|
||||
- Blocks:
|
||||
- External state:
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands:
|
||||
- Fixtures:
|
||||
- Fidelity evidence:
|
||||
- Performance budget:
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs:
|
||||
- Module specification:
|
||||
- Data-flow diagram:
|
||||
- Sequence/state/dependency diagrams:
|
||||
- Source map/status updates:
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced:
|
||||
- Simplest considered solution:
|
||||
- Rejected complexity/abstractions:
|
||||
- Unavoidable complexity and justification:
|
||||
- Measured optimization evidence:
|
||||
|
||||
## Status
|
||||
|
||||
- State: claimed
|
||||
- Done:
|
||||
- Next:
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit:
|
||||
- Results:
|
||||
- Remaining risks:
|
||||
- Documentation updated:
|
||||
@@ -0,0 +1,78 @@
|
||||
# M00-QAR-CAMERA-FRAMING-REFINEMENT-001 — Goldshire pitch/FOV refinement
|
||||
|
||||
<!-- OPENWC_CLAIM:M00-QAR-CAMERA-FRAMING-REFINEMENT-001:sindo-main-codex:2026-07-14 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M00
|
||||
- Program: QAR
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m00-camera-framing-refinement`
|
||||
- Lease expires UTC: 2026-07-14
|
||||
- Integrator: milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Jointly refine Goldshire checkpoint pitch and FOV against a WMO-ready build 12340 reference.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Changing runtime player camera defaults.
|
||||
- Changing manifest values without an interior optimum and human approval.
|
||||
- Optimizing yaw beyond the ready-scene coarse result.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: joint FOV option in camera-pose sweep and framing evidence
|
||||
- Shared/hotspots: camera-pose operational documentation and renderer module findings
|
||||
- Generated/ignored: candidate PNGs and ranking JSON under `user://`
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Public API/events: additive `-CameraFovValues`; scalar `-CameraFov` remains compatible
|
||||
- Schema/format version: ranking schema remains version 1 with existing per-candidate FOV field
|
||||
- Migration/compatibility: scalar sweeps retain existing candidate paths
|
||||
- Consumers: M00 fidelity workflow
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: WMO-ready 8-second capture contract
|
||||
- Blocks: Goldshire manifest framing decision
|
||||
- External state: private build 12340 reference remains outside Git
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: joint-grid plan regression, real ready-scene sweep, comparator self-test, repository gates
|
||||
- Fixtures: private Goldshire Inn reference
|
||||
- Fidelity evidence: joint pitch/FOV ranking and human inspection
|
||||
- Performance budget: offline diagnostic
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs: PowerShell parameter
|
||||
- Module specification: framing finding
|
||||
- Data-flow diagram: FOV joins pose grid
|
||||
- Sequence/state/dependency diagrams: unchanged
|
||||
- Source map/status updates: operational guide
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced: `CameraFovValues`, `effectiveCameraFovValues`
|
||||
- Simplest considered solution: one additional loop around the existing grid
|
||||
- Rejected complexity/abstractions: generic optimizer
|
||||
- Unavoidable complexity and justification: projection and pitch jointly affect framing
|
||||
- Measured optimization evidence: bounded 1x5x3 grid
|
||||
|
||||
## Status
|
||||
|
||||
- State: ready-for-review
|
||||
- Done: additive joint FOV grid, compatibility-preserving paths, plan regression, ready-scene 15-candidate ranking and human inspection
|
||||
- Next: add approved landmark/region scoring before any manifest camera calibration
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: this work-package commit
|
||||
- Results: numeric best FOV 62/yaw 10/pitch -25 at mean 0.078843 and ratio 0.667721 was visually rejected as grass-dominated; FOV 38/yaw 10/pitch -10 better matched building scale at mean 0.084220
|
||||
- Remaining risks: full-frame metric does not preserve landmark framing; no manifest values were changed
|
||||
- Documentation updated: `docs/CAMERA_POSE_SWEEP.md`, `docs/modules/world-renderer.md`
|
||||
@@ -0,0 +1,78 @@
|
||||
# M00-QAR-CAMERA-OCCLUDERS-001 — Camera occluder diagnostic
|
||||
|
||||
<!-- OPENWC_CLAIM:M00-QAR-CAMERA-OCCLUDERS-001:sindo-main-codex:2026-07-13 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M00
|
||||
- Program: QAR
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m00-camera-occluders`
|
||||
- Lease expires UTC: 2026-07-13
|
||||
- Integrator: milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Report scene-tree M2/WMO geometry that contains calibrated cameras or intersects each camera-to-target segment.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Changing placements, cameras, FOV, culling, or renderer behavior.
|
||||
- Claiming full coverage of RenderingServer RID-only instances.
|
||||
- Implementing a production visibility or collision service.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/tools/probe_render_camera_occluders.gd`
|
||||
- Shared/hotspots: renderer baseline documentation
|
||||
- Generated/ignored: local JSON probe reports
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Public API/events: headless diagnostic CLI only
|
||||
- Schema/format version: report schema 1
|
||||
- Migration/compatibility: none
|
||||
- Consumers: M00 fidelity workflow
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: calibrated five-point manifest and streaming scene
|
||||
- Blocks: placement versus camera-composition classification
|
||||
- External state: local extracted/cache data
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: camera occluder probe, coordination and documentation gates
|
||||
- Fixtures: five calibrated camera/target segments
|
||||
- Fidelity evidence: scene-tree bounds at paired build 12340 viewpoints
|
||||
- Performance budget: offline diagnostic
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs: probe header and output fields
|
||||
- Module specification: verification/source map
|
||||
- Data-flow diagram: occluder probe flow
|
||||
- Sequence/state/dependency diagrams: synchronous diagnostic; not applicable
|
||||
- Source map/status updates: baseline findings
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced: `camera_containing_geometry`, `segment_intersecting_geometry`
|
||||
- Simplest considered solution: transformed published AABBs
|
||||
- Rejected complexity/abstractions: GPU visibility readback or new BVH
|
||||
- Unavoidable complexity and justification: RID-only geometry cannot be named by this probe
|
||||
- Measured optimization evidence: not applicable
|
||||
|
||||
## Status
|
||||
|
||||
- State: ready
|
||||
- Done: five-point scene-tree AABB probe, containment/intersection classification and documentation
|
||||
- Next: integrator review; calibrate reproducible reference camera direction/FOV separately
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: branch HEAD
|
||||
- Results: zero containing geometry at all five cameras; expected WMO/liquid target intersections; ADT/dense segments unobstructed
|
||||
- Remaining risks: RID-only instances are excluded; manual reference direction and FOV were not recorded exactly
|
||||
- Documentation updated: `docs/RENDER_BASELINE.md`, `docs/modules/world-renderer.md`
|
||||
@@ -0,0 +1,78 @@
|
||||
# M00-QAR-CAMERA-POSE-EVIDENCE-001 — Goldshire camera pose evidence
|
||||
|
||||
<!-- OPENWC_CLAIM:M00-QAR-CAMERA-POSE-EVIDENCE-001:sindo-main-codex:2026-07-14 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M00
|
||||
- Program: QAR
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m00-camera-pose-evidence`
|
||||
- Lease expires UTC: 2026-07-14
|
||||
- Integrator: milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Produce coarse-to-fine yaw/pitch evidence for the build 12340 Goldshire Inn reference and classify whether framing dominates the paired-image gap.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Changing manifest camera defaults before human approval.
|
||||
- Treating minimum perceptual error as proof of exact camera parity.
|
||||
- Committing proprietary reference images or generated candidates.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: camera-pose evidence record
|
||||
- Shared/hotspots: renderer baseline and module fidelity findings
|
||||
- Generated/ignored: all sweep PNGs and JSON under `user://`
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Public API/events: none
|
||||
- Schema/format version: unchanged
|
||||
- Migration/compatibility: none
|
||||
- Consumers: M00 fidelity review
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: merged camera-pose sweep and local build 12340 reference
|
||||
- Blocks: classification of the Goldshire WMO paired gap
|
||||
- External state: `sources/OpenWCReferenceCheckpoints/goldshire_inn_large_wmo.jpg`
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: coarse and fine real sweeps, ranking inspection, repository gates
|
||||
- Fixtures: private Goldshire Inn build 12340 screenshot
|
||||
- Fidelity evidence: ranked mean perceptual error and changed-pixel ratio
|
||||
- Performance budget: offline diagnostic only
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs: unchanged
|
||||
- Module specification: fidelity finding and remaining risk
|
||||
- Data-flow diagram: unchanged
|
||||
- Sequence/state/dependency diagrams: unchanged
|
||||
- Source map/status updates: baseline evidence
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced: none
|
||||
- Simplest considered solution: use the merged bounded grid runner
|
||||
- Rejected complexity/abstractions: optimizer or renderer changes
|
||||
- Unavoidable complexity and justification: real images must be rendered for each candidate
|
||||
- Measured optimization evidence: single pass per candidate
|
||||
|
||||
## Status
|
||||
|
||||
- State: ready-for-review
|
||||
- Done: private reference dimensions verified; coarse, extended and limit sweeps completed; representative candidates inspected; runner failure handling hardened
|
||||
- Next: investigate Goldshire Inn spatial/placement composition separately; do not change manifest yaw/pitch from this metric
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: this work-package commit
|
||||
- Results: zero offset mean error 0.099632; coarse best (-10,-20) 0.077575; metric decreased monotonically to (0,-60) 0.063574/changed ratio 0.549556, while visual inspection showed only grass and no inn
|
||||
- Remaining risks: full-frame color error cannot register a missing landmark; WMO placement/streaming at this checkpoint remains unresolved
|
||||
- Documentation updated: `docs/CAMERA_POSE_SWEEP.md`, `docs/modules/world-renderer.md`
|
||||
@@ -0,0 +1,78 @@
|
||||
# M00-QAR-CAMERA-POSE-SWEEP-001 — Empirical camera pose sweep
|
||||
|
||||
<!-- OPENWC_CLAIM:M00-QAR-CAMERA-POSE-SWEEP-001:sindo-main-codex:2026-07-14 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M00
|
||||
- Program: QAR
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m00-camera-pose-sweep`
|
||||
- Lease expires UTC: 2026-07-14
|
||||
- Integrator: milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Recover reproducible checkpoint framing by sweeping bounded yaw/pitch offsets and ranking paired-image error.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Changing runtime player camera defaults.
|
||||
- Claiming an exact original-client camera pose from perceptual score alone.
|
||||
- Adding computer-vision registration or image warping.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: camera-pose CLI and sweep orchestration
|
||||
- Shared/hotspots: renderer baseline documentation
|
||||
- Generated/ignored: local candidate PNGs and JSON reports
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Public API/events: additive camera yaw/pitch offset and single-pass capture options; new PowerShell sweep command
|
||||
- Schema/format version: capture report schema remains version 1 with additive camera-offset fields
|
||||
- Migration/compatibility: existing capture commands retain zero offsets and two passes
|
||||
- Consumers: M00 paired-fidelity workflow
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: build 12340 reference image, capture tool, paired-image comparator
|
||||
- Blocks: reproducible manual-reference framing
|
||||
- External state: original screenshots remain outside Git
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: capture dry-run offset regression, comparator self-test, bounded local sweep when a display/reference is available, repository gates
|
||||
- Fixtures: one named M00 checkpoint
|
||||
- Fidelity evidence: ranked yaw/pitch candidates with explicit human-approval requirement
|
||||
- Performance budget: offline diagnostic; single-pass mode avoids redundant warm capture
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs: capture and sweep usage
|
||||
- Module specification: camera-pose data flow, verification and source map
|
||||
- Data-flow diagram: updated pose-sweep flow
|
||||
- Sequence/state/dependency diagrams: unchanged
|
||||
- Source map/status updates: baseline workflow and findings
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced: `camera_yaw_offset_degrees`, `camera_pitch_offset_degrees`, `single_pass`
|
||||
- Simplest considered solution: bounded grid over existing capture/comparator tools
|
||||
- Rejected complexity/abstractions: feature matching, optimizer framework, image transforms
|
||||
- Unavoidable complexity and justification: original-client camera angles are not exposed by tested APIs
|
||||
- Measured optimization evidence: single-pass mode halves captures per candidate
|
||||
|
||||
## Status
|
||||
|
||||
- State: ready-for-review
|
||||
- Done: additive capture offsets, single-pass calibration mode, bounded grid runner, ranked JSON contract, grid-plan regression and documentation
|
||||
- Next: run a coarse-to-fine real sweep with the private build 12340 reference directory, then obtain human framing approval
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: this work-package commit
|
||||
- Results: dry-run reported yaw 12.50/pitch -7.50; comparator self-test passed; a 3x2 plan produced six unique candidates; full seven-checkpoint M00 dry-run passed with default zero offsets
|
||||
- Remaining risks: no private reference directory was available in this worktree, so real perceptual ranking and human approval remain external evidence
|
||||
- Documentation updated: `docs/CAMERA_POSE_SWEEP.md`; renderer module verification, risk and source map
|
||||
@@ -0,0 +1,78 @@
|
||||
# M00-QAR-CAPTURE-SHUTDOWN-001 — Capture shutdown drain
|
||||
|
||||
<!-- OPENWC_CLAIM:M00-QAR-CAPTURE-SHUTDOWN-001:sindo-main-codex:2026-07-14 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M00
|
||||
- Program: QAR
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m00-capture-shutdown`
|
||||
- Lease expires UTC: 2026-07-14
|
||||
- Integrator: milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Eliminate the reproducible capture-tool ObjectDB leak by completing owned threaded resource requests and draining deferred scene deletion before process exit.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Refactoring renderer resource ownership.
|
||||
- Hiding genuine RID or worker-task leaks.
|
||||
- Changing capture images, metrics, or streaming behavior.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: capture tool shutdown sequence
|
||||
- Shared/hotspots: renderer baseline documentation
|
||||
- Generated/ignored: verbose Godot logs
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Public API/events: none
|
||||
- Schema/format version: unchanged
|
||||
- Migration/compatibility: none
|
||||
- Consumers: M00 baseline runner
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: reproducible verbose leak diagnostic
|
||||
- Blocks: clean M00 capture shutdown evidence
|
||||
- External state: none
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: identical verbose dry-run before/after, M00 dry-run, repository gates
|
||||
- Fixtures: ADT-boundary filtered capture
|
||||
- Fidelity evidence: no visual behavior change
|
||||
- Performance budget: blocking resource completion is restricted to shutdown; two SceneTree drain frames only
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs: shutdown rationale comment
|
||||
- Module specification: recovery/known-risk update
|
||||
- Data-flow diagram: unchanged
|
||||
- Sequence/state/dependency diagrams: shutdown sequence documented in baseline
|
||||
- Source map/status updates: baseline evidence
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced: none
|
||||
- Simplest considered solution: await two SceneTree frames after `queue_free` and finish registered in-flight ResourceLoader requests during loader shutdown
|
||||
- Rejected complexity/abstractions: resource registry or manual child traversal
|
||||
- Unavoidable complexity and justification: deferred deletion requires frame drain
|
||||
- Measured optimization evidence: not applicable
|
||||
|
||||
## Status
|
||||
|
||||
- State: ready-for-review
|
||||
- Done: one leaked RefCounted reproduced; isolation attributed it to `StreamingWorldLoader`; active threaded tile request was confirmed as the retained object; all owned threaded resource registries now finish in-progress requests before clearing; two identical verbose ADT-boundary runs and the full M00 dry-run completed without ObjectDB leak diagnostics
|
||||
- Next: integrator review and merge
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: this work-package commit
|
||||
- Results: verbose filtered capture returned one report and zero `Leaked instance`/`ObjectDB instances leaked` lines on two consecutive runs; `run_render_baseline.ps1 -DryRun` passed all gates and seven checkpoints
|
||||
- Remaining risks:
|
||||
- Documentation updated: `docs/modules/world-renderer.md` shutdown sequence, ownership contract, recovery table and known-risk status
|
||||
@@ -0,0 +1,78 @@
|
||||
# M00-QAR-COORD-CALIBRATION-001 — Renderer coordinate calibration
|
||||
|
||||
<!-- OPENWC_CLAIM:M00-QAR-COORD-CALIBRATION-001:sindo-main-codex:2026-07-13 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M00
|
||||
- Program: QAR
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m00-coordinate-calibration`
|
||||
- Lease expires UTC: 2026-07-13
|
||||
- Integrator: milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Record the five observed build 12340 camera positions as non-proprietary golden coordinates and determine whether the existing WoW/Godot position formula round-trips them.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Introducing the M01 production `CoordinateMapper` contract.
|
||||
- Changing terrain, placement, WMO, liquid, or streaming behavior.
|
||||
- Claiming camera composition parity from coordinate round-trip alone.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/tools/verify_render_coordinate_calibration.gd`
|
||||
- Shared/hotspots: `src/tools/render_baseline_manifest.json`, renderer baseline documentation
|
||||
- Generated/ignored: original-client screenshots and local reports
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Public API/events: none; headless diagnostic only
|
||||
- Schema/format version: additive checkpoint calibration metadata, manifest schema remains 1
|
||||
- Migration/compatibility: existing capture consumers ignore additive fields
|
||||
- Consumers: M00 fidelity workflow and future M01 golden fixtures
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: five accepted original-client build 12340 viewpoints
|
||||
- Blocks: diagnosis of paired camera/placement mismatch
|
||||
- External state: screenshots remain outside Git
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: coordinate calibration probe, baseline manifest, coordination and documentation gates
|
||||
- Fixtures: five `reference_wow_camera` values in renderer manifest
|
||||
- Fidelity evidence: positions were observed in build 12340 during the paired session
|
||||
- Performance budget: negligible headless arithmetic
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs: diagnostic script header
|
||||
- Module specification: renderer verification/source map update
|
||||
- Data-flow diagram: baseline calibration flow update
|
||||
- Sequence/state/dependency diagrams: not applicable; stateless synchronous probe
|
||||
- Source map/status updates: renderer module and baseline document
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced: `reference_wow_camera`, `maximum_round_trip_error`
|
||||
- Simplest considered solution: direct formula probe over manifest fixtures
|
||||
- Rejected complexity/abstractions: production domain mapper before M01
|
||||
- Unavoidable complexity and justification: none
|
||||
- Measured optimization evidence: not applicable
|
||||
|
||||
## Status
|
||||
|
||||
- State: ready
|
||||
- Done: five golden camera points, headless round-trip probe, runner integration and documentation
|
||||
- Next: integrator review; diagnose terrain height/placement/composition separately
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: branch HEAD
|
||||
- Results: five points passed with maximum mapping/round-trip error 0.000015
|
||||
- Remaining risks: correct position arithmetic does not prove terrain height, placement or camera direction parity
|
||||
- Documentation updated: `docs/RENDER_BASELINE.md`, `docs/modules/world-renderer.md`
|
||||
@@ -0,0 +1,78 @@
|
||||
# M00-QAR-FOV-SWEEP-001 — Empirical camera FOV sweep
|
||||
|
||||
<!-- OPENWC_CLAIM:M00-QAR-FOV-SWEEP-001:sindo-main-codex:2026-07-14 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M00
|
||||
- Program: QAR
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m00-fov-sweep`
|
||||
- Lease expires UTC: 2026-07-14
|
||||
- Integrator: milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Support a bounded empirical FOV sweep when build 12340 does not expose a readable camera FOV CVar.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Claiming exact WoW FOV from a single manually aimed screenshot.
|
||||
- Changing runtime player camera defaults.
|
||||
- Automating yaw/pitch registration or image warping.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: FOV/filter CLI additions in checkpoint tools
|
||||
- Shared/hotspots: renderer baseline documentation
|
||||
- Generated/ignored: local sweep PNGs and JSON reports
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Public API/events: additive `--camera-fov` and comparator `--only` CLI options
|
||||
- Schema/format version: unchanged
|
||||
- Migration/compatibility: existing commands retain defaults
|
||||
- Consumers: M00 fidelity workflow
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: build 12340 reference JPG and paired-image comparator
|
||||
- Blocks: empirical projection ranking
|
||||
- External state: original screenshots remain outside Git
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: synthetic comparator test, capture dry-run with override, bounded real sweep
|
||||
- Fixtures: `elwynn_adt_boundary` reference
|
||||
- Fidelity evidence: ranked perceptual metrics with manual-direction limitation
|
||||
- Performance budget: offline diagnostic
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs: CLI headers
|
||||
- Module specification: verification/source map if needed
|
||||
- Data-flow diagram: FOV sweep flow
|
||||
- Sequence/state/dependency diagrams: not applicable
|
||||
- Source map/status updates: baseline findings
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced: `camera_fov_override`, `only_filter`
|
||||
- Simplest considered solution: reuse capture and comparator
|
||||
- Rejected complexity/abstractions: computer vision registration framework
|
||||
- Unavoidable complexity and justification: multiple captures are required because client FOV is inaccessible
|
||||
- Measured optimization evidence: not applicable
|
||||
|
||||
## Status
|
||||
|
||||
- State: ready
|
||||
- Done: client FOV access audit, capture override, comparator filter/regression, dedicated-camera ownership fix and bounded sweep
|
||||
- Next: integrator review; obtain reproducible yaw/pitch/zoom metadata before changing normative FOV
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: branch HEAD
|
||||
- Results: corrected ranking 26=0.079588, 38=0.079633, 50=0.084353, 62=0.088360, 86=0.097993; plateau is inconclusive
|
||||
- Remaining risks: build 12340 FOV is inaccessible through tested APIs; manual direction/framing dominates metrics
|
||||
- Documentation updated: `docs/RENDER_BASELINE.md`, `docs/modules/world-renderer.md`
|
||||
@@ -0,0 +1,78 @@
|
||||
# M00-QAR-LANDMARK-REGION-DIFF-001 — Landmark-region visual diff
|
||||
|
||||
<!-- OPENWC_CLAIM:M00-QAR-LANDMARK-REGION-DIFF-001:sindo-main-codex:2026-07-14 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M00
|
||||
- Program: QAR
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m00-landmark-region-diff`
|
||||
- Lease expires UTC: 2026-07-14
|
||||
- Integrator: milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Rank camera candidates inside an approved common screen region so unrelated grass/sky coverage cannot dominate landmark framing.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Automatic feature detection or image warping.
|
||||
- Different crops for reference and candidate.
|
||||
- Changing manifest camera values without human approval.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: comparator common-region option and sweep pass-through
|
||||
- Shared/hotspots: camera-pose documentation and renderer fidelity findings
|
||||
- Generated/ignored: local ROI sweep images/reports
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Public API/events: additive comparator `--region x,y,width,height` and sweep `-ComparisonRegion`
|
||||
- Schema/format version: report/ranking schema remains version 1 with additive region metadata
|
||||
- Migration/compatibility: omitted region preserves full-frame behavior
|
||||
- Consumers: M00 paired camera calibration
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: ready-scene joint pitch/FOV sweep
|
||||
- Blocks: landmark-weighted Goldshire framing decision
|
||||
- External state: private build 12340 reference
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: comparator synthetic ROI regression, invalid-bound regression, sweep plan, bounded real ROI sweep, repository gates
|
||||
- Fixtures: synthetic 2x2 images and private Goldshire reference
|
||||
- Fidelity evidence: facade-region ranking plus human inspection
|
||||
- Performance budget: region comparison visits only selected pixels
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs: comparator usage and PowerShell parameter
|
||||
- Module specification: ROI finding
|
||||
- Data-flow diagram: approved region into comparator
|
||||
- Sequence/state/dependency diagrams: unchanged
|
||||
- Source map/status updates: camera-pose guide
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced: `ComparisonRegion`, `comparison_region`
|
||||
- Simplest considered solution: one common integer rectangle
|
||||
- Rejected complexity/abstractions: masks, feature detectors, separate movable crops
|
||||
- Unavoidable complexity and justification: full-frame color error is biased by large terrain regions
|
||||
- Measured optimization evidence: bounded pixel loop
|
||||
|
||||
## Status
|
||||
|
||||
- State: ready-for-review
|
||||
- Done: common ROI parser/validation/reporting, synthetic inclusion/exclusion regression, sweep pass-through, 15-candidate Goldshire rescoring and human inspection
|
||||
- Next: define explicit approved landmark coordinates or mask; do not calibrate manifest from the common rectangle alone
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: this work-package commit
|
||||
- Results: ROI [300,100,1400,650] ranked FOV 38/yaw 10/pitch -15 first at mean 0.081777 and ratio 0.697185; visually the facade remains oversized and cropped
|
||||
- Remaining risks: rectangle includes non-semantic road/terrain pixels and cannot prove landmark alignment
|
||||
- Documentation updated: `docs/CAMERA_POSE_SWEEP.md`, `docs/modules/world-renderer.md`
|
||||
@@ -0,0 +1,78 @@
|
||||
# M00-QAR-TERRAIN-HEIGHT-001 — Terrain height diagnostic
|
||||
|
||||
<!-- OPENWC_CLAIM:M00-QAR-TERRAIN-HEIGHT-001:sindo-main-codex:2026-07-13 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M00
|
||||
- Program: QAR
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m00-terrain-height`
|
||||
- Lease expires UTC: 2026-07-13
|
||||
- Integrator: milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Measure rendered terrain height and camera clearance at the five build 12340 golden checkpoints without changing renderer behavior.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Adding a runtime terrain-query API or collision system.
|
||||
- Changing terrain geometry, coordinate mapping, placements, or cameras.
|
||||
- Implementing the M01 CoordinateMapper.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/tools/probe_render_terrain_height.gd`
|
||||
- Shared/hotspots: renderer baseline documentation and runner
|
||||
- Generated/ignored: local JSON probe reports
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Public API/events: headless diagnostic CLI only
|
||||
- Schema/format version: report schema 1
|
||||
- Migration/compatibility: none
|
||||
- Consumers: M00 fidelity diagnosis
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: calibrated renderer manifest and active terrain meshes
|
||||
- Blocks: classification of under-terrain camera gaps
|
||||
- External state: local extracted/cache data
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: terrain probe, M00 dry-run, coordination and documentation gates
|
||||
- Fixtures: five calibrated manifest checkpoints
|
||||
- Fidelity evidence: camera clearance against rendered OpenWC terrain
|
||||
- Performance budget: offline diagnostic only
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs: script CLI header
|
||||
- Module specification: verification/source map
|
||||
- Data-flow diagram: terrain probe flow
|
||||
- Sequence/state/dependency diagrams: synchronous diagnostic; not applicable
|
||||
- Source map/status updates: baseline and renderer module
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced: `terrain_height`, `camera_clearance`
|
||||
- Simplest considered solution: CPU ray against already loaded mesh
|
||||
- Rejected complexity/abstractions: new parser, physics collision, runtime query service
|
||||
- Unavoidable complexity and justification: tile-local ray transform is required by mesh ownership
|
||||
- Measured optimization evidence: not applicable
|
||||
|
||||
## Status
|
||||
|
||||
- State: ready
|
||||
- Done: active-mesh terrain probe, four clearance measurements, isolated waterfall missing-mesh confirmation and documentation
|
||||
- Next: integrator review; placement/composition diagnosis for four points and tile 30_49 ownership diagnosis remain separate packages
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: branch HEAD
|
||||
- Results: four cameras are 12.034..90.178 units above terrain; waterfall tile has no accessible terrain mesh after 10-second settle
|
||||
- Remaining risks: mesh ray does not measure WMO/M2 occlusion; waterfall missing mesh requires streaming ownership investigation
|
||||
- Documentation updated: `docs/RENDER_BASELINE.md`, `docs/modules/world-renderer.md`
|
||||
@@ -0,0 +1,78 @@
|
||||
# M00-QAR-TILE-OWNERSHIP-001 — Waterfall terrain tile ownership
|
||||
|
||||
<!-- OPENWC_CLAIM:M00-QAR-TILE-OWNERSHIP-001:sindo-main-codex:2026-07-13 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M00
|
||||
- Program: QAR
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m00-waterfall-tile-ownership`
|
||||
- Lease expires UTC: 2026-07-13
|
||||
- Integrator: milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Identify the runtime transition that prevents waterfall tile `30_49` from exposing a terrain mesh to the height probe.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Changing streaming budgets or terrain behavior.
|
||||
- Rebuilding caches or modifying extracted assets.
|
||||
- Adding a production terrain query service.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: terrain probe runtime ownership diagnostics
|
||||
- Shared/hotspots: renderer baseline documentation
|
||||
- Generated/ignored: local probe reports and caches
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Public API/events: additive diagnostic JSON fields
|
||||
- Schema/format version: terrain report remains schema 1
|
||||
- Migration/compatibility: additive fields only
|
||||
- Consumers: M00 fidelity diagnosis
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: merged terrain-height probe and local cache inventory
|
||||
- Blocks: waterfall terrain ownership classification
|
||||
- External state: local extracted/cache data
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: isolated waterfall probe, coordination and documentation gates
|
||||
- Fixtures: checkpoint tile `30_49`
|
||||
- Fidelity evidence: runtime state correlated with build 12340 waterfall viewpoint
|
||||
- Performance budget: offline diagnostic
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs: diagnostic output fields
|
||||
- Module specification: verification/source map if behavior changes
|
||||
- Data-flow diagram: update ownership transition if needed
|
||||
- Sequence/state/dependency diagrams: document observed tile transition
|
||||
- Source map/status updates: baseline findings
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced: `available`, `queued_index`, `loading`, `state_present`, `mesh_source`
|
||||
- Simplest considered solution: inspect existing loader registries read-only
|
||||
- Rejected complexity/abstractions: new tracing framework
|
||||
- Unavoidable complexity and justification: none
|
||||
- Measured optimization evidence: not applicable
|
||||
|
||||
## Status
|
||||
|
||||
- State: ready
|
||||
- Done: raw/cache inventory, isolated runtime state, mesh AABB/local probe and nearby sampling diagnosis
|
||||
- Next: integrator review; placement/composition remains the actual paired-camera gap
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: branch HEAD
|
||||
- Results: tile 30_49 ownership and meshes are healthy; exact ray misses a triangle seam/edge, while a 2-unit offset samples terrain at 113.872
|
||||
- Remaining risks: nearby estimate is diagnostic and must not become a gameplay terrain-query contract
|
||||
- Documentation updated: `docs/RENDER_BASELINE.md`, `docs/modules/world-renderer.md`
|
||||
@@ -0,0 +1,78 @@
|
||||
# M00-QAR-VISUAL-DIFF-001 — Renderer checkpoint visual diff
|
||||
|
||||
<!-- OPENWC_CLAIM:M00-QAR-VISUAL-DIFF-001:sindo-main-codex:2026-07-13 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M00
|
||||
- Program: QAR
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m00-checkpoint-diff`
|
||||
- Lease expires UTC: 2026-07-13
|
||||
- Integrator: milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Provide a deterministic, headless perceptual comparison for paired renderer checkpoint PNGs, with machine-readable results and synthetic regression coverage.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Supplying or committing original-client screenshots.
|
||||
- Claiming visual parity or replacing human approval.
|
||||
- Image registration, camera correction, or temporal animation matching.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: `src/tools/compare_render_checkpoints.gd`
|
||||
- Shared/hotspots: `src/tools/render_baseline_manifest.json`, `tools/run_render_baseline.ps1`, `docs/RENDER_BASELINE.md`, `docs/modules/world-renderer.md`, `RENDER.md`
|
||||
- Generated/ignored: comparison reports, diff PNGs, original-client captures
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Public API/events: headless CLI arguments and JSON report documented in renderer baseline docs
|
||||
- Schema/format version: comparison report schema 1; baseline manifest schema remains 1
|
||||
- Migration/compatibility: additive manifest budget fields
|
||||
- Consumers: developers and CI
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: Godot `Image` API and M00 checkpoint naming contract
|
||||
- Blocks: automated portion of original-client paired comparison
|
||||
- External state: approved build 12340 screenshots remain unavailable
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: headless self-test; manifest verification; documentation and coordination gates
|
||||
- Fixtures: tool-generated identical and changed 2x2 PNGs
|
||||
- Fidelity evidence: algorithm is deterministic; real-client evidence remains a manual/external input
|
||||
- Performance budget: offline checkpoint operation, linear in pixel count
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs: CLI usage in script header
|
||||
- Module specification: update renderer verification and source map
|
||||
- Data-flow diagram: update baseline documentation
|
||||
- Sequence/state/dependency diagrams: not stateful or asynchronous; not applicable
|
||||
- Source map/status updates: renderer module and RENDER notes
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced: `mean_perceptual_error`, `changed_pixel_ratio`
|
||||
- Simplest considered solution: direct Godot Image iteration with no dependency
|
||||
- Rejected complexity/abstractions: external image library, SSIM window pipeline, image registration
|
||||
- Unavoidable complexity and justification: sRGB linearization avoids comparing encoded channel values directly
|
||||
- Measured optimization evidence: not required for offline seven-checkpoint comparison
|
||||
|
||||
## Status
|
||||
|
||||
- State: ready
|
||||
- Done: comparator, runner integration, manifest tolerance contract, synthetic regression, five local build 12340 references, three viewpoint calibrations, corrected visual capture and first paired report
|
||||
- Next: integrator review; coordinate/placement gap should be resolved before tolerance calibration
|
||||
- Blocked by: real paired screenshots only for final human fidelity approval
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: branch HEAD
|
||||
- Results: ten pairs compared with no missing candidates; mean error 0.0707..0.1746 and changed ratio 0.5504..0.8187; human review identified under-terrain/inside-geometry camera mismatches
|
||||
- Remaining risks: coordinate/placement mismatch blocks meaningful tolerance calibration; synthetic animation, dusk and full human approval remain incomplete
|
||||
- Documentation updated: `docs/RENDER_BASELINE.md`, `docs/modules/world-renderer.md`, `RENDER.md`
|
||||
@@ -0,0 +1,78 @@
|
||||
# M00-QAR-WMO-READINESS-001 — Goldshire WMO readiness
|
||||
|
||||
<!-- OPENWC_CLAIM:M00-QAR-WMO-READINESS-001:sindo-main-codex:2026-07-14 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M00
|
||||
- Program: QAR
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m00-wmo-readiness`
|
||||
- Lease expires UTC: 2026-07-14
|
||||
- Integrator: milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Prevent pose calibration against an under-loaded WMO scene and replace invalid Goldshire evidence with a baseline-ready comparison.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Changing runtime streaming budgets.
|
||||
- Claiming all WMO queues must globally drain before a checkpoint.
|
||||
- Changing manifest camera pose without valid paired evidence.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: pose-sweep readiness default and Goldshire timing evidence
|
||||
- Shared/hotspots: renderer baseline/module fidelity findings
|
||||
- Generated/ignored: timing and sweep images under `user://`
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Public API/events: pose sweep default wait becomes 8 seconds, matching the M00 manifest
|
||||
- Schema/format version: unchanged
|
||||
- Migration/compatibility: explicit `-WaitSeconds` remains supported
|
||||
- Consumers: M00 paired-fidelity workflow
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: Goldshire pose evidence and private build 12340 reference
|
||||
- Blocks: valid Goldshire camera-pose ranking
|
||||
- External state: private reference and generated captures remain outside Git
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: 2/8/15 second timing audit, ready-scene 3x3 pose sweep, baseline dry-run, repository gates
|
||||
- Fixtures: private Goldshire Inn screenshot
|
||||
- Fidelity evidence: visual WMO presence plus streaming snapshot and paired metrics
|
||||
- Performance budget: offline diagnostic
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs: PowerShell default
|
||||
- Module specification: corrected readiness/fidelity finding
|
||||
- Data-flow diagram: readiness precondition
|
||||
- Sequence/state/dependency diagrams: unchanged
|
||||
- Source map/status updates: camera-pose operational guide
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced: none
|
||||
- Simplest considered solution: reuse the manifest's measured 8-second stabilization default
|
||||
- Rejected complexity/abstractions: global queue barrier that would include unrelated distant work
|
||||
- Unavoidable complexity and justification: WMO publication is asynchronous
|
||||
- Measured optimization evidence: 2/8/15 second snapshots
|
||||
|
||||
## Status
|
||||
|
||||
- State: ready-for-review
|
||||
- Done: timing audit invalidated the 2-second evidence; sweep default now matches the 8-second manifest; ready-scene 3x3 grid and human inspection completed
|
||||
- Next: jointly refine negative pitch and FOV around the ready-scene Goldshire candidate
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: this work-package commit
|
||||
- Results: WMO instances 9/113/328 at 2/8/15 seconds; inn absent at 2 and visible at 8/15; ready grid improved mean error from 0.101402 at (0,0) to 0.087952 at (10,-10)
|
||||
- Remaining risks: global WMO queues still contain unrelated work; best pitch is at the tested boundary and building scale indicates unresolved FOV/framing
|
||||
- Documentation updated: `docs/CAMERA_POSE_SWEEP.md`, `docs/modules/world-renderer.md`
|
||||
@@ -0,0 +1,5 @@
|
||||
# Fallback claims
|
||||
|
||||
Этот каталог используется только когда общий issue/PR tracker недоступен. Каждый активный work package создаёт отдельный Markdown-файл по [`../TASK_TEMPLATE.md`](../TASK_TEMPLATE.md).
|
||||
|
||||
Файл без валидного `OPENWC_CLAIM` marker не считается claim. Не хранить здесь secrets, credentials, proprietary data или большие логи.
|
||||
@@ -0,0 +1,63 @@
|
||||
# Renderer Camera Pose Sweep
|
||||
|
||||
This offline M00 diagnostic recovers reproducible checkpoint framing when an original-client screenshot has no recorded camera yaw or pitch. It ranks a bounded grid; it does not change renderer or player-camera defaults and does not prove the exact build 12340 camera pose without human approval.
|
||||
|
||||
## Camera contract
|
||||
|
||||
`capture_render_checkpoints.gd` accepts additive `--camera-yaw-offset` and `--camera-pitch-offset` values in degrees. Yaw rotates the no-roll checkpoint basis around Godot world `Vector3.UP`; pitch then rotates around the camera-local right axis. Zero offsets preserve the manifest target exactly. `--single-pass` captures only `cold_process` and is intended for pose calibration, not performance baselines.
|
||||
|
||||
## Workflow
|
||||
|
||||
```powershell
|
||||
.\tools\sweep_render_checkpoint_camera_pose.ps1 `
|
||||
-ReferenceDirectory 'D:\private-fixtures\wow-3.3.5a-checkpoints' `
|
||||
-Checkpoint elwynn_adt_boundary `
|
||||
-YawOffsets -15,-10,-5,0,5,10,15 `
|
||||
-PitchOffsets -10,-5,0,5,10 `
|
||||
-CameraFovValues 38,50,62 `
|
||||
-ViewportWidth 1280 -ViewportHeight 960 `
|
||||
-WaitSeconds 8
|
||||
```
|
||||
|
||||
Each candidate receives its own output directory and comparison report. `-CameraFovValues` adds FOV as another bounded grid dimension; omit it to retain the scalar `-CameraFov` workflow and legacy candidate paths. The viewport must exactly match the reference image dimensions; the runner fails on `size_mismatch` instead of inventing a score. The default 8-second wait matches the M00 manifest and is a readiness precondition: lowering it requires evidence that the checkpoint's WMO/M2 content has already appeared. `ranking.json` sorts candidates by mean perceptual error and then changed-pixel ratio. Use `-PlanOnly` to validate the Cartesian grid and output paths without rendering. Run a coarse grid first, then a finer grid around the best candidate.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Y[Yaw offsets in degrees] --> G[Bounded Cartesian grid]
|
||||
P[Pitch offsets in degrees] --> G
|
||||
G --> C[Single-pass checkpoint captures]
|
||||
R[Original-client reference] --> D[Perceptual comparator]
|
||||
C --> D
|
||||
D --> J[ranking.json]
|
||||
J --> H[Human framing approval]
|
||||
```
|
||||
|
||||
## Interpretation and recovery
|
||||
|
||||
The smallest error is a candidate, not automatic fidelity approval. Geometry, materials, lighting and FOV can move the perceptual optimum away from the true original-client pose. Inspect the best images manually and retain the original reference outside Git. A missing matching reference or candidate is a hard error; correct the checkpoint filter or filenames and rerun. Generated PNG and JSON outputs are disposable local evidence.
|
||||
|
||||
## Verification
|
||||
|
||||
The headless capture dry-run must report requested yaw and pitch offsets. Comparator `--self-test` covers paired metrics. Sweep `-PlanOnly` covers deterministic grid expansion and output naming without requiring a display. A real ranked sweep requires a display and a private build 12340 reference image.
|
||||
|
||||
## Goldshire Inn evidence — 2026-07-12
|
||||
|
||||
The private `2560x1440` build 12340 `goldshire_inn_large_wmo` reference was evaluated at FOV 62 degrees. A coarse 5x5 grid over yaw/pitch `[-20, -10, 0, 10, 20]` reduced mean error from `0.099632` at `(0, 0)` to `0.077575` at `(-10, -20)`. Extending pitch produced `0.070901` at `(0, -40)` and `0.063574` at `(0, -60)` with changed-pixel ratio `0.549556`.
|
||||
|
||||
These values are invalid camera-pose evidence because the sweep used a 2-second wait. A subsequent readiness audit showed 9 WMO instances and no inn after 2 seconds, 113 instances with the inn visible after 8 seconds, and 328 instances after 15 seconds. The apparent monotonic improvement rewarded grass while the landmark was still absent; it does not demonstrate a spatial/placement defect. The manifest pose remains unchanged pending a ready-scene rerun.
|
||||
|
||||
The ready-scene 3x3 rerun at FOV 62 degrees ranked `(yaw=10, pitch=-10)` first with mean error `0.087952` and changed-pixel ratio `0.665527`, compared with `0.101402`/`0.702489` at zero offsets. Human inspection confirms that the inn is present and the direction is plausible. Pitch remains on the tested boundary and the building scale differs from the reference, so these offsets are coarse evidence only; a joint pitch/FOV refinement is required before changing manifest camera values.
|
||||
|
||||
## Goldshire joint pitch/FOV refinement — 2026-07-12
|
||||
|
||||
A WMO-ready joint grid fixed yaw at 10 degrees and evaluated pitch `[-25, -20, -15, -10, -5]` across FOV `[38, 50, 62]`. Full-frame error ranked FOV 62/pitch -25 first at `0.078843` with changed-pixel ratio `0.667721`. Human inspection rejects it as a manifest calibration: the view is dominated by grass and crops the inn. FOV 38/pitch -10 is closer to the reference building scale but ranks only `0.084220`, and horizontal landmark alignment remains different.
|
||||
|
||||
The grid therefore has no approved joint optimum. Full-frame color error continues to trade landmark framing against large terrain regions. The manifest remains unchanged. The next registration method must score an approved inn/road/tree landmark region or explicit landmark coordinates rather than the entire image.
|
||||
|
||||
## Common landmark region — 2026-07-12
|
||||
|
||||
The comparator accepts an optional common screen rectangle through `--region x,y,width,height`; the sweep runner exposes it as `-ComparisonRegion x,y,width,height`. Coordinates are integer pixels in the shared reference/candidate image space. Both images must have identical dimensions and the rectangle must be fully inside them. Omitting the option preserves full-frame behavior.
|
||||
|
||||
The existing 15 WMO-ready Goldshire candidates were rescored inside `[300,100,1400,650]`, covering the reference facade/chimney and nearby trees. ROI error ranked FOV 38/yaw 10/pitch -15 first at `0.081777` with changed-pixel ratio `0.697185`. This is an interior pitch candidate and improves on the full-frame grass preference, but human inspection still rejects manifest calibration: the facade is too large, its upper part is cropped, and the rectangle retains substantial road/terrain pixels. A common rectangle is useful diagnostic weighting, not semantic landmark registration. Explicit approved landmark coordinates or a mask remain necessary.
|
||||
|
||||
During this run the sweep orchestration was hardened in two ways: expected comparator exit code `1` is now collected through an explicit child process, and viewport dimensions are explicit. A reference/candidate `size_mismatch` now fails before ranking instead of producing an empty metric.
|
||||
@@ -37,6 +37,7 @@
|
||||
- Packed GUIDs, update fields, create/update/out-of-range/destroy.
|
||||
- Movement flags, heartbeat, jump/fall/swim/fly, transports, knockback и spline movement.
|
||||
- Server time, tutorial flags, account data и feature capability detection.
|
||||
- Warden handshake/module compatibility для явно поддерживаемых test/private server profiles с sandbox и без ориентации на official servers.
|
||||
|
||||
## Player и camera
|
||||
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
# OpenWC Coding Standard
|
||||
|
||||
## Главные принципы
|
||||
|
||||
1. Код читается значительно чаще, чем пишется.
|
||||
2. Явное и точное имя предпочтительнее короткого и двусмысленного.
|
||||
3. KISS применяется по умолчанию: выбирается самое простое решение, которое корректно выполняет текущую задачу и сохраняет архитектурные границы.
|
||||
4. Сложность допускается только там, где она следует из предметной области, формата, протокола, производительности или совместимости 3.3.5a.
|
||||
5. Неизбежная сложность должна быть локализована, протестирована и документирована.
|
||||
|
||||
## Имена
|
||||
|
||||
Не нужно бояться длинных имён переменных, функций, типов, сигналов и файлов. Имя должно позволять понять назначение сущности без поиска всех мест использования.
|
||||
|
||||
Хорошо:
|
||||
|
||||
```gdscript
|
||||
var terrain_control_splat_cache_finalize_queue: Array = []
|
||||
var maximum_concurrent_terrain_upgrade_tasks: int = 1
|
||||
|
||||
func enqueue_terrain_control_splat_cache_finalize(tile_coordinate: Vector2i) -> void:
|
||||
pass
|
||||
|
||||
func should_retain_streaming_tile_outside_visible_radius(tile_state: TileState) -> bool:
|
||||
return false
|
||||
```
|
||||
|
||||
Плохо:
|
||||
|
||||
```gdscript
|
||||
var q = []
|
||||
var max_t = 1
|
||||
|
||||
func do_ctl(p):
|
||||
pass
|
||||
|
||||
func chk(s):
|
||||
return false
|
||||
```
|
||||
|
||||
Длина не является самоцелью. Не повторять контекст, который уже однозначно задаётся типом или модулем:
|
||||
|
||||
```gdscript
|
||||
# Внутри TerrainStreamingService:
|
||||
func enqueue_finalize(tile: TerrainTile) -> void:
|
||||
pass
|
||||
```
|
||||
|
||||
Такое имя допустимо, если `finalize` в этом классе имеет только одно значение. Если существуют terrain mesh, material и liquid finalization, имя должно уточнять конкретную операцию.
|
||||
|
||||
## Naming rules
|
||||
|
||||
- Типы/classes: `PascalCase`.
|
||||
- GDScript functions/variables/signals: `snake_case`.
|
||||
- Constants: `UPPER_SNAKE_CASE`.
|
||||
- Private implementation members: `_leading_underscore` по Godot convention.
|
||||
- C++ следует Godot/GDExtension convention внутри существующего native module; новый API не смешивает стили в одной области.
|
||||
- Boolean: `is_`, `has_`, `can_`, `should_`, `needs_`, `was_`.
|
||||
- Collections называются во множественном числе или по роли: `loaded_tiles`, `pending_packet_events`, `entity_by_guid`.
|
||||
- Functions начинаются с действия: `load_`, `parse_`, `build_`, `enqueue_`, `apply_`, `validate_`, `resolve_`, `convert_`, `release_`.
|
||||
- Units указываются в имени: `_milliseconds`, `_seconds`, `_bytes`, `_meters`, `_tiles`, `_radians`, `_degrees`.
|
||||
- Coordinate space указывается, если возможна путаница: `canonical_wow_position`, `godot_world_position`, `adt_tile_coordinate`.
|
||||
- Версия/build/profile указывается в типе или namespace, если semantics различаются: `Wotlk335ProtocolProfile`.
|
||||
|
||||
Допустимые устоявшиеся доменные сокращения:
|
||||
|
||||
- WoW, MPQ, BLP, DBC;
|
||||
- ADT, WDT, WDL;
|
||||
- M2, WMO;
|
||||
- GUID, SRP, API, UI;
|
||||
- LOD, RID, GPU, CPU;
|
||||
- SQL, TCP.
|
||||
|
||||
Новые локальные сокращения из двух-трёх букв без общеизвестного значения запрещены.
|
||||
|
||||
## Запрещённые размытые имена
|
||||
|
||||
Избегать без уточнения:
|
||||
|
||||
```text
|
||||
data
|
||||
info
|
||||
item
|
||||
object
|
||||
value
|
||||
temp
|
||||
result
|
||||
state
|
||||
manager
|
||||
helper
|
||||
utils
|
||||
process
|
||||
handle
|
||||
do_work
|
||||
```
|
||||
|
||||
Они допустимы только когда тип и ближайший scope делают значение полностью однозначным. Например, `parse_packet() -> PacketParseResult` может использовать локальную переменную `result`, если функция мала и другого результата нет.
|
||||
|
||||
`Manager`, `Helper`, `Utils` почти всегда скрывают несколько обязанностей. Предпочтительны названия по роли:
|
||||
|
||||
```text
|
||||
WorldSession
|
||||
StreamingTargetPlanner
|
||||
RenderBudgetScheduler
|
||||
CoordinateMapper
|
||||
AssetRepository
|
||||
QuestValidator
|
||||
ServerSchemaAdapter
|
||||
```
|
||||
|
||||
## KISS decision order
|
||||
|
||||
При выборе реализации двигаться по порядку:
|
||||
|
||||
1. Простая typed function с явными inputs/outputs.
|
||||
2. Маленький value object или immutable result.
|
||||
3. Небольшой service с одной ответственностью.
|
||||
4. Composition нескольких services.
|
||||
5. Interface/strategy только при реальной вариативности или boundary.
|
||||
6. Generic framework только после нескольких подтверждённых use cases.
|
||||
7. Native/low-level optimization только после измеренного bottleneck.
|
||||
|
||||
Не переходить на следующий уровень, если предыдущий решает задачу без нарушения correctness и architecture.
|
||||
|
||||
## Простота функций
|
||||
|
||||
- Одна функция выполняет одну логическую операцию.
|
||||
- Название функции описывает observable effect.
|
||||
- Inputs и outputs явные; hidden global state минимален.
|
||||
- Guard clauses предпочтительнее глубокой вложенности.
|
||||
- Happy path должен читаться сверху вниз.
|
||||
- Side effects не смешиваются с pure calculation без необходимости.
|
||||
- Parsing, validation, state mutation и presentation отделяются.
|
||||
- Функция не принимает набор boolean flags, меняющих её смысл.
|
||||
|
||||
Плохо:
|
||||
|
||||
```gdscript
|
||||
func load_tile(path: String, high: bool, async: bool, editor: bool):
|
||||
pass
|
||||
```
|
||||
|
||||
Лучше:
|
||||
|
||||
```gdscript
|
||||
func request_streaming_tile_load(request: StreamingTileLoadRequest) -> void:
|
||||
pass
|
||||
```
|
||||
|
||||
При этом `StreamingTileLoadRequest` оправдан только если параметры действительно образуют устойчивый contract. Для двух простых аргументов не нужно создавать объект только ради архитектурности.
|
||||
|
||||
## Простота data flow
|
||||
|
||||
- Один владелец mutable state.
|
||||
- Один канонический тип для одной концепции.
|
||||
- Один mapper для coordinate conversion.
|
||||
- Один authoritative source для content/runtime state.
|
||||
- Изменения передаются commands/events/read models, а не чтением чужих внутренних dictionaries.
|
||||
- Не создавать второй cache, registry или event bus без описанной отдельной ответственности.
|
||||
- Не использовать строки и `call()` как основной type system.
|
||||
- Dictionary допустим на binary/dynamic boundary; внутри domain данные переводятся в typed representation.
|
||||
|
||||
## Простота архитектуры
|
||||
|
||||
- Не вводить interface только потому, что «когда-нибудь может появиться второй backend».
|
||||
- Interface обязателен на реальной внешней границе: renderer facade, protocol profile, Lua VM, server schema adapter, asset repository.
|
||||
- Не создавать base class с одним наследником без конкретного boundary/invariant.
|
||||
- Предпочитать composition inheritance.
|
||||
- Не строить универсальную ECS, service container, message broker или plugin framework до подтверждённых требований OpenWC.
|
||||
- Не переносить архитектуру reference-проекта целиком; извлекать только проверенное решение конкретной проблемы.
|
||||
|
||||
## Работа со сложностью
|
||||
|
||||
Сложность считается оправданной для:
|
||||
|
||||
- binary formats и build-specific layouts;
|
||||
- encrypted/framed network protocol;
|
||||
- incremental world streaming;
|
||||
- animation/material compatibility;
|
||||
- Lua/FrameXML/taint semantics;
|
||||
- cross-core schema mappings;
|
||||
- async jobs, cancellation и deterministic cleanup;
|
||||
- performance-critical batching после profiling.
|
||||
|
||||
Если сложность неизбежна:
|
||||
|
||||
1. Изолировать её в одном модуле.
|
||||
2. Дать точные имена промежуточным этапам.
|
||||
3. Разбить pipeline на небольшие transformations.
|
||||
4. Добавить data-flow/sequence/state diagram.
|
||||
5. Зафиксировать invariants и ownership.
|
||||
6. Создать fixtures для edge cases.
|
||||
7. Объяснить, почему более простое решение недостаточно.
|
||||
|
||||
## Conditions and branching
|
||||
|
||||
- Предпочитать именованные predicates длинным boolean expressions.
|
||||
- Branch по capability/profile должен быть локальным и типизированным.
|
||||
- Не распространять `if core == "azerothcore"` по проекту; использовать adapter/capability.
|
||||
- Не смешивать Blizzlike и Enhanced branches в каждом shader/service: выбирать profile/strategy на boundary.
|
||||
- Pattern/table-driven mapping предпочтительнее сотен одинаковых `if`, если таблица остаётся читаемой и валидируемой.
|
||||
|
||||
## Comments
|
||||
|
||||
Комментарий объясняет `why`, invariant, source behavior или ограничение. Код и имя объясняют `what`.
|
||||
|
||||
Хорошо:
|
||||
|
||||
```gdscript
|
||||
# WoW M2 bone indices are relative to the skin-section bone palette,
|
||||
# not directly to the model-wide bone array.
|
||||
```
|
||||
|
||||
Плохо:
|
||||
|
||||
```gdscript
|
||||
# Increment index by one.
|
||||
index += 1
|
||||
```
|
||||
|
||||
TODO/FIXME обязан содержать target/work-package ID или diagnostic code.
|
||||
|
||||
## Error handling
|
||||
|
||||
- Ошибка содержит operation, subject и actionable context.
|
||||
- Не возвращать пустой Dictionary как неразличимое сочетание `not found`, `invalid` и `unsupported`.
|
||||
- Expected failure моделируется typed result/status; programming invariant может использовать assert в подходящем profile.
|
||||
- Не проглатывать ошибку ради продолжения renderer/editor: degraded behavior должен быть виден diagnostics.
|
||||
- Recovery path проще и предпочтительнее сложной попытки угадать состояние.
|
||||
|
||||
## Performance and KISS
|
||||
|
||||
- Не оптимизировать по предположению.
|
||||
- Сначала baseline/profile, затем smallest measured fix.
|
||||
- Batch/buffer/RenderingServer/native path вводится после доказательства bottleneck.
|
||||
- Оптимизированный path обязан иметь простой contract и reference tests.
|
||||
- Медленный понятный reference implementation полезен как oracle для optimized implementation.
|
||||
- Сложная оптимизация удаляется или упрощается, если она не даёт измеримого результата.
|
||||
|
||||
## Review checklist
|
||||
|
||||
- Можно ли понять назначение сущности по имени?
|
||||
- Есть ли двусмысленные сокращения?
|
||||
- Выполняет ли функция одну операцию?
|
||||
- Можно ли убрать abstraction без потери correctness/boundary?
|
||||
- Есть ли скрытый mutable state или side effect?
|
||||
- Не появился ли второй источник истины?
|
||||
- Измерена ли сложная optimization?
|
||||
- Изолирована и документирована ли неизбежная сложность?
|
||||
- Соответствуют ли API docs и diagrams фактическому коду?
|
||||
|
||||
Если reviewer не может объяснить data flow модуля после чтения имён, public API и module spec, work package не готов к интеграции.
|
||||
@@ -0,0 +1,280 @@
|
||||
# OpenWC Documentation Standard
|
||||
|
||||
## Цель
|
||||
|
||||
Документация является частью реализации. Код без актуального объяснения публичного API, данных, ownership и поведения считается незавершённым, даже если компилируется и проходит тесты.
|
||||
|
||||
Документация должна позволить человеку ответить:
|
||||
|
||||
- зачем существует модуль;
|
||||
- какие данные он получает и откуда;
|
||||
- что он создаёт или изменяет;
|
||||
- кто владеет состоянием и ресурсами;
|
||||
- какие зависимости допустимы;
|
||||
- что происходит по шагам;
|
||||
- как модуль завершается, отменяется и восстанавливается после ошибки;
|
||||
- как проверить fidelity, correctness и performance;
|
||||
- какие части реализованы, запланированы или известны как gap.
|
||||
|
||||
Правила именования, KISS и локализации неизбежной сложности определены в [`CODING_STANDARD.md`](CODING_STANDARD.md). Документация и ясные имена дополняют друг друга: комментарий не должен компенсировать неудачное имя.
|
||||
|
||||
## Уровни документации
|
||||
|
||||
### 1. Inline API documentation
|
||||
|
||||
Находится рядом с кодом и является источником истины для точной сигнатуры:
|
||||
|
||||
- публичные classes/interfaces;
|
||||
- public methods/functions;
|
||||
- signals/events;
|
||||
- exported properties/configuration;
|
||||
- commands, events и read models;
|
||||
- ownership/lifetime/threading requirements;
|
||||
- error/return semantics;
|
||||
- non-obvious invariants.
|
||||
|
||||
### 2. Module specification
|
||||
|
||||
Находится в `docs/modules/<module>.md` и объясняет назначение, архитектуру, data flow, state/lifecycle, extension points и verification. Шаблон: [`modules/TEMPLATE.md`](modules/TEMPLATE.md).
|
||||
|
||||
### 3. Data/schema documentation
|
||||
|
||||
Описывает persisted/wire/content formats: fields, types, units, optionality, version, migration, provenance и compatibility. Нельзя объяснять schema только примером JSON или SQL.
|
||||
|
||||
### 4. ADR
|
||||
|
||||
Фиксирует значимое решение, варианты и последствия. ADR не заменяет актуальное описание модуля: после решения module spec обновляется до текущего состояния.
|
||||
|
||||
### 5. Operational documentation
|
||||
|
||||
Команды build/test/import/bake/deploy/recovery, expected outputs и diagnostics. Команда без условий запуска и интерпретации результата недостаточна.
|
||||
|
||||
## Обязательная module specification
|
||||
|
||||
Новый самостоятельный модуль или существенно выделенная подсистема обязаны иметь документ со следующими разделами:
|
||||
|
||||
1. Metadata/status.
|
||||
2. Purpose и non-goals.
|
||||
3. Context и architecture boundaries.
|
||||
4. Public API.
|
||||
5. Inputs и outputs.
|
||||
6. Data flow diagram.
|
||||
7. State/lifecycle или sequence diagram, если применимо.
|
||||
8. Ownership, threading и resource lifetime.
|
||||
9. Errors, cancellation и recovery.
|
||||
10. Configuration/capabilities/profiles.
|
||||
11. Persistence/cache/migrations.
|
||||
12. Diagnostics и observability.
|
||||
13. Tests, fidelity evidence и performance budgets.
|
||||
14. Extension points.
|
||||
15. Known gaps.
|
||||
16. Source map.
|
||||
|
||||
`Status` принимает только:
|
||||
|
||||
- `Planned`;
|
||||
- `Prototype`;
|
||||
- `Partial`;
|
||||
- `Implemented`;
|
||||
- `Verified`;
|
||||
- `Deprecated`.
|
||||
|
||||
`Implemented` не означает fidelity; `Verified` требует evidence и ссылок на тесты.
|
||||
|
||||
## Обязательные схемы
|
||||
|
||||
### Data flow diagram
|
||||
|
||||
Обязательна для каждого module spec. Показывает реальные имена contracts и направление данных:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Source[ADT bytes] --> Parser[ADTLoader]
|
||||
Parser --> Data[AdtTileData]
|
||||
Data --> Planner[StreamingTargetPlanner]
|
||||
Planner --> Queue[RenderFinalizeCommand]
|
||||
Queue --> Renderer[TerrainRenderer]
|
||||
```
|
||||
|
||||
Диаграмма должна показывать:
|
||||
|
||||
- внешние источники;
|
||||
- входные contracts;
|
||||
- преобразования;
|
||||
- выходные contracts/side effects;
|
||||
- persistence/cache, если участвует;
|
||||
- запрещённое направление, если оно является важной границей.
|
||||
|
||||
### Sequence diagram
|
||||
|
||||
Обязательна, если операция пересекает два и более из следующих boundary:
|
||||
|
||||
- thread/job queue;
|
||||
- network;
|
||||
- gameplay;
|
||||
- renderer/UI/audio;
|
||||
- Editor;
|
||||
- server/database;
|
||||
- filesystem/build pipeline.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Net as WorldSession
|
||||
participant Game as WorldState
|
||||
participant View as EntityPresenter
|
||||
participant Render as WorldRenderFacade
|
||||
Net->>Game: EntityCreated
|
||||
Game-->>View: EntityReadModel change
|
||||
View->>Render: spawn_entity(descriptor)
|
||||
```
|
||||
|
||||
### State diagram
|
||||
|
||||
Обязательна для lifecycle/state machine с тремя и более состояниями, reconnect/retry или rollback:
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Disconnected
|
||||
Disconnected --> Connecting
|
||||
Connecting --> Authenticated
|
||||
Connecting --> Failed
|
||||
Authenticated --> Disconnected
|
||||
Failed --> Connecting: retry
|
||||
```
|
||||
|
||||
### Dependency diagram
|
||||
|
||||
Обязательна при изменении архитектурной границы или модуле с тремя и более downstream consumers. Она показывает compile/runtime dependencies, а не случайное расположение Node в сцене.
|
||||
|
||||
## Public API documentation
|
||||
|
||||
Для каждого public symbol указываются:
|
||||
|
||||
- краткое назначение;
|
||||
- параметры с единицами и coordinate space;
|
||||
- return value или emitted event;
|
||||
- preconditions/postconditions;
|
||||
- ownership и mutability;
|
||||
- допустимый thread;
|
||||
- failure behavior;
|
||||
- profile/capability restrictions;
|
||||
- пример только если контракт остаётся неочевидным.
|
||||
|
||||
### GDScript
|
||||
|
||||
- Использовать `##` documentation comments для class, public methods, signals и exported properties.
|
||||
- Всегда указывать типы аргументов/return, где Godot API позволяет.
|
||||
- `@export` property объясняет units/range/profile и runtime mutability.
|
||||
- Signal документирует момент emission, arguments и ordering guarantees.
|
||||
- Internal helper получает комментарий, если содержит сложный invariant, coordinate conversion, ownership или performance assumption.
|
||||
|
||||
### C++/GDExtension
|
||||
|
||||
- Public headers используют Doxygen-compatible `///`/`/** */`.
|
||||
- Документировать ownership указателей/references/buffers, thread safety и error transport.
|
||||
- Binary structs указывают source format/build, packing/endianness и validation.
|
||||
- Native/Godot boundary описывает allocation и lifetime Variant/PackedArray/Resource.
|
||||
- Implementation comments объясняют `why` и invariant, а не пересказывают строку кода.
|
||||
|
||||
### Shaders/materials
|
||||
|
||||
- Uniform/global parameter: coordinate/color space, range, units и producer.
|
||||
- Material profile: supported WoW shader/blend modes и approximations.
|
||||
- Expensive branch/texture dependency имеет cost/fallback note.
|
||||
- Blizzlike и Enhanced behavior документируются отдельно.
|
||||
|
||||
### Network codecs
|
||||
|
||||
- Opcode/direction/session phase.
|
||||
- Exact build/profile.
|
||||
- Field order/type/endianness/conditional presence.
|
||||
- Domain events на выходе.
|
||||
- Malformed/unknown handling.
|
||||
- Fixture/reference source без sensitive payload.
|
||||
|
||||
### Content/server schemas
|
||||
|
||||
- Canonical field и mapping на каждый core adapter.
|
||||
- Unit, default, null/optional semantics и flags.
|
||||
- Numeric ID allocation и references.
|
||||
- Migration/rollback и unsupported degradation.
|
||||
|
||||
## Документация потока данных
|
||||
|
||||
Inputs/outputs оформляются таблицей:
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | `EntityMoved` | `WorldSession` | `WorldState` | Immutable event | Game-thread apply |
|
||||
| Output | `EntityReadModel` | `WorldState` | Presenter/UI | Snapshot | Until next revision |
|
||||
|
||||
Side effects перечисляются отдельно:
|
||||
|
||||
- scene tree mutation;
|
||||
- RenderingServer RID creation/free;
|
||||
- filesystem/cache write;
|
||||
- network send;
|
||||
- DB query/apply;
|
||||
- settings/SavedVariables mutation.
|
||||
|
||||
Скрытый side effect является дефектом документации и API.
|
||||
|
||||
## Статус реализации
|
||||
|
||||
Документ не должен выдавать roadmap за существующий код. Каждый важный capability помечается:
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
|
||||
После изменения кода статус и `Last verified` обновляются в том же work package. Если документацию нельзя подтвердить, ставится `Unknown`/`Planned`, а не предположение.
|
||||
|
||||
## Documentation change rules
|
||||
|
||||
Документация обновляется в том же work package, если изменились:
|
||||
|
||||
- public API/signal/event;
|
||||
- data flow или ownership;
|
||||
- state/lifecycle;
|
||||
- thread boundary;
|
||||
- format/schema/cache version;
|
||||
- configuration/capability;
|
||||
- diagnostics/error/recovery;
|
||||
- fidelity или performance behavior;
|
||||
- extension point.
|
||||
|
||||
Переименование symbol/path требует обновить source map, diagrams и ссылки. Stale documentation считается regression.
|
||||
|
||||
## Code comments
|
||||
|
||||
Хороший комментарий объясняет:
|
||||
|
||||
- почему решение отличается от очевидного;
|
||||
- форматное или protocol ограничение;
|
||||
- ownership/thread invariant;
|
||||
- причину batching/budget;
|
||||
- источник fidelity behavior;
|
||||
- временный compatibility path и условие удаления.
|
||||
|
||||
Плохой комментарий:
|
||||
|
||||
- пересказывает код;
|
||||
- обещает будущую работу без target/work package ID;
|
||||
- содержит устаревшую сигнатуру;
|
||||
- маскирует magic number без source/unit;
|
||||
- утверждает `Blizzlike` без evidence.
|
||||
|
||||
TODO/FIXME должен содержать target/work package или diagnostic code.
|
||||
|
||||
## Review gate
|
||||
|
||||
Reviewer/integrator проверяет:
|
||||
|
||||
- public symbols имеют inline docs;
|
||||
- module spec существует или обновлён;
|
||||
- input/output и side effects перечислены;
|
||||
- diagrams соответствуют реальным contracts;
|
||||
- state/error/recovery paths показаны;
|
||||
- tests/evidence и known gaps честны;
|
||||
- документы не дублируют противоречивые источники истины.
|
||||
|
||||
Если хотя бы один обязательный пункт отсутствует, handoff остаётся `PARTIAL`, а target не может получить `DONE`.
|
||||
@@ -0,0 +1,120 @@
|
||||
# Godot Best Practices for OpenWC
|
||||
|
||||
## Статус
|
||||
|
||||
Это нормативные правила применения Godot в OpenWC. Они дополняют общую архитектуру и основаны на официальных Godot practices с учётом большого streaming world, native parsers и встроенного Editor tooling.
|
||||
|
||||
## Nodes только на границах
|
||||
|
||||
- Domain, packet codecs, validators, coordinate math, build graph и canonical content не должны быть Nodes.
|
||||
- Node/Control используются для lifecycle, scene composition, input, rendering/audio presentation и Editor UI.
|
||||
- Большой объём однородных данных хранится в typed objects/packed arrays/resources, а не в тысячах служебных Nodes.
|
||||
- Scene не ищет глобально dependency по имени; parent composition передаёт contract/reference явно.
|
||||
|
||||
## Scene organization
|
||||
|
||||
- Каждая scene имеет одну ответственность и может запускаться в isolation с test doubles.
|
||||
- Parent-child означает ownership: удаление parent логично удаляет child. Иначе системы являются siblings/services.
|
||||
- Scene наружу публикует небольшой API/signals, а внутренние NodePaths не становятся публичным контрактом.
|
||||
- Runtime, render sandbox, preview и capture scenes собираются из общих presenters/services.
|
||||
- Generated world preview не сохраняется как authoring source.
|
||||
|
||||
## Dependency injection и signals
|
||||
|
||||
- Dependencies передаются composition root, initializer или exported interface reference.
|
||||
- Signals используются для notification; commands/intents вызываются через явные методы/bus contracts.
|
||||
- Signal connections снимаются симметрично при unload/plugin disable.
|
||||
- Не использовать SceneTree groups как скрытый service locator для core architecture.
|
||||
|
||||
## Autoload policy
|
||||
|
||||
Autoload разрешён только для действительно process-wide lifecycle:
|
||||
|
||||
- composition/bootstrap coordinator;
|
||||
- глобальная immutable build/version information;
|
||||
- crash/diagnostic sink, если ему нужен process lifetime.
|
||||
|
||||
Gameplay state, network session, renderer world, editor session, caches конкретного проекта и audio emitters не должны становиться неявными глобальными managers. Они создаются/освобождаются владельцем режима.
|
||||
|
||||
## Resources and data
|
||||
|
||||
- Resource подходит для immutable/imported descriptors и editor-inspectable configuration.
|
||||
- Runtime mutable authoritative state не хранится в shared Resource, который может быть случайно разделён сценами.
|
||||
- Resource schema имеет version и migration, если сохраняется пользователем.
|
||||
- Original extracted corpus и bulk caches не следует заставлять Godot массово импортировать без необходимости; применять `.gdignore`, custom import staging или native repository.
|
||||
- Importer проверяет malformed input и записывает dependency/profile metadata.
|
||||
|
||||
## Background work
|
||||
|
||||
- Blocking `load()` не используется в frame-critical streaming path; применяются threaded load/jobs.
|
||||
- Worker выполняет file I/O, parsing, decompression, grouping и pure data generation.
|
||||
- SceneTree mutation, Node attach и большинство GPU/RID finalization выполняются main thread budget.
|
||||
- Один Resource не модифицируется несколькими workers.
|
||||
- GDScript container resize/mutation между threads защищается mutex или заменяется message passing.
|
||||
- Все worker tasks ожидаются/очищаются; session cancellation не оставляет orphan work.
|
||||
|
||||
## Rendering performance
|
||||
|
||||
- Сначала измерять SceneTree/CPU/GPU bottleneck, затем переходить к RenderingServer.
|
||||
- Repeated static M2 группировать spatial MultiMesh cells; один MultiMesh на весь мир ухудшает culling.
|
||||
- Large transform buffers готовить линейно/в native code и отправлять батчем.
|
||||
- Visibility ranges/HLOD, occlusion и automatic mesh LOD использовать совместно по профилю.
|
||||
- Unique materials/textures минимизировать; descriptor/resource counts являются budget metric.
|
||||
- RenderingServer RIDs имеют явного владельца и освобождаются в deterministic shutdown test.
|
||||
- Shader/material profiles разделяют Blizzlike и Enhanced; runtime не компилирует тяжёлые варианты при пересечении ADT boundary.
|
||||
|
||||
## EditorPlugin lifecycle
|
||||
|
||||
- Любая регистрация в `_enter_tree()` имеет соответствующее удаление в `_exit_tree()`.
|
||||
- Docks, inspectors, importers, gizmos, debugger plugins, signals и jobs снимаются при disable/reload.
|
||||
- Editor UI не пишет файлы/SQL напрямую — только создаёт application commands.
|
||||
- Mutation интегрирована с EditorUndoRedoManager или общей command history.
|
||||
- Preview nodes считаются disposable; stable content IDs не равны instance IDs.
|
||||
- Долгая операция имеет progress, cancellation и staging output.
|
||||
|
||||
## Editor imports and inspectors
|
||||
|
||||
- Custom import plugin применяется для формата, которому действительно нужен first-class Godot Resource lifecycle.
|
||||
- Один extension/type имеет стабильный importer name и version.
|
||||
- Platform/profile variants фиксируются явно.
|
||||
- Custom inspectors редактируют canonical data через commands и показывают field-level validation.
|
||||
- Gizmos не владеют сущностью и создают одну undoable logical operation на drag/stroke.
|
||||
|
||||
## UI
|
||||
|
||||
- Control tree не читает network packets и не ищет world nodes.
|
||||
- View models immutable на frame/update boundary.
|
||||
- Layout использует anchors/containers/theme tokens, а не hardcoded pixel positions вне fidelity backend.
|
||||
- FrameXML compatibility layout изолирован от modern Godot-native screens.
|
||||
- Input consumption и focus/modal context тестируются сценами.
|
||||
|
||||
## Profiling and diagnostics
|
||||
|
||||
- Использовать Godot profiler для script/physics и Visual Profiler для render CPU/GPU.
|
||||
- Headless/CLI runs сохраняют JSON metrics; GPU runs фиксируют backend/device/driver/resolution.
|
||||
- Performance changes сравниваются cold/warm и p50/p95/p99, а не одним FPS.
|
||||
- GPU validation, memory tracking и RenderDoc workflow документируются для shader/backend defects.
|
||||
- Custom monitors публикуют streaming queues, cache hits, entities, materials, RIDs и job latency.
|
||||
|
||||
## Tests
|
||||
|
||||
- Pure logic тестируется без SceneTree.
|
||||
- Scene runner используется только для lifecycle/input/signals/layout/presentation.
|
||||
- EditorPlugin имеет enable/disable/reload и undo/save/reload tests.
|
||||
- Headless является обязательным CI path; visual tests запускаются отдельно на GPU runner.
|
||||
- GdUnit4 рассматривается как кандидат для GDScript/scene tests; native tests остаются отдельным target.
|
||||
|
||||
## Official references
|
||||
|
||||
- [Godot Best practices](https://docs.godotengine.org/en/stable/tutorials/best_practices/index.html)
|
||||
- [Scene organization](https://docs.godotengine.org/en/stable/tutorials/best_practices/scene_organization.html)
|
||||
- [Autoloads versus regular nodes](https://docs.godotengine.org/en/stable/tutorials/best_practices/autoloads_versus_regular_nodes.html)
|
||||
- [Background loading](https://docs.godotengine.org/en/stable/tutorials/io/background_loading.html)
|
||||
- [Thread-safe APIs](https://docs.godotengine.org/en/stable/tutorials/performance/thread_safe_apis.html)
|
||||
- [Optimization using MultiMeshes](https://docs.godotengine.org/en/stable/tutorials/performance/using_multimesh.html)
|
||||
- [RenderingServer](https://docs.godotengine.org/en/stable/classes/class_renderingserver.html)
|
||||
- [Import plugins](https://docs.godotengine.org/en/stable/tutorials/plugins/editor/import_plugins.html)
|
||||
- [Inspector plugins](https://docs.godotengine.org/en/stable/tutorials/plugins/editor/inspector_plugins.html)
|
||||
- [3D gizmo plugins](https://docs.godotengine.org/en/stable/tutorials/plugins/editor/3d_gizmos.html)
|
||||
- [Command line tutorial](https://docs.godotengine.org/en/stable/tutorials/editor/command_line_tutorial.html)
|
||||
- [Debugging tools](https://docs.godotengine.org/en/stable/tutorials/scripting/debug/overview_of_debugging_tools.html)
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
Исполняемая последовательность текущих целей находится в [`../targets/README.md`](../targets/README.md). Все агенты обязаны следовать корневому [`../AGENTS.md`](../AGENTS.md).
|
||||
|
||||
Полная инженерная карта проекта находится в [`../targets/DEVELOPMENT_ROADMAP.md`](../targets/DEVELOPMENT_ROADMAP.md); она раскрывает renderer, protocol, gameplay, UI/Lua/audio, Editor tooling, server emulators и quality/release workstreams.
|
||||
|
||||
## Статус документов
|
||||
|
||||
Документы описывают целевую архитектуру. Реализованное состояние рендера зафиксировано отдельно в [`../RENDER.md`](../RENDER.md). Если описание целевого решения расходится с кодом, это считается известным архитектурным долгом, а не разрешением обходить границы слоёв.
|
||||
@@ -23,6 +25,11 @@
|
||||
| [ROADMAP.md](ROADMAP.md) | Последовательность вертикальных срезов и критерии готовности |
|
||||
| [REFERENCES.md](REFERENCES.md) | Использованные локальные reference-проекты и правила работы с ними |
|
||||
| [TOOLING_CATALOG.md](TOOLING_CATALOG.md) | Реестр готовых библиотек, редакторов, test oracles и кандидатов на интеграцию |
|
||||
| [GODOT_BEST_PRACTICES.md](GODOT_BEST_PRACTICES.md) | Нормативные правила scenes, data, jobs, rendering, EditorPlugin, profiling и tests |
|
||||
| [TEAM_WORKFLOW.md](TEAM_WORKFLOW.md) | Ветки/worktree, task claims, ownership, handoff, integration и conflict protocol |
|
||||
| [DOCUMENTATION_STANDARD.md](DOCUMENTATION_STANDARD.md) | Обязательные API docs, module specs, data-flow/state/sequence diagrams и documentation gate |
|
||||
| [modules/README.md](modules/README.md) | Реестр фактических модулей и шаблон их спецификаций |
|
||||
| [CODING_STANDARD.md](CODING_STANDARD.md) | Явные длинные имена, KISS, простота функций/data flow и правила оправданной сложности |
|
||||
|
||||
## Обязательный workflow изменения функции
|
||||
|
||||
|
||||
@@ -67,6 +67,22 @@ Reference-код используется для исследования фор
|
||||
|
||||
Подробные статусы и evaluation cards ведутся в [`TOOLING_CATALOG.md`](TOOLING_CATALOG.md).
|
||||
|
||||
### Godot, GdUnit4 и server tooling
|
||||
|
||||
- [Godot best practices](https://docs.godotengine.org/en/stable/tutorials/best_practices/index.html) — основной источник engine-specific правил; принятые решения собраны в [`GODOT_BEST_PRACTICES.md`](GODOT_BEST_PRACTICES.md).
|
||||
- [`godot-gdunit-labs/gdUnit4`](https://github.com/godot-gdunit-labs/gdUnit4) — кандидат для GDScript/scene/Editor/CI тестов.
|
||||
- [`azerothcore/Keira3`](https://github.com/azerothcore/Keira3) — reference существующего редактора world DB и SQL generation.
|
||||
- [AzerothCore database and module documentation](https://www.azerothcore.org/wiki/documentation-index) — primary reference для AzerothCore adapter/deployment workflow.
|
||||
- [WowBench](https://sourceforge.net/projects/wowbench/) — исторический offline WoW XML/Lua API emulator и источник addon test ideas.
|
||||
|
||||
### Warcraft format ecosystem
|
||||
|
||||
- [`wowemulation-dev/warcraft-rs`](https://github.com/wowemulation-dev/warcraft-rs) — современный Rust CLI/library set для MPQ/DBC/BLP/ADT/WDT/WDL/M2/WMO и независимый кандидат на format oracle.
|
||||
- [`wowdev/WoWDBDefs`](https://github.com/wowdev/WoWDBDefs) — versioned definitions client database tables; полезны для schema generation и coverage.
|
||||
- [`gtker/wow_dbc`](https://github.com/gtker/wow_dbc) — независимый 1.12/2.4.3/3.3.5 DBC reader/writer и converter reference.
|
||||
|
||||
Используем для differential testing текущих native parsers и поиска coverage gaps. Новый Rust dependency не принимается только из-за более широкого списка поддерживаемых форматов: сначала требуются build-specific fixtures и измеренное преимущество.
|
||||
|
||||
## Правила исследования
|
||||
|
||||
1. Для protocol semantics приоритет имеют wire fixtures и соответствующая версия server core.
|
||||
|
||||
+121
-1
@@ -24,6 +24,46 @@
|
||||
- `<checkpoint>__cold_process.png` — первый визит в текущем процессе;
|
||||
- `<checkpoint>__warm_revisit.png` — повторный визит в том же процессе после обхода контрольных точек.
|
||||
|
||||
## Парное визуальное сравнение
|
||||
|
||||
Одобренные снимки оригинального клиента хранятся вне Git. Если каталог с ними доступен локально, единый запуск может сразу сравнить PNG с одинаковыми именами:
|
||||
|
||||
```powershell
|
||||
.\tools\run_render_baseline.ps1 `
|
||||
-ReferenceCheckpointDirectory 'D:\private-fixtures\wow-3.3.5a-checkpoints'
|
||||
```
|
||||
|
||||
Результат записывается в `user://render_baseline/visual_comparison.json`. Другой путь задаётся через `-VisualComparisonReport`. `-DryRun` несовместим с визуальным сравнением, потому что не создаёт PNG. Runner сохраняет обычный performance capture в нормативном `1280×900`, затем отдельно снимает visual candidates в `2560×1440`; разрешение можно переопределить через `-VisualComparisonWidth` и `-VisualComparisonHeight`, не меняя performance baseline.
|
||||
|
||||
Отдельный запуск сравнения:
|
||||
|
||||
```powershell
|
||||
godot --headless --path . `
|
||||
--script res://src/tools/compare_render_checkpoints.gd -- `
|
||||
--reference 'D:\private-fixtures\wow-3.3.5a-checkpoints' `
|
||||
--candidate "$env:APPDATA\Godot\app_userdata\OpenWC\render_baseline" `
|
||||
--output user://render_baseline/visual_comparison.json
|
||||
```
|
||||
|
||||
Reference-каталог принимает JPG/JPEG/PNG оригинального клиента с именем checkpoint, например `goldshire_dense_m2.jpg`. Диагностические кадры с префиксом `diagnostic_` игнорируются. Один reference автоматически сопоставляется с `<checkpoint>__cold_process.png` и `<checkpoint>__warm_revisit.png` OpenWC.
|
||||
|
||||
Сравнение переводит sRGB в linear color space, вычисляет взвешенную ошибку яркости/цвета для каждого пикселя, среднюю ошибку кадра и долю пикселей выше локального порога. Нормативные defaults находятся в `comparison_budgets` manifest: mean error `0.015`, changed-pixel ratio `0.01`, pixel error threshold `0.04`. Любое превышение, несовпадение размеров, отсутствие пары или пустой reference-каталог возвращает ненулевой exit code.
|
||||
|
||||
Автоматический pass не является доказательством parity: animation phase, weather, camera alignment и intentional gaps требуют human approval. Для проверки алгоритма без proprietary данных используется:
|
||||
|
||||
```powershell
|
||||
godot --headless --path . --script res://src/tools/compare_render_checkpoints.gd -- --self-test
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
R[Private approved reference PNGs] --> C[Checkpoint comparator]
|
||||
N[New baseline PNGs] --> C
|
||||
M[Manifest tolerance defaults] --> C
|
||||
C --> J[JSON metrics and pass/fail]
|
||||
J --> H[Human fidelity approval]
|
||||
```
|
||||
|
||||
`cold_process` не означает очищенный Windows filesystem cache. Поле `cache_state` и полный inventory обязаны интерпретироваться вместе с результатом. Удаление или принудительная пересборка cache не входит в baseline-команду. Для отдельного запуска после контролируемой очистки безопасного локального cache следует передать осмысленную метку, например `-CacheState rebuilt-clean`; proprietary source assets команда не изменяет.
|
||||
|
||||
## Контрольные точки и детерминизм
|
||||
@@ -38,7 +78,7 @@
|
||||
- синтетический native-animation probe `GryphonRoost01.m2`;
|
||||
- тот же Elwynn overview в 19:00 для sky transition.
|
||||
|
||||
Зафиксированы camera/target/player position, viewport, FOV, quality preset и время мира. Для temporal shader/animation кадров SHA-256 служит идентификатором артефакта, но не pass-критерием: будущая автоматическая визуальная проверка должна использовать perceptual diff и согласованный tolerance. Синтетический animation probe существует только внутри capture tool и не меняет runtime streaming scene.
|
||||
Зафиксированы camera/target/player position, viewport, FOV, quality preset и время мира. Для temporal shader/animation кадров SHA-256 служит идентификатором артефакта, но не самостоятельным pass-критерием: автоматическая визуальная проверка использует perceptual diff и согласованный tolerance. Синтетический animation probe существует только внутри capture tool и не меняет runtime streaming scene.
|
||||
|
||||
## Метрики и budget
|
||||
|
||||
@@ -74,3 +114,83 @@ Baseline пока не имеет approved парных кадров ориги
|
||||
- возможная смена D3D12 на Vulkan при ошибке descriptor heap — фактический backend всегда берётся из `report.json`.
|
||||
|
||||
Парное сравнение с клиентом 3.3.5a должно использовать те же map position, local time и weather. Каждый gap регистрируется как terrain/material, placement, animation, lighting, liquid или culling; baseline сам по себе gap не закрывает.
|
||||
|
||||
Локальная сессия build 12340 от 2026-07-11 откалибровала три непригодные исходные позиции: dense-M2 camera была полностью закрыта кронами, large-WMO camera находилась у дымохода кузни, liquid camera попадала под terrain оригинального клиента. Manifest теперь содержит проверенные replacement camera positions. Пять approved локальных reference JPG хранятся вне Git; synthetic animated probe и dusk checkpoint всё ещё требуют отдельной процедуры.
|
||||
|
||||
Первый paired run после калибровки создал десять сравнений (пять reference × cold/warm), без missing pairs. Все пары ожидаемо превысили строгий tolerance: mean perceptual error `0.0707..0.1746`, changed-pixel ratio `0.5504..0.8187`. Human inspection показал, что это пока не чистая material/lighting ошибка: при тех же WoW-derived координатах OpenWC terrain-overview camera находится под terrain/placements, WMO camera — внутри таверны, liquid camera — внутри скал; ADT и dense-M2 композиции также существенно смещены. До исправления coordinate/placement mismatch эти значения являются gap evidence, а не основанием расширять tolerance.
|
||||
|
||||
Capture tool строит camera basis явно из target и world-up. Это исключает неоднозначный roll `look_at` при автоматической съёмке. `ViewportTexture.get_image()` сохраняется без дополнительного vertical flip для Godot 4.6.1.
|
||||
|
||||
## Coordinate calibration
|
||||
|
||||
Пять принятых build 12340 viewpoints записаны в manifest как `reference_wow_camera`. Они содержат только числовые world coordinates и не включают proprietary данные. Headless probe:
|
||||
|
||||
```powershell
|
||||
godot --headless --path . --script res://src/tools/verify_render_coordinate_calibration.gd
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
O[Observed build 12340 WoW XYZ] --> W[WoW to Godot formula]
|
||||
W --> G[Manifest Godot camera XYZ]
|
||||
G --> R[Godot to WoW round-trip]
|
||||
R --> E[Maximum numeric error]
|
||||
```
|
||||
|
||||
На пяти точках maximum mapping/round-trip error равен `0.000015`. Это исключает текущую формулу `gx = center - wy`, `gy = wz`, `gz = center - wx` как источник крупного paired mismatch. Результат не доказывает renderer parity: следующая диагностика должна отдельно проверить terrain height, placement transforms и фактическое camera direction/FOV. Production `CoordinateMapper` остаётся задачей M01; M00 probe не создаёт второй публичный coordinate contract.
|
||||
|
||||
## Terrain height diagnostic
|
||||
|
||||
Rendered terrain проверяется без нового runtime API: offline probe использует уже загруженный tile mesh, строит `TriangleMesh` и выполняет вертикальный ray в tile-local space.
|
||||
|
||||
```powershell
|
||||
godot --headless --path . --script res://src/tools/probe_render_terrain_height.gd -- --wait 2
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
C[Calibrated camera XZ] --> S[Streaming tile state]
|
||||
S --> M[Active terrain mesh]
|
||||
M --> T[Tile-local TriangleMesh ray]
|
||||
T --> H[Terrain height and camera clearance]
|
||||
```
|
||||
|
||||
Измеренный clearance: terrain overview `89.044`, ADT boundary `44.788`, dense M2 `90.178`, large WMO `12.034`, waterfall примерно `76.128` Godot units. Следовательно, все пять камер находятся над rendered terrain; visual obstruction принадлежит placements/WMO/composition, а не terrain height.
|
||||
|
||||
Waterfall XZ сначала давал `no_intersection`, хотя tile `30_49` был available, полностью загружен, имел `control_splat_cache` quality mesh и LOD0 mesh, а probe находился внутри mesh AABB. Ray со смещением `2.0` units пересёк тот же mesh на высоте `113.872`; точная XZ попала на triangle seam/edge numerical miss. Probe теперь сообщает `sampled_nearby`, distance и source tile вместо ложного streaming ownership gap. `--require-all` остаётся строгим режимом для действительно неснятых точек.
|
||||
|
||||
## Camera occluder diagnostic
|
||||
|
||||
Scene-tree placement composition проверяется transformed AABB без изменения renderer:
|
||||
|
||||
```powershell
|
||||
godot --headless --path . --script res://src/tools/probe_render_camera_occluders.gd -- --wait 3
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
C[Calibrated camera] --> A[Published Mesh/MultiMesh AABBs]
|
||||
T[Manifest target] --> S[Camera-to-target segment]
|
||||
A --> P[Camera containment test]
|
||||
A --> I[Segment intersection test]
|
||||
S --> I
|
||||
P --> J[JSON occluder report]
|
||||
I --> J
|
||||
```
|
||||
|
||||
Ни одна из пяти камер не находится внутри опубликованной scene-tree geometry. Terrain-overview segment пересекает четыре Stormwind WMO groups, large-WMO segment — три Goldshire Inn groups, waterfall segment — liquid surface; ADT boundary и dense-M2 segments не пересекают placement AABB. Поэтому прежнее визуальное впечатление «камера внутри WMO/placements» не подтверждается. Основной paired gap сейчас — неточно воспроизведённые manual look direction/target/FOV reference-кадров: например, автоматический Goldshire target направляет луч через фасад внутрь WMO, тогда как reference был вручную скадрирован на весь фасад. Probe охватывает только scene-tree MeshInstance/MultiMesh; RID-only instances не имеют доступного semantic path и явно исключены из coverage.
|
||||
|
||||
## Empirical FOV sweep
|
||||
|
||||
В build 12340 `GetCVar("cameraFoV")` возвращает `nil`, а `/console cameraFoV` и `ConsoleExec("cameraFoV")` не выдают значения. Поэтому capture tool поддерживает additive `--camera-fov`, а comparator — `--only`, позволяющий ограничить sweep одним reference checkpoint.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
F[Candidate vertical FOV] --> C[Dedicated checkpoint camera]
|
||||
C --> P[Filtered checkpoint PNGs]
|
||||
R[One original-client reference] --> D[Perceptual comparator --only]
|
||||
P --> D
|
||||
D --> M[Ranked metrics]
|
||||
```
|
||||
|
||||
Первый sweep обнаружил capture defect: разные FOV иногда создавали одинаковые hashes, потому что scene camera могла перехватить viewport после входа в tree. Capture теперь вызывает `camera.make_current()` после добавления world и перед каждым checkpoint. После исправления ADT-boundary ranking стал: `26° → 0.079588`, `38° → 0.079633`, `50° → 0.084353`, `62° → 0.088360`, `86° → 0.097993` mean error. Plateau `26–38°` и несовпавший changed-pixel optimum показывают доминирование manual look direction/framing; эти данные не обосновывают изменение нормативного manifest FOV `62°`. Для настоящей калибровки reference capture должен сохранять воспроизводимые yaw/pitch/zoom или независимый projection fixture.
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
> Этот документ объясняет стратегию на высоком уровне. Исполняемый порядок, текущий статус, checklist и доказательства завершения ведутся только в [`../targets/README.md`](../targets/README.md). При расхождении `targets/` имеет приоритет.
|
||||
|
||||
Полный roadmap с точки зрения разработки, contracts, subsystem dependencies и gates находится в [`../targets/DEVELOPMENT_ROADMAP.md`](../targets/DEVELOPMENT_ROADMAP.md).
|
||||
|
||||
## Стратегия
|
||||
|
||||
Работа идёт вертикальными срезами. Каждый срез включает data, domain, adapter, presentation, editor support, diagnostics и tests. Горизонтальная реализация всех opcodes или всех editor-панелей без пользовательского результата не считается milestone.
|
||||
|
||||
@@ -0,0 +1,344 @@
|
||||
# Командная разработка OpenWC
|
||||
|
||||
## Цель
|
||||
|
||||
Этот workflow позволяет нескольким людям и Codex-агентам работать параллельно, не перезаписывая изменения друг друга и не разрушая архитектурные границы. Единица параллельной работы — не milestone целиком, а ограниченный `work package`.
|
||||
|
||||
## Основные правила
|
||||
|
||||
1. Каждый агент работает в отдельном clone или Git worktree и собственной ветке.
|
||||
2. Никто не разрабатывает напрямую в `master`.
|
||||
3. До изменений задача получает ID, владельца, claim и список затрагиваемых путей.
|
||||
4. Один work package имеет только одного активного владельца.
|
||||
5. Общие contracts согласуются до параллельной реализации producers/consumers.
|
||||
6. Статус milestone и `OPENWC_TARGET_DONE` изменяет только интегратор.
|
||||
7. Merge выполняется только после handoff, локальных проверок и синхронизации с актуальным `master`.
|
||||
|
||||
## Идентификаторы
|
||||
|
||||
### Agent ID
|
||||
|
||||
Каждый человек/агент выбирает стабильный ID на весь work package:
|
||||
|
||||
```text
|
||||
<human>-<machine>-codex
|
||||
```
|
||||
|
||||
Примеры:
|
||||
|
||||
```text
|
||||
sindo-main-codex
|
||||
alex-laptop-codex
|
||||
```
|
||||
|
||||
ID записывается в claim, ветке, PR/MR и handoff. Нельзя использовать просто `codex`, потому что таких агентов несколько.
|
||||
|
||||
### Work Package ID
|
||||
|
||||
```text
|
||||
<TARGET>-<PROGRAM>-<SHORT-NAME>-<NNN>
|
||||
```
|
||||
|
||||
Примеры:
|
||||
|
||||
```text
|
||||
M01-FND-COORDS-001
|
||||
M03-RND-SCHEDULER-001
|
||||
M08-NET-SRP-001
|
||||
M12-UIA-LUA-SPIKE-001
|
||||
```
|
||||
|
||||
Program codes определены в [`../targets/DEVELOPMENT_ROADMAP.md`](../targets/DEVELOPMENT_ROADMAP.md).
|
||||
|
||||
## Claim protocol
|
||||
|
||||
Предпочтительный канал координации — issue + ранний draft PR/MR на общем Git forge. Если forge недоступен, используется отдельный файл в `coordination/claims/`, который сначала публикуется в общей ветке.
|
||||
|
||||
Claim marker:
|
||||
|
||||
```text
|
||||
<!-- OPENWC_CLAIM:M01-FND-COORDS-001:sindo-main-codex:2026-07-13 -->
|
||||
```
|
||||
|
||||
Последнее поле — дата истечения claim в UTC. Рекомендуемый срок — 48 часов; владелец продлевает его коротким status update. Истёкший claim нельзя забирать молча: сначала оставить сообщение владельцу и интегратору.
|
||||
|
||||
Claim содержит:
|
||||
|
||||
- target и work package;
|
||||
- owner/agent ID;
|
||||
- outcome и non-goals;
|
||||
- exclusive paths;
|
||||
- shared/hotspot paths;
|
||||
- contracts/schema/format changes;
|
||||
- dependencies и blockers;
|
||||
- verification commands;
|
||||
- expected fidelity evidence.
|
||||
- documentation deliverables: inline API, module spec, diagrams и source map.
|
||||
|
||||
## Ветки и worktrees
|
||||
|
||||
Имя ветки:
|
||||
|
||||
```text
|
||||
work/<agent-id>/<target>-<short-name>
|
||||
```
|
||||
|
||||
Пример:
|
||||
|
||||
```text
|
||||
work/sindo-main-codex/m01-coordinate-mapper
|
||||
```
|
||||
|
||||
Начало работы:
|
||||
|
||||
```powershell
|
||||
git fetch origin
|
||||
git worktree add ..\open-wc-m01 -b work/sindo-main-codex/m01-coordinate-mapper origin/master
|
||||
```
|
||||
|
||||
Правила:
|
||||
|
||||
- один worktree принадлежит одному агенту;
|
||||
- не запускать двух агентов в одном dirty worktree;
|
||||
- не менять и не удалять чужой worktree;
|
||||
- generated caches и proprietary data не переносятся commit/cherry-pick;
|
||||
- перед handoff ветка синхронизируется с актуальным `origin/master`.
|
||||
|
||||
## Разделение задач
|
||||
|
||||
Хороший work package:
|
||||
|
||||
- даёт один наблюдаемый результат;
|
||||
- принадлежит преимущественно одной подсистеме;
|
||||
- имеет явные входы/выходы;
|
||||
- меняет не более одного публичного contract одновременно;
|
||||
- имеет собственные tests/diagnostics;
|
||||
- может быть review и merged независимо либо после одной явно указанной dependency.
|
||||
|
||||
Плохое разделение:
|
||||
|
||||
- «сделать сеть»;
|
||||
- один агент меняет protocol и UI напрямую;
|
||||
- два агента одновременно рефакторят один monolithic file;
|
||||
- задача зависит от незаписанного устного соглашения;
|
||||
- work package оставляет временный второй источник истины.
|
||||
|
||||
## Parallelization patterns
|
||||
|
||||
### Contract first
|
||||
|
||||
Для cross-layer feature сначала отдельным маленьким work package фиксируются:
|
||||
|
||||
- domain types;
|
||||
- commands/events/read models;
|
||||
- version/capabilities;
|
||||
- failure states;
|
||||
- contract tests.
|
||||
|
||||
После merge независимо работают producer и consumers.
|
||||
|
||||
Пример:
|
||||
|
||||
```text
|
||||
WP-A: MovementSnapshot contract
|
||||
├─ WP-B: network movement decoder
|
||||
├─ WP-C: gameplay reconciliation
|
||||
└─ WP-D: character/camera presentation
|
||||
```
|
||||
|
||||
### Fixtures in parallel
|
||||
|
||||
Пока один агент реализует pure logic, второй может готовить:
|
||||
|
||||
- synthetic fixtures;
|
||||
- original-client comparison procedure;
|
||||
- server-core matrix;
|
||||
- visual checkpoints;
|
||||
- documentation/reference analysis.
|
||||
|
||||
Fixture agent не изменяет production contract без согласования.
|
||||
|
||||
### Adapter split
|
||||
|
||||
После merge canonical interface допустимо параллельно реализовать TrinityCore и AzerothCore adapters, если у каждого разные файлы и общая contract suite.
|
||||
|
||||
### Hotspot extraction first
|
||||
|
||||
Если работа требует одного большого файла, сначала один владелец извлекает стабильный seam. Параллельная работа начинается только после merge seam, а не внутри разных участков монолита.
|
||||
|
||||
## Hotspot-файлы
|
||||
|
||||
Следующие пути требуют явного указания в claim и согласования с интегратором:
|
||||
|
||||
- `project.godot`;
|
||||
- `AGENTS.md`, `docs/README.md`, `targets/README.md`;
|
||||
- текущий `targets/[0-9][0-9]-*.md`;
|
||||
- `RENDER.md`;
|
||||
- `src/scenes/streaming/streaming_world_loader.gd` до декомпозиции;
|
||||
- native registration/CMake/GDExtension manifests;
|
||||
- editor `plugin.cfg` и root plugin lifecycle;
|
||||
- shared schema/version/format constants;
|
||||
- dependency lockfiles и submodules.
|
||||
|
||||
Два активных work packages не должны редактировать один hotspot. При необходимости интегратор выделяет serial integration window.
|
||||
|
||||
## Изменение contracts и форматов
|
||||
|
||||
Contract/schema change обязан иметь:
|
||||
|
||||
- owner и потребителей;
|
||||
- backward compatibility или migration;
|
||||
- old/new fixtures;
|
||||
- version bump policy;
|
||||
- rollout order;
|
||||
- ADR для существенного решения.
|
||||
|
||||
Merge order:
|
||||
|
||||
1. contract + compatibility layer;
|
||||
2. producers;
|
||||
3. consumers;
|
||||
4. removal deprecated path отдельным work package.
|
||||
|
||||
Нельзя одновременно удалить старый API и ожидать, что незамердженная ветка другого агента уже использует новый.
|
||||
|
||||
## Commits
|
||||
|
||||
Формат заголовка:
|
||||
|
||||
```text
|
||||
<program>(<target>): <imperative summary>
|
||||
```
|
||||
|
||||
Примеры:
|
||||
|
||||
```text
|
||||
fnd(M01): add canonical coordinate mapper
|
||||
net(M08): decode auth challenge safely
|
||||
rnd(M03): extract streaming target planner
|
||||
test(M00): add paired checkpoint manifest
|
||||
```
|
||||
|
||||
Описание commit/PR содержит:
|
||||
|
||||
```text
|
||||
Work-Package: M01-FND-COORDS-001
|
||||
Agent: sindo-main-codex
|
||||
Tests: <commands/results>
|
||||
Fidelity: <evidence/gap>
|
||||
```
|
||||
|
||||
Не смешивать formatting unrelated files, generated artifacts и функциональное изменение в одном commit.
|
||||
|
||||
## Handoff protocol
|
||||
|
||||
Готовый work package получает marker:
|
||||
|
||||
```text
|
||||
<!-- OPENWC_HANDOFF:READY:M01-FND-COORDS-001:<commit> -->
|
||||
```
|
||||
|
||||
Handoff обязан содержать:
|
||||
|
||||
- точный commit/branch;
|
||||
- outcome и изменённые paths;
|
||||
- публичные contracts/schema changes;
|
||||
- verification commands и результаты;
|
||||
- fidelity evidence/known gaps;
|
||||
- migration/cache/rebuild requirements;
|
||||
- unresolved risks;
|
||||
- recommended merge order;
|
||||
- updated API/module documentation и перечень diagrams.
|
||||
|
||||
Если работа частичная:
|
||||
|
||||
```text
|
||||
<!-- OPENWC_HANDOFF:PARTIAL:M01-FND-COORDS-001:<commit> -->
|
||||
```
|
||||
|
||||
`PARTIAL` не считается завершением и не разрешает ставить target `DONE`.
|
||||
|
||||
## Integration protocol
|
||||
|
||||
Интегратор:
|
||||
|
||||
1. Проверяет claim и отсутствие overlapping ownership.
|
||||
2. Проверяет diff на unrelated/user changes.
|
||||
3. Синхронизирует branch с `master` без уничтожения чужой истории.
|
||||
4. Запускает package verification и затронутые общие gates.
|
||||
5. Проверяет docs, migrations, cache versions и fidelity statement.
|
||||
6. Проверяет documentation gate из `docs/DOCUMENTATION_STANDARD.md`: API docs, inputs/outputs, diagrams, ownership и recovery.
|
||||
7. Проверяет coding gate из `docs/CODING_STANDARD.md`: ясные имена, KISS, отсутствие premature abstractions и скрытого data flow.
|
||||
8. Merge/squash выполняет в соответствии с историей work package.
|
||||
9. После merge запускает post-merge smoke.
|
||||
10. Закрывает claim marker-ом:
|
||||
|
||||
```text
|
||||
<!-- OPENWC_INTEGRATION:ACCEPTED:M01-FND-COORDS-001:<merge-commit> -->
|
||||
```
|
||||
|
||||
Только после интеграции всех обязательных work packages интегратор обновляет target Evidence/status.
|
||||
|
||||
## Конфликты
|
||||
|
||||
При обнаружении overlapping edits агент должен остановить изменение затронутого файла и связаться с владельцем. Запрещено:
|
||||
|
||||
- разрешать конфликт через слепое `ours/theirs`;
|
||||
- копировать чужой файл поверх своего;
|
||||
- делать `reset --hard` или checkout чужих изменений;
|
||||
- «временно» дублировать contract без migration plan.
|
||||
|
||||
Порядок разрешения:
|
||||
|
||||
1. определить semantic owner;
|
||||
2. выбрать merge order;
|
||||
3. первый владелец публикует минимальный contract/seam;
|
||||
4. второй rebase и адаптирует свою ветку;
|
||||
5. integration tests подтверждают совместимость.
|
||||
|
||||
## Status updates и stale work
|
||||
|
||||
Владелец обновляет claim после значимого результата или не реже срока lease. Status краткий:
|
||||
|
||||
```text
|
||||
State: active | waiting | ready | partial
|
||||
Done: ...
|
||||
Next: ...
|
||||
Blocked by: ...
|
||||
Touched paths: ...
|
||||
```
|
||||
|
||||
Если работа прекращена, claim явно освобождается. Незавершённую ветку не удаляют до handoff с описанием пригодных commits и известных проблем.
|
||||
|
||||
## Минимальная схема для двух разработчиков
|
||||
|
||||
- Один человек назначается интегратором текущего milestone.
|
||||
- Второй берёт work package без пересечения файлов либо готовит fixtures/reference/adapter.
|
||||
- Роль интегратора можно менять после каждого milestone.
|
||||
- Перед началом дня оба выполняют `git fetch` и сверяют claims/draft PRs.
|
||||
- Перед merge второй агент не начинает работу поверх непроверенного commit, если dependency не опубликована как contract branch.
|
||||
|
||||
Пример для M01:
|
||||
|
||||
```text
|
||||
Developer A / integrator:
|
||||
M01-FND-COORDS-001 — coordinate types and mapper
|
||||
|
||||
Developer B:
|
||||
M01-QAR-COORD-FIXTURES-001 — known ADT/server points and tests
|
||||
|
||||
После merge contract:
|
||||
A — StreamingFocus migration
|
||||
B — placement/SQL conversion audit
|
||||
```
|
||||
|
||||
## Автоматическая проверка
|
||||
|
||||
Перед handoff и merge запускать:
|
||||
|
||||
```powershell
|
||||
.\tools\verify_coordination.ps1
|
||||
.\tools\verify_documentation.ps1
|
||||
```
|
||||
|
||||
Первая проверка требует ровно один `ACTIVE` numbered target, соответствие `Current target`, валидные и уникальные fallback claim markers. Вторая проверяет обязательные documentation files, структуру module specs, наличие data-flow diagram и относительные ссылки. Истёкший claim выдаёт warning и требует человеческого решения, но не удаляется автоматически.
|
||||
@@ -47,9 +47,15 @@ Decision/ADR:
|
||||
| wow.export | REFERENCE | Asset conversion | M2/WMO/material/export edge cases | Web-specific pipeline |
|
||||
| Blender WoW Studio | REFERENCE | Authoring/conversion | WMO/M2/ADT authoring knowledge | Blender-specific UI/data model |
|
||||
| [Wowser](https://github.com/wowserhq/wowser) | REFERENCE | 3.3.5a web client | Auth/realm/character/world protocol, binary parsing, asset pipeline и render research | Старый JS/WebGL proof-of-concept, неполный клиент |
|
||||
| [warcraft-rs](https://github.com/wowemulation-dev/warcraft-rs) | CANDIDATE | WoW formats/CLI | Independent MPQ/DBC/BLP/ADT/WDT/WDL/M2/WMO validation и conversion oracle | Rust/tool duplication; claims require fixtures |
|
||||
| [WoWDBDefs](https://github.com/wowdev/WoWDBDefs) | REFERENCE | Client DB schemas | Versioned DBC definitions и typed-code generation input | Definitions still require build-specific validation |
|
||||
| [wow_dbc](https://github.com/gtker/wow_dbc) | REFERENCE | DBC | 1.12/2.4.3/3.3.5 read/write и SQLite conversion ideas | Older release, Rust integration unnecessary by default |
|
||||
| [recast-rs](https://github.com/wowemulation-dev/recast-rs) | CANDIDATE | Navigation | Offline navmesh, reachability и dungeon validation | Server mmap compatibility не доказана |
|
||||
| [rilua](https://github.com/wowemulation-dev/rilua) | CANDIDATE | Lua/addons | Lua 5.1.1 runtime candidate и headless compatibility oracle | Taint/WoW API/FrameXML отсутствуют; Rust bridge |
|
||||
| PUC-Rio Lua 5.1.1 | CANDIDATE | Lua reference/runtime | Authoritative Lua oracle и альтернативный embedded VM | Нужны WoW sandbox, taint и безопасный C++ boundary |
|
||||
| [GdUnit4](https://github.com/godot-gdunit-labs/gdUnit4) | CANDIDATE | Godot tests | GDScript, scene, Editor и CI test runner для Godot 4.6 | Plugin/toolchain pin и adoption spike |
|
||||
| [Keira3](https://github.com/azerothcore/Keira3) | REFERENCE | AzerothCore DB editor | Field semantics, SQL generation и DB editor UX | AGPL; schema-specific web architecture |
|
||||
| [WowBench](https://sourceforge.net/projects/wowbench/) | REFERENCE | WoW UI/API | Offline XML/Lua API emulation и addon test ideas | Старый и неполный implementation |
|
||||
|
||||
## rilua — карточка кандидата
|
||||
|
||||
@@ -79,6 +85,22 @@ Decision/ADR:
|
||||
- **Known gaps:** совместимость `.mmap/.mmtile` и параметров TrinityCore/AzerothCore не доказана.
|
||||
- **Spike:** synthetic + one WoW tile comparison с C++ Recast; затем отдельный server mmap compatibility experiment.
|
||||
|
||||
## warcraft-rs — карточка кандидата
|
||||
|
||||
- **Problem solved:** единый CLI/library set для MPQ, DBC, BLP, ADT, WDT, WDL, M2 и WMO версий 1.12.1–5.4.8, включая заявленную full support 3.3.5a.
|
||||
- **Planned boundary:** сначала внешний pinned CLI как independent parser/validator oracle; не заменять текущий native pipeline автоматически.
|
||||
- **License:** MIT OR Apache-2.0.
|
||||
- **Spike:** сравнить OpenWC и warcraft-rs на synthetic/corrupt/real local fixtures: parsed counts, bounds, flags, alpha, materials, animations и diagnostics.
|
||||
- **Decision policy:** отдельные crates рассматриваются только если дают доказанное преимущество; Rust runtime/toolchain не вводится ради дублирования работающего C++ parser.
|
||||
|
||||
## GdUnit4 — карточка кандидата
|
||||
|
||||
- **Problem solved:** Godot 4 GDScript/scene tests, assertions, mocks, input simulation, command-line and JUnit reports.
|
||||
- **Planned boundary:** development/test plugin only; production export не зависит от test framework.
|
||||
- **License:** MIT.
|
||||
- **Spike:** закрепить совместимую с Godot 4.6.1 версию; проверить pure GDScript, scene lifecycle, input, EditorPlugin и headless CI sample; измерить startup overhead.
|
||||
- **Decision policy:** ADOPTED только после воспроизводимого Windows/headless run и отсутствия конфликта с project plugins.
|
||||
|
||||
## Процесс добавления инструмента
|
||||
|
||||
1. Добавить карточку со статусом `REFERENCE` или `CANDIDATE`.
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# OpenWC Module Documentation
|
||||
|
||||
Модульные спецификации создаются по [`TEMPLATE.md`](TEMPLATE.md) и правилам [`../DOCUMENTATION_STANDARD.md`](../DOCUMENTATION_STANDARD.md).
|
||||
|
||||
## Registry
|
||||
|
||||
| Module | Current status | Primary specification |
|
||||
|---|---|---|
|
||||
| Foundation/data | Planned/Partial | [`../../targets/roadmap/01-foundation-and-data.md`](../../targets/roadmap/01-foundation-and-data.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) |
|
||||
| Gameplay | Prototype/Planned | [`../../targets/roadmap/04-gameplay-systems.md`](../../targets/roadmap/04-gameplay-systems.md) |
|
||||
| UI/Lua/audio | Prototype/Planned | [`../../targets/roadmap/05-ui-lua-audio.md`](../../targets/roadmap/05-ui-lua-audio.md) |
|
||||
| Editor/content tools | Prototype/Planned | [`../../targets/roadmap/06-editor-and-content-tools.md`](../../targets/roadmap/06-editor-and-content-tools.md) |
|
||||
| Server adapters | Planned | [`../../targets/roadmap/07-server-emulator-integration.md`](../../targets/roadmap/07-server-emulator-integration.md) |
|
||||
| Quality/release | Partial | [`../../targets/roadmap/08-quality-and-release.md`](../../targets/roadmap/08-quality-and-release.md) |
|
||||
|
||||
Roadmap-файл описывает полную целевую программу. При начале реализации самостоятельной подсистемы создаётся отдельный module spec, показывающий фактический статус и текущий data flow.
|
||||
@@ -0,0 +1,127 @@
|
||||
# <Module Name>
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Planned / Prototype / Partial / Implemented / Verified / Deprecated |
|
||||
| Target/work package | |
|
||||
| Owners | |
|
||||
| Last verified | `<commit>, YYYY-MM-DD` |
|
||||
| Profiles/capabilities | |
|
||||
|
||||
## Purpose
|
||||
|
||||
|
||||
## Non-goals
|
||||
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Input[Input contract] --> Module[Module]
|
||||
Module --> Output[Output contract]
|
||||
```
|
||||
|
||||
Allowed dependencies:
|
||||
|
||||
-
|
||||
|
||||
Forbidden dependencies:
|
||||
|
||||
-
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
Side effects:
|
||||
|
||||
-
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Source --> Transform --> Result
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Created
|
||||
Created --> Ready
|
||||
Ready --> Stopped
|
||||
Stopped --> [*]
|
||||
```
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant A as Producer
|
||||
participant M as Module
|
||||
participant B as Consumer
|
||||
A->>M: Input
|
||||
M-->>B: Output
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|
||||
|---|---|---|---|---|
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
- Logs:
|
||||
- Metrics:
|
||||
- Debug views:
|
||||
- Correlation IDs:
|
||||
|
||||
## Verification
|
||||
|
||||
- Unit/contract tests:
|
||||
- Integration/E2E:
|
||||
- Fidelity evidence:
|
||||
- Performance budgets:
|
||||
- Manual diagnostics:
|
||||
|
||||
## Extension points
|
||||
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- ADR:
|
||||
- Upstream/reference:
|
||||
@@ -0,0 +1,267 @@
|
||||
# World Renderer
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Partial |
|
||||
| Target/work package | M00 active; декомпозиция M01–M03 |
|
||||
| Owners | Renderer workstream / milestone integrator |
|
||||
| Last verified | M00 visual-diff worktree, 2026-07-11 |
|
||||
| Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete |
|
||||
|
||||
## Purpose
|
||||
|
||||
Загрузить и визуализировать мир WoW 3.3.5a вокруг streaming focus: terrain, ADT placements, M2, WMO, liquids, sky/light и character/world presentation experiments. Система должна скрывать I/O/parsing/finalization latency за prewarm и per-frame budgets.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Не владеет gameplay/world authoritative state.
|
||||
- Не читает network packets или server DB.
|
||||
- Не определяет player input/movement rules.
|
||||
- Не является Content Project или editor source of truth.
|
||||
- Текущий module не заявляет визуальное соответствие `1:1`; gaps ведутся в [`../../RENDER.md`](../../RENDER.md).
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Focus[Camera/player/editor focus] --> Loader[StreamingWorldLoader]
|
||||
Assets[Extracted WDT/ADT/M2/WMO/BLP] --> Native[Native loaders]
|
||||
Cache[Baked terrain/M2/WMO caches] --> Loader
|
||||
Native --> Parsed[Parsed tile/model data]
|
||||
Parsed --> Loader
|
||||
Loader --> Scene[SceneTree nodes]
|
||||
Loader --> RS[RenderingServer RIDs/MultiMesh]
|
||||
Sky[DBC sky/light data] --> SkyController[WowSkyController]
|
||||
SkyController --> Scene
|
||||
```
|
||||
|
||||
Allowed dependencies:
|
||||
|
||||
- native format loaders;
|
||||
- render builders/material helpers;
|
||||
- immutable/cached render resources;
|
||||
- renderer configuration and focus;
|
||||
- Godot rendering/scene APIs.
|
||||
|
||||
Forbidden dependencies:
|
||||
|
||||
- packet codecs/world session;
|
||||
- gameplay reducers/quest/combat state;
|
||||
- runtime UI Controls;
|
||||
- direct TrinityCore/AzerothCore database access.
|
||||
|
||||
## Public API
|
||||
|
||||
Текущая система ещё не имеет стабильного facade. Фактический integration surface — `StreamingWorldLoader` Node3D и exported properties. M03 должен заменить этот surface на `WorldRenderFacade`.
|
||||
|
||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||
|---|---|---|---|---|
|
||||
| `map_name` | Exported property | WDT/map directory name | Set before world load | Missing WDT produces diagnostic/empty world |
|
||||
| `extracted_dir` | Exported property | Root of legally extracted client data | Session lifetime | Missing files reported per loader path |
|
||||
| `camera_path` | Exported property | Current streaming focus compatibility path | Main thread, scene lifetime | Missing camera prevents normal focus update |
|
||||
| `quality_preset` | Exported property | Applies renderer budgets/radii | Before/at runtime depending property | Invalid combination should be diagnosed |
|
||||
| `runtime_stats_enabled` | Exported property | Enables periodic performance snapshot | Runtime mutable | Logging overhead only |
|
||||
| `hitch_profiler_enabled` | Exported property | Enables named hitch sections | Runtime mutable | Logging overhead only |
|
||||
|
||||
Публичным contract не считаются внутренние dictionaries, queues, job records и generated node names.
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Map/focus/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 | Baked resources | Cache build tools | Streamer/builders | Shared immutable resource/cache entry | Session/cache lifetime |
|
||||
| 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 | Metrics/diagnostics | Hitch/stat instrumentation | Logs/baseline tools | Immutable records | Periodic/frame |
|
||||
|
||||
Side effects:
|
||||
|
||||
- reads extracted files and generated caches;
|
||||
- creates/frees scene nodes, meshes, materials, MultiMeshes and RIDs;
|
||||
- submits worker tasks;
|
||||
- mutates shader globals/environment through render/sky paths;
|
||||
- writes logs and baseline captures through tools.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
F[Streaming focus] --> T[Compute desired ADT tiles]
|
||||
WDT[WDT tile catalog] --> T
|
||||
T --> Q[Tile load queue]
|
||||
Q --> P[Worker parse/cache load]
|
||||
P --> R[Result queues]
|
||||
R --> B[Per-frame budget scheduler]
|
||||
B --> Terrain[Terrain attach/upgrade]
|
||||
B --> M2[M2 group/MultiMesh attach]
|
||||
B --> WMO[WMO instance/group attach]
|
||||
B --> Liquid[Liquid attach]
|
||||
Terrain --> World[Godot world]
|
||||
M2 --> World
|
||||
WMO --> World
|
||||
Liquid --> World
|
||||
F --> E[Eviction/retention decisions]
|
||||
E --> World
|
||||
```
|
||||
|
||||
## Lifecycle/state
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Initializing
|
||||
Initializing --> Ready: WDT/catalog/config ready
|
||||
Initializing --> Degraded: missing loaders/data/cache
|
||||
Ready --> Streaming: focus update
|
||||
Streaming --> Streaming: load/finalize/evict ticks
|
||||
Streaming --> SwitchingMap: map/focus reset
|
||||
SwitchingMap --> Initializing
|
||||
Ready --> ShuttingDown
|
||||
Degraded --> ShuttingDown
|
||||
Streaming --> ShuttingDown
|
||||
ShuttingDown --> [*]
|
||||
```
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Focus as Camera/Player/Editor
|
||||
participant Stream as StreamingWorldLoader
|
||||
participant Worker as Worker task
|
||||
participant Budget as Main-thread budget
|
||||
participant Render as SceneTree/RenderingServer
|
||||
Focus->>Stream: focus position changed
|
||||
Stream->>Stream: desired tiles and priorities
|
||||
Stream->>Worker: parse/load tile and detail data
|
||||
Worker-->>Stream: immutable result
|
||||
Stream->>Budget: enqueue finalize operations
|
||||
Budget->>Render: attach bounded terrain/M2/WMO/liquid work
|
||||
Stream->>Render: evict outside retention range
|
||||
Stream->>Worker: shutdown: wait for WorkerThreadPool tasks
|
||||
Stream->>Stream: shutdown: finish registered ResourceLoader requests
|
||||
Stream->>Render: clear queues, nodes, caches and RIDs
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- `StreamingWorldLoader` владеет tile states, queues, task registries, active world instances и render caches session scope.
|
||||
- Worker tasks не должны менять SceneTree и shared Resource concurrently.
|
||||
- Parsed/grouped results передаются обратно через guarded result queues.
|
||||
- Mesh/material/node/RID finalization выполняется main thread и ограничивается exported budgets.
|
||||
- Shutdown/map switch обязан дождаться WorkerThreadPool jobs и зарегистрированных threaded ResourceLoader requests до очистки очередей, nodes, caches и RIDs.
|
||||
- Cache resources считаются immutable после публикации.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure | Detection | Behavior | Diagnostic | Recovery |
|
||||
|---|---|---|---|---|
|
||||
| Missing WDT/ADT | File/catalog check | Tile/map unavailable | Loader warning/error | Fix extraction/profile, reload |
|
||||
| Cache version mismatch | Required format constant | Reject stale cache/fallback | Version diagnostic | Rebuild matching cache |
|
||||
| Worker failure | Task result/empty data | Skip/degrade affected asset | Queue/task warning | Retry/rebuild source |
|
||||
| Main-thread hitch | Named section timing | Frame spike, work remains queued | `HITCH` log | Lower budget/fix finalize path |
|
||||
| D3D12 descriptor exhaustion | Rendering backend error | Render failure/fallback backend | Godot error + baseline report | Dedup resources/fix settings |
|
||||
| Teleport/map change | Focus/session transition | Old jobs become stale | Target/session generation | Cancel/drop stale results |
|
||||
| Shutdown leak | Godot leak/RID diagnostics | Resource retained | Verbose shutdown report | Drain owned worker and resource requests before clearing registries |
|
||||
|
||||
## Configuration and capabilities
|
||||
|
||||
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|
||||
|---|---|---|---|---|
|
||||
| Quality preset | Scene-specific | All | Limited | Applies radii and finalize budgets |
|
||||
| Terrain control splat | Enabled in current High paths | Render | Limited | Near terrain texture quality |
|
||||
| M2/WMO/liquids | Scene/profile-specific | Render | Debug/config | Enables asset categories |
|
||||
| Runtime stats/hitch profiler | Usually debug | Dev/test | Yes | Observability cost |
|
||||
| Shadow flags | Mostly disabled/limited | Blizzlike/Enhanced | Profile | CPU/GPU cost and visuals |
|
||||
|
||||
Exact exported settings and cache versions remain documented in [`../../RENDER.md`](../../RENDER.md) until extracted services receive individual specs.
|
||||
|
||||
## Persistence, cache and migration
|
||||
|
||||
- Renderer reads caches under `data/cache`; caches are not committed.
|
||||
- Format changes require `FORMAT_VERSION`/required-version bump and rebuild instructions.
|
||||
- Atomic cache publication and unified manifest are target-state work from FND/M03.
|
||||
- Material-only changes may refresh without geometry rebake only when documented safe.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
- Logs: `HITCH`, `PERF`, `TERRAIN_QUALITY`, `SKY_LIGHT`, `SKYBOX_MODEL`.
|
||||
- Metrics: queue depths, active tiles/assets, named finalize times, frame percentiles and max hitch.
|
||||
- Debug views: render checkpoint scenes/captures; future streaming/LOD/portal overlays.
|
||||
- Correlation IDs: currently mostly tile/path keys; target architecture adds session/job IDs.
|
||||
|
||||
## Verification
|
||||
|
||||
- Unit/contract tests: 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.
|
||||
- Fidelity evidence: пять локальных build 12340 reference JPG откалибровали terrain/ADT/M2/WMO/liquid viewpoints; automated paired-image metrics exist, но synthetic animation/dusk и полный human approval ещё не закрыты.
|
||||
- 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).
|
||||
|
||||
## Extension points
|
||||
|
||||
- Target `WorldRenderFacade` for runtime/editor/capture callers.
|
||||
- Target `StreamingFocus` independent of Camera3D.
|
||||
- Extracted planner, budget scheduler, terrain, M2, WMO, liquid, sky and character services.
|
||||
- Separate Blizzlike/Enhanced material and quality profiles.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| ADT streaming/terrain | Partial | M00 checkpoints and current scenes | Fidelity/performance gaps remain |
|
||||
| Static M2 placement | Partial | MultiMesh/dedupe probes | Full materials/animation/effects |
|
||||
| WMO rendering | Partial | Cached group rendering | Portals/rooms/material parity |
|
||||
| Liquids | Partial | MH2O/MLIQ paths | LiquidType/depth/shore fidelity |
|
||||
| Sky/light | Partial | DBC controller/logs | Zone transition paired validation |
|
||||
| Character presentation | Prototype | Character creator/player experiment | Full customization/equipment states |
|
||||
| Stable renderer facade | Planned | M03 | Current caller uses monolithic Node API |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- Monolithic streamer mixes planning, jobs, caches and presentation.
|
||||
- Direct camera path remains until M01/M03.
|
||||
- Original-client paired fidelity evidence incomplete.
|
||||
- Первый paired run выявил coordinate/placement mismatch: несколько совпадающих server-derived camera positions оказываются под terrain или внутри WMO/rocks OpenWC.
|
||||
- Terrain-height probe исключил under-terrain состояние для всех пяти точек; waterfall exact-XZ miss классифицирован как TriangleMesh seam/edge и подтверждён nearby sample в 2 units.
|
||||
- Camera-occluder probe не нашёл camera containment в пяти точках; paired mismatch локализован прежде всего в manual look direction/target/FOV calibration, с явным ограничением по RID-only geometry.
|
||||
- Empirical FOV sweep выявил, что checkpoint camera должна явно вызывать `make_current()`; после исправления projection ranking остаётся inconclusive из-за неизвестного manual yaw/pitch/framing reference.
|
||||
- Camera-pose sweep can now rank bounded yaw/pitch grids without changing manifest defaults; perceptual ranking remains diagnostic and requires human framing approval.
|
||||
- The first Goldshire pose grid was invalidated by WMO readiness: the inn is absent after 2 seconds but visible after the manifest-standard 8-second wait. Pose comparisons must stabilize asynchronous checkpoint content first.
|
||||
- A ready-scene Goldshire 3x3 grid improved mean error from `0.101402` at zero offsets to `0.087952` at yaw `10`/pitch `-10`; pitch/FOV refinement remains required before manifest calibration.
|
||||
- Joint Goldshire pitch/FOV refinement had no human-approved optimum: full-frame error preferred grass-heavy FOV 62/pitch -25, while FOV 38/pitch -10 better matched building scale. Landmark/region scoring is required before calibration.
|
||||
- Common-region scoring `[300,100,1400,650]` moved Goldshire's numeric optimum to FOV 38/yaw 10/pitch -15, but human inspection still found an oversized/cropped facade; semantic landmarks or masks remain required.
|
||||
- D3D12 descriptor issues remain; the capture-path anonymous `RefCounted` shutdown leak is regression-covered by a clean verbose dry-run, while other RID/resource diagnostics still require independent evidence.
|
||||
- M2/WMO/material/particle/ribbon/portal parity incomplete.
|
||||
- Public API is mostly exported configuration rather than stable contracts.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Current runtime orchestration, queues, caches and attachment |
|
||||
| `addons/mpq_extractor/loaders/adt_builder.gd` | Terrain/material/liquid construction |
|
||||
| `addons/mpq_extractor/loaders/m2_builder.gd` | Static M2 mesh/material construction |
|
||||
| `addons/mpq_extractor/loaders/m2_native_animated_builder.gd` | Native animated M2 experiment |
|
||||
| `addons/mpq_extractor/loaders/wmo_builder.gd` | WMO groups/doodads/liquids construction |
|
||||
| `src/scenes/sky/wow_sky_controller.gd` | DBC-driven sky/light/environment |
|
||||
| `src/native/src/*_loader.cpp` | Native binary parsing |
|
||||
| `src/tools/build_*cache.gd`, `src/tools/bake_*cache.gd` | Offline cache generation |
|
||||
| `tools/run_render_baseline.ps1` | Unified M00 baseline runner |
|
||||
| `src/tools/compare_render_checkpoints.gd` | Offline JPG/PNG paired-image perceptual metrics and JSON pass/fail report |
|
||||
| `src/tools/capture_render_checkpoints.gd` | Deterministic no-roll checkpoint camera, performance and visual capture |
|
||||
| `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/probe_render_terrain_height.gd` | Offline active-mesh terrain height and camera-clearance report |
|
||||
| `src/tools/probe_render_camera_occluders.gd` | Scene-tree placement containment and camera-to-target AABB intersection report |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
- [`../RENDER_BASELINE.md`](../RENDER_BASELINE.md)
|
||||
- [`../CAMERA_POSE_SWEEP.md`](../CAMERA_POSE_SWEEP.md)
|
||||
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||
- [`../GODOT_BEST_PRACTICES.md`](../GODOT_BEST_PRACTICES.md)
|
||||
@@ -2073,7 +2073,7 @@ func _wait_for_tile_tasks() -> void:
|
||||
var path: String = String(pending.get("path", ""))
|
||||
if not path.is_empty():
|
||||
var status := ResourceLoader.load_threaded_get_status(path)
|
||||
if status == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
if status == ResourceLoader.THREAD_LOAD_IN_PROGRESS or status == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
ResourceLoader.load_threaded_get(path)
|
||||
_tile_loading_tasks.clear()
|
||||
|
||||
@@ -2094,7 +2094,7 @@ func _wait_for_tile_tasks() -> void:
|
||||
if path.is_empty():
|
||||
continue
|
||||
var status := ResourceLoader.load_threaded_get_status(path)
|
||||
if status == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
if status == ResourceLoader.THREAD_LOAD_IN_PROGRESS or status == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
ResourceLoader.load_threaded_get(path)
|
||||
_m2_mesh_load_requests.clear()
|
||||
_m2_mesh_finalize_queue.clear()
|
||||
@@ -2105,7 +2105,7 @@ func _wait_for_tile_tasks() -> void:
|
||||
if path.is_empty():
|
||||
continue
|
||||
var status := ResourceLoader.load_threaded_get_status(path)
|
||||
if status == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
if status == ResourceLoader.THREAD_LOAD_IN_PROGRESS or status == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
ResourceLoader.load_threaded_get(path)
|
||||
_m2_animation_load_requests.clear()
|
||||
_m2_animation_finalize_queue.clear()
|
||||
@@ -2115,7 +2115,7 @@ func _wait_for_tile_tasks() -> void:
|
||||
if path.is_empty():
|
||||
continue
|
||||
var status := ResourceLoader.load_threaded_get_status(path)
|
||||
if status == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
if status == ResourceLoader.THREAD_LOAD_IN_PROGRESS or status == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
ResourceLoader.load_threaded_get(path)
|
||||
_wmo_scene_load_requests.clear()
|
||||
|
||||
@@ -2124,7 +2124,7 @@ func _wait_for_tile_tasks() -> void:
|
||||
if path.is_empty():
|
||||
continue
|
||||
var status := ResourceLoader.load_threaded_get_status(path)
|
||||
if status == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
if status == ResourceLoader.THREAD_LOAD_IN_PROGRESS or status == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
ResourceLoader.load_threaded_get(path)
|
||||
_wmo_render_load_requests.clear()
|
||||
_wmo_render_missing_cache.clear()
|
||||
@@ -2137,7 +2137,7 @@ func _wait_for_tile_tasks() -> void:
|
||||
if path.is_empty():
|
||||
continue
|
||||
var status := ResourceLoader.load_threaded_get_status(path)
|
||||
if status == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
if status == ResourceLoader.THREAD_LOAD_IN_PROGRESS or status == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
ResourceLoader.load_threaded_get(path)
|
||||
_terrain_upgrade_tasks.clear()
|
||||
|
||||
@@ -2148,7 +2148,7 @@ func _wait_for_tile_tasks() -> void:
|
||||
if path.is_empty():
|
||||
continue
|
||||
var status := ResourceLoader.load_threaded_get_status(path)
|
||||
if status == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
if status == ResourceLoader.THREAD_LOAD_IN_PROGRESS or status == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
ResourceLoader.load_threaded_get(path)
|
||||
_terrain_control_splat_cache_tasks.clear()
|
||||
|
||||
@@ -2162,7 +2162,7 @@ func _wait_for_tile_tasks() -> void:
|
||||
if path.is_empty():
|
||||
continue
|
||||
var status := ResourceLoader.load_threaded_get_status(path)
|
||||
if status == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
if status == ResourceLoader.THREAD_LOAD_IN_PROGRESS or status == ResourceLoader.THREAD_LOAD_LOADED:
|
||||
ResourceLoader.load_threaded_get(path)
|
||||
_terrain_splat_cache_tasks.clear()
|
||||
_terrain_splat_result_mutex.lock()
|
||||
|
||||
@@ -2,6 +2,7 @@ extends SceneTree
|
||||
|
||||
const DEFAULT_MANIFEST_PATH := "res://src/tools/render_baseline_manifest.json"
|
||||
const M2_NATIVE_ANIMATED_BUILDER := preload("res://addons/mpq_extractor/loaders/m2_native_animated_builder.gd")
|
||||
const SHUTDOWN_DRAIN_FRAMES := 2
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
@@ -22,10 +23,16 @@ func _capture_async() -> void:
|
||||
var measure_seconds := float(_arg(args, "--measure", str(manifest.get("default_measure_seconds", 3.0))))
|
||||
var revision := _arg(args, "--revision", "worktree")
|
||||
var cache_state := _arg(args, "--cache-state", "existing")
|
||||
var camera_fov_override := float(_arg(args, "--camera-fov", str(manifest.get("camera_fov", 62.0))))
|
||||
var camera_yaw_offset_degrees := float(_arg(args, "--camera-yaw-offset", "0.0"))
|
||||
var camera_pitch_offset_degrees := float(_arg(args, "--camera-pitch-offset", "0.0"))
|
||||
var single_pass := args.has("--single-pass")
|
||||
var headless := DisplayServer.get_name().to_lower() == "headless"
|
||||
var dry_run := args.has("--dry-run") or headless
|
||||
var viewport: Array = manifest.get("viewport", [1280, 900])
|
||||
get_root().size = Vector2i(maxi(16, int(viewport[0])), maxi(16, int(viewport[1])))
|
||||
var viewport_width := int(_arg(args, "--viewport-width", str(viewport[0])))
|
||||
var viewport_height := int(_arg(args, "--viewport-height", str(viewport[1])))
|
||||
get_root().size = Vector2i(maxi(16, viewport_width), maxi(16, viewport_height))
|
||||
|
||||
var abs_output_dir := ProjectSettings.globalize_path(output_dir)
|
||||
DirAccess.make_dir_recursive_absolute(abs_output_dir)
|
||||
@@ -53,7 +60,7 @@ func _capture_async() -> void:
|
||||
var camera := Camera3D.new()
|
||||
camera.name = "CheckpointCamera"
|
||||
camera.current = true
|
||||
camera.fov = float(manifest.get("camera_fov", 62.0))
|
||||
camera.fov = camera_fov_override
|
||||
camera.far = 50000.0
|
||||
camera.position = _vector3(first.get("camera", [0.0, 0.0, 0.0]))
|
||||
(world as Node3D).add_child(camera)
|
||||
@@ -63,6 +70,7 @@ func _capture_async() -> void:
|
||||
get_root().add_child(world)
|
||||
await process_frame
|
||||
await process_frame
|
||||
camera.make_current()
|
||||
|
||||
var player := world.get_node_or_null("ThirdPersonPlayer") as Node3D
|
||||
if player != null:
|
||||
@@ -80,6 +88,9 @@ func _capture_async() -> void:
|
||||
"created_utc": Time.get_datetime_string_from_system(true, true),
|
||||
"dry_run": dry_run,
|
||||
"cache_state": cache_state,
|
||||
"camera_fov": camera_fov_override,
|
||||
"camera_yaw_offset_degrees": camera_yaw_offset_degrees,
|
||||
"camera_pitch_offset_degrees": camera_pitch_offset_degrees,
|
||||
"environment": _environment_metadata(),
|
||||
"comparison_budgets": manifest.get("comparison_budgets", {}),
|
||||
"cache_contract": manifest.get("cache_contract", {}),
|
||||
@@ -88,6 +99,8 @@ func _capture_async() -> void:
|
||||
}
|
||||
var captured := 0
|
||||
var passes := ["cold_process", "warm_revisit"]
|
||||
if single_pass:
|
||||
passes = ["cold_process"]
|
||||
if dry_run:
|
||||
passes = ["dry_run"]
|
||||
|
||||
@@ -107,7 +120,9 @@ func _capture_async() -> void:
|
||||
probe.scale = Vector3.ONE * float(checkpoint.get("probe_scale", 1.0))
|
||||
|
||||
camera.global_position = _vector3(checkpoint.get("camera", [0.0, 0.0, 0.0]))
|
||||
camera.look_at(_vector3(checkpoint.get("target", [0.0, 0.0, 0.0])), Vector3.UP)
|
||||
_orient_camera_without_roll(camera, _vector3(checkpoint.get("target", [0.0, 0.0, 0.0])))
|
||||
_apply_camera_pose_offsets(camera, camera_yaw_offset_degrees, camera_pitch_offset_degrees)
|
||||
camera.make_current()
|
||||
if player != null:
|
||||
player.global_position = _vector3(checkpoint.get("player", checkpoint.get("target", [0.0, 0.0, 0.0])))
|
||||
_set_sky_time(world, float(checkpoint.get("time_hours", 13.0)))
|
||||
@@ -115,11 +130,13 @@ func _capture_async() -> void:
|
||||
world.call("_refresh_streaming_targets_at", camera.global_position, true)
|
||||
|
||||
if dry_run:
|
||||
print("RENDER_CHECKPOINT dry_run name=%s coverage=%s camera=%s target=%s time=%.2f" % [
|
||||
print("RENDER_CHECKPOINT dry_run name=%s coverage=%s camera=%s target=%s yaw_offset=%.2f pitch_offset=%.2f time=%.2f" % [
|
||||
checkpoint_name,
|
||||
str(checkpoint.get("coverage", [])),
|
||||
camera.global_position,
|
||||
_vector3(checkpoint.get("target", [0.0, 0.0, 0.0])),
|
||||
camera_yaw_offset_degrees,
|
||||
camera_pitch_offset_degrees,
|
||||
float(checkpoint.get("time_hours", 13.0)),
|
||||
])
|
||||
(report["results"] as Array).append(_result_record(checkpoint, pass_name, 0.0, {}, ""))
|
||||
@@ -135,7 +152,6 @@ func _capture_async() -> void:
|
||||
await RenderingServer.frame_post_draw
|
||||
|
||||
var image := get_root().get_texture().get_image()
|
||||
image.flip_y()
|
||||
var file_name := "%s__%s.png" % [checkpoint_name, pass_name]
|
||||
var abs_path := abs_output_dir.path_join(file_name)
|
||||
var err := image.save_png(abs_path)
|
||||
@@ -162,6 +178,10 @@ func _capture_async() -> void:
|
||||
print("RENDER_BASELINE report=%s results=%d" % [report_path, captured])
|
||||
|
||||
world.queue_free()
|
||||
# SceneTree deletion is deferred. Allow the world and its queued children
|
||||
# to run their shutdown paths before engine teardown.
|
||||
for unused_frame in SHUTDOWN_DRAIN_FRAMES:
|
||||
await process_frame
|
||||
if captured <= 0:
|
||||
push_error("No checkpoints selected. --only filter was: %s" % only)
|
||||
quit(1)
|
||||
@@ -196,6 +216,20 @@ func _measure_frames(seconds: float, world: Node) -> Dictionary:
|
||||
}
|
||||
|
||||
|
||||
func _orient_camera_without_roll(camera: Camera3D, target_position: Vector3) -> void:
|
||||
var forward := (target_position - camera.global_position).normalized()
|
||||
var right := forward.cross(Vector3.UP).normalized()
|
||||
if right.is_zero_approx():
|
||||
right = Vector3.RIGHT
|
||||
var corrected_up := right.cross(forward).normalized()
|
||||
camera.global_basis = Basis(right, corrected_up, -forward)
|
||||
|
||||
|
||||
func _apply_camera_pose_offsets(camera: Camera3D, yaw_offset_degrees: float, pitch_offset_degrees: float) -> void:
|
||||
camera.global_rotate(Vector3.UP, deg_to_rad(yaw_offset_degrees))
|
||||
camera.rotate_object_local(Vector3.RIGHT, deg_to_rad(pitch_offset_degrees))
|
||||
|
||||
|
||||
func _result_record(checkpoint: Dictionary, pass_name: String, load_time_ms: float, metrics: Dictionary, sha256: String) -> Dictionary:
|
||||
return {
|
||||
"name": checkpoint.get("name", "checkpoint"),
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
extends SceneTree
|
||||
|
||||
## Compares original-client JPG/PNG references with OpenWC checkpoint PNGs.
|
||||
## Usage: godot --headless --path . --script res://src/tools/compare_render_checkpoints.gd --
|
||||
## --reference <directory> --candidate <directory> [--output <report.json>]
|
||||
## [--region x,y,width,height]
|
||||
## [--pixel-threshold 0.04] [--mean-threshold 0.015] [--changed-ratio-threshold 0.01]
|
||||
## Use --self-test for a synthetic regression check that writes only under user://.
|
||||
|
||||
const REPORT_SCHEMA_VERSION := 1
|
||||
const DEFAULT_PIXEL_THRESHOLD := 0.04
|
||||
const DEFAULT_MEAN_THRESHOLD := 0.015
|
||||
const DEFAULT_CHANGED_RATIO_THRESHOLD := 0.01
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
var arguments := _parse_arguments(OS.get_cmdline_user_args())
|
||||
if arguments.has("error"):
|
||||
push_error("RENDER_CHECKPOINT_DIFF: %s" % arguments.error)
|
||||
quit(2)
|
||||
return
|
||||
if arguments.get("self_test", false):
|
||||
quit(_run_self_test())
|
||||
return
|
||||
|
||||
var reference_directory := String(arguments.get("reference", ""))
|
||||
var candidate_directory := String(arguments.get("candidate", ""))
|
||||
if reference_directory.is_empty() or candidate_directory.is_empty():
|
||||
push_error("RENDER_CHECKPOINT_DIFF: --reference and --candidate are required")
|
||||
quit(2)
|
||||
return
|
||||
|
||||
var report := _compare_directories(reference_directory, candidate_directory, arguments)
|
||||
var output_path := String(arguments.get("output", ""))
|
||||
if not output_path.is_empty() and not _write_report(output_path, report):
|
||||
quit(2)
|
||||
return
|
||||
print("RENDER_CHECKPOINT_DIFF %s compared=%d failed=%d missing=%d" % [
|
||||
"PASS" if report.passed else "FAIL",
|
||||
report.compared_count,
|
||||
report.failed_count,
|
||||
report.missing_count,
|
||||
])
|
||||
quit(0 if report.passed else 1)
|
||||
|
||||
|
||||
func _parse_arguments(raw_arguments: PackedStringArray) -> Dictionary:
|
||||
var parsed := {
|
||||
"pixel_threshold": DEFAULT_PIXEL_THRESHOLD,
|
||||
"mean_threshold": DEFAULT_MEAN_THRESHOLD,
|
||||
"changed_ratio_threshold": DEFAULT_CHANGED_RATIO_THRESHOLD,
|
||||
}
|
||||
var index := 0
|
||||
while index < raw_arguments.size():
|
||||
var argument := raw_arguments[index]
|
||||
if argument == "--self-test":
|
||||
parsed.self_test = true
|
||||
index += 1
|
||||
continue
|
||||
if argument not in ["--reference", "--candidate", "--output", "--only", "--region", "--pixel-threshold", "--mean-threshold", "--changed-ratio-threshold"]:
|
||||
return {"error": "unknown argument: %s" % argument}
|
||||
if index + 1 >= raw_arguments.size():
|
||||
return {"error": "missing value for %s" % argument}
|
||||
var key := argument.trim_prefix("--").replace("-", "_")
|
||||
var value := raw_arguments[index + 1]
|
||||
if key == "region":
|
||||
var region_parse_result := _parse_comparison_region(value)
|
||||
if region_parse_result.has("error"):
|
||||
return region_parse_result
|
||||
parsed[key] = region_parse_result.region
|
||||
elif key.ends_with("threshold"):
|
||||
if not value.is_valid_float():
|
||||
return {"error": "%s requires a number" % argument}
|
||||
parsed[key] = float(value)
|
||||
else:
|
||||
parsed[key] = value
|
||||
index += 2
|
||||
return parsed
|
||||
|
||||
|
||||
func _parse_comparison_region(value: String) -> Dictionary:
|
||||
var components := value.split(",", false)
|
||||
if components.size() != 4:
|
||||
return {"error": "--region requires x,y,width,height"}
|
||||
for component in components:
|
||||
if not component.strip_edges().is_valid_int():
|
||||
return {"error": "--region components must be integers"}
|
||||
var region := Rect2i(
|
||||
int(components[0]),
|
||||
int(components[1]),
|
||||
int(components[2]),
|
||||
int(components[3])
|
||||
)
|
||||
if region.position.x < 0 or region.position.y < 0 or region.size.x <= 0 or region.size.y <= 0:
|
||||
return {"error": "--region requires non-negative x/y and positive width/height"}
|
||||
return {"region": region}
|
||||
|
||||
|
||||
func _compare_directories(reference_directory: String, candidate_directory: String, options: Dictionary) -> Dictionary:
|
||||
reference_directory = ProjectSettings.globalize_path(reference_directory)
|
||||
candidate_directory = ProjectSettings.globalize_path(candidate_directory)
|
||||
var reference_files := _reference_image_file_names(reference_directory)
|
||||
var only_filter := String(options.get("only", "")).to_lower()
|
||||
var results: Array[Dictionary] = []
|
||||
var failed_count := 0
|
||||
var missing_count := 0
|
||||
for file_name in reference_files:
|
||||
if not only_filter.is_empty() and not file_name.get_basename().to_lower().contains(only_filter):
|
||||
continue
|
||||
var reference_path := reference_directory.path_join(file_name)
|
||||
var checkpoint_name := file_name.get_basename()
|
||||
var candidate_file_names := _candidate_file_names(candidate_directory, checkpoint_name)
|
||||
if candidate_file_names.is_empty():
|
||||
results.append({"checkpoint": checkpoint_name, "reference": file_name, "status": "missing_candidate"})
|
||||
missing_count += 1
|
||||
continue
|
||||
for candidate_file_name in candidate_file_names:
|
||||
var candidate_path := candidate_directory.path_join(candidate_file_name)
|
||||
var comparison := _compare_images(reference_path, candidate_path, options)
|
||||
comparison.checkpoint = checkpoint_name
|
||||
comparison.reference = file_name
|
||||
comparison.candidate = candidate_file_name
|
||||
results.append(comparison)
|
||||
if comparison.status != "passed":
|
||||
failed_count += 1
|
||||
var no_reference_images := reference_files.is_empty()
|
||||
return {
|
||||
"schema_version": REPORT_SCHEMA_VERSION,
|
||||
"passed": not no_reference_images and failed_count == 0 and missing_count == 0,
|
||||
"reference_directory": reference_directory,
|
||||
"candidate_directory": candidate_directory,
|
||||
"pixel_threshold": options.pixel_threshold,
|
||||
"mean_threshold": options.mean_threshold,
|
||||
"changed_ratio_threshold": options.changed_ratio_threshold,
|
||||
"comparison_region": _region_array(options.get("region", Rect2i())),
|
||||
"compared_count": results.size() - missing_count,
|
||||
"failed_count": failed_count,
|
||||
"missing_count": missing_count,
|
||||
"no_reference_images": no_reference_images,
|
||||
"results": results,
|
||||
}
|
||||
|
||||
|
||||
func _compare_images(reference_path: String, candidate_path: String, options: Dictionary) -> Dictionary:
|
||||
var reference_image := Image.load_from_file(reference_path)
|
||||
var candidate_image := Image.load_from_file(candidate_path)
|
||||
if reference_image == null or candidate_image == null or reference_image.is_empty() or candidate_image.is_empty():
|
||||
return {"status": "load_error"}
|
||||
if reference_image.get_size() != candidate_image.get_size():
|
||||
return {
|
||||
"status": "size_mismatch",
|
||||
"reference_size": [reference_image.get_width(), reference_image.get_height()],
|
||||
"candidate_size": [candidate_image.get_width(), candidate_image.get_height()],
|
||||
}
|
||||
var comparison_region: Rect2i = options.get("region", Rect2i(Vector2i.ZERO, reference_image.get_size()))
|
||||
if comparison_region == Rect2i():
|
||||
comparison_region = Rect2i(Vector2i.ZERO, reference_image.get_size())
|
||||
var image_region := Rect2i(Vector2i.ZERO, reference_image.get_size())
|
||||
if not image_region.encloses(comparison_region):
|
||||
return {
|
||||
"status": "region_out_of_bounds",
|
||||
"region": _region_array(comparison_region),
|
||||
"image_size": [reference_image.get_width(), reference_image.get_height()],
|
||||
}
|
||||
|
||||
var error_sum := 0.0
|
||||
var changed_pixel_count := 0
|
||||
var pixel_count := comparison_region.size.x * comparison_region.size.y
|
||||
for y in range(comparison_region.position.y, comparison_region.end.y):
|
||||
for x in range(comparison_region.position.x, comparison_region.end.x):
|
||||
var perceptual_error := _perceptual_color_error(reference_image.get_pixel(x, y), candidate_image.get_pixel(x, y))
|
||||
error_sum += perceptual_error
|
||||
if perceptual_error > float(options.pixel_threshold):
|
||||
changed_pixel_count += 1
|
||||
var mean_perceptual_error := error_sum / float(pixel_count)
|
||||
var changed_pixel_ratio := float(changed_pixel_count) / float(pixel_count)
|
||||
var passed := mean_perceptual_error <= float(options.mean_threshold) and changed_pixel_ratio <= float(options.changed_ratio_threshold)
|
||||
return {
|
||||
"status": "passed" if passed else "threshold_exceeded",
|
||||
"width": reference_image.get_width(),
|
||||
"height": reference_image.get_height(),
|
||||
"region": _region_array(comparison_region),
|
||||
"mean_perceptual_error": mean_perceptual_error,
|
||||
"changed_pixel_ratio": changed_pixel_ratio,
|
||||
}
|
||||
|
||||
|
||||
func _region_array(region: Rect2i) -> Array[int]:
|
||||
if region == Rect2i():
|
||||
return []
|
||||
return [region.position.x, region.position.y, region.size.x, region.size.y]
|
||||
|
||||
|
||||
func _perceptual_color_error(reference_color: Color, candidate_color: Color) -> float:
|
||||
var reference_linear := reference_color.srgb_to_linear()
|
||||
var candidate_linear := candidate_color.srgb_to_linear()
|
||||
var red_delta := absf(reference_linear.r - candidate_linear.r)
|
||||
var green_delta := absf(reference_linear.g - candidate_linear.g)
|
||||
var blue_delta := absf(reference_linear.b - candidate_linear.b)
|
||||
var alpha_delta := absf(reference_linear.a - candidate_linear.a)
|
||||
return 0.2126 * red_delta + 0.7152 * green_delta + 0.0722 * blue_delta + 0.25 * alpha_delta
|
||||
|
||||
|
||||
func _reference_image_file_names(directory_path: String) -> Array[String]:
|
||||
var directory := DirAccess.open(directory_path)
|
||||
if directory == null:
|
||||
return []
|
||||
var file_names: Array[String] = []
|
||||
for file_name in directory.get_files():
|
||||
var lower_name := file_name.to_lower()
|
||||
if not lower_name.begins_with("diagnostic_") and (lower_name.ends_with(".png") or lower_name.ends_with(".jpg") or lower_name.ends_with(".jpeg")):
|
||||
file_names.append(file_name)
|
||||
file_names.sort()
|
||||
return file_names
|
||||
|
||||
|
||||
func _candidate_file_names(candidate_directory: String, checkpoint_name: String) -> Array[String]:
|
||||
var candidates: Array[String] = []
|
||||
for pass_name in ["cold_process", "warm_revisit"]:
|
||||
var file_name := "%s__%s.png" % [checkpoint_name, pass_name]
|
||||
if FileAccess.file_exists(candidate_directory.path_join(file_name)):
|
||||
candidates.append(file_name)
|
||||
if candidates.is_empty():
|
||||
var exact_file_name := "%s.png" % checkpoint_name
|
||||
if FileAccess.file_exists(candidate_directory.path_join(exact_file_name)):
|
||||
candidates.append(exact_file_name)
|
||||
return candidates
|
||||
|
||||
|
||||
func _write_report(output_path: String, report: Dictionary) -> bool:
|
||||
var absolute_path := ProjectSettings.globalize_path(output_path)
|
||||
var error := DirAccess.make_dir_recursive_absolute(absolute_path.get_base_dir())
|
||||
if error != OK:
|
||||
push_error("RENDER_CHECKPOINT_DIFF: cannot create report directory: %s" % error_string(error))
|
||||
return false
|
||||
var report_file := FileAccess.open(absolute_path, FileAccess.WRITE)
|
||||
if report_file == null:
|
||||
push_error("RENDER_CHECKPOINT_DIFF: cannot write report: %s" % output_path)
|
||||
return false
|
||||
report_file.store_string(JSON.stringify(report, " "))
|
||||
return true
|
||||
|
||||
|
||||
func _run_self_test() -> int:
|
||||
var root := "user://render_checkpoint_diff_self_test"
|
||||
var reference_directory := root.path_join("reference")
|
||||
var identical_directory := root.path_join("identical")
|
||||
var changed_directory := root.path_join("changed")
|
||||
for directory_path in [reference_directory, identical_directory, changed_directory]:
|
||||
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(directory_path))
|
||||
_clear_directory_files(directory_path)
|
||||
var reference_image := Image.create(2, 2, false, Image.FORMAT_RGBA8)
|
||||
reference_image.fill(Color(0.25, 0.5, 0.75, 1.0))
|
||||
var changed_image := reference_image.duplicate()
|
||||
changed_image.set_pixel(0, 0, Color.WHITE)
|
||||
if reference_image.save_jpg(reference_directory.path_join("synthetic.jpg"), 1.0) != OK:
|
||||
push_error("RENDER_CHECKPOINT_DIFF SELF_TEST: cannot save JPG reference")
|
||||
return 1
|
||||
for pass_name in ["cold_process", "warm_revisit"]:
|
||||
if reference_image.save_png(identical_directory.path_join("synthetic__%s.png" % pass_name)) != OK:
|
||||
return 1
|
||||
if changed_image.save_png(changed_directory.path_join("synthetic__%s.png" % pass_name)) != OK:
|
||||
return 1
|
||||
var options := {
|
||||
"pixel_threshold": DEFAULT_PIXEL_THRESHOLD,
|
||||
"mean_threshold": DEFAULT_MEAN_THRESHOLD,
|
||||
"changed_ratio_threshold": DEFAULT_CHANGED_RATIO_THRESHOLD,
|
||||
}
|
||||
var identical_report := _compare_directories(reference_directory, identical_directory, options)
|
||||
var changed_report := _compare_directories(reference_directory, changed_directory, options)
|
||||
if not identical_report.passed or changed_report.passed:
|
||||
push_error("RENDER_CHECKPOINT_DIFF SELF_TEST: expected identical pass and changed failure")
|
||||
return 1
|
||||
var region_options := options.duplicate()
|
||||
region_options["region"] = Rect2i(1, 1, 1, 1)
|
||||
var excluded_change_report := _compare_directories(reference_directory, changed_directory, region_options)
|
||||
if not excluded_change_report.passed or excluded_change_report.comparison_region != [1, 1, 1, 1]:
|
||||
push_error("RENDER_CHECKPOINT_DIFF SELF_TEST: region must exclude pixels outside the rectangle")
|
||||
return 1
|
||||
var out_of_bounds_options := options.duplicate()
|
||||
out_of_bounds_options["region"] = Rect2i(1, 1, 2, 2)
|
||||
var out_of_bounds_result := _compare_images(
|
||||
reference_directory.path_join("synthetic.jpg"),
|
||||
changed_directory.path_join("synthetic__cold_process.png"),
|
||||
out_of_bounds_options
|
||||
)
|
||||
if out_of_bounds_result.status != "region_out_of_bounds":
|
||||
push_error("RENDER_CHECKPOINT_DIFF SELF_TEST: out-of-bounds region must fail")
|
||||
return 1
|
||||
if reference_image.save_jpg(reference_directory.path_join("ignored.jpg"), 1.0) != OK:
|
||||
return 1
|
||||
var filtered_options := options.duplicate()
|
||||
filtered_options["only"] = "synthetic"
|
||||
var filtered_report := _compare_directories(reference_directory, identical_directory, filtered_options)
|
||||
if not filtered_report.passed or filtered_report.compared_count != 2:
|
||||
push_error("RENDER_CHECKPOINT_DIFF SELF_TEST: --only must exclude unrelated references")
|
||||
return 1
|
||||
print("RENDER_CHECKPOINT_DIFF SELF_TEST PASS")
|
||||
return 0
|
||||
|
||||
|
||||
func _clear_directory_files(directory_path: String) -> void:
|
||||
var directory := DirAccess.open(directory_path)
|
||||
if directory == null:
|
||||
return
|
||||
for file_name in directory.get_files():
|
||||
directory.remove(file_name)
|
||||
@@ -0,0 +1,166 @@
|
||||
extends SceneTree
|
||||
|
||||
## Reports published scene-tree geometry around calibrated renderer cameras.
|
||||
## Usage: godot --headless --path . --script res://src/tools/probe_render_camera_occluders.gd --
|
||||
## [--wait 3.0] [--output user://render_camera_occluders/report.json]
|
||||
|
||||
const MANIFEST_PATH := "res://src/tools/render_baseline_manifest.json"
|
||||
const MAX_REPORTED_INTERSECTIONS := 20
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
_run_async.call_deferred()
|
||||
|
||||
|
||||
func _run_async() -> void:
|
||||
var arguments := OS.get_cmdline_user_args()
|
||||
var wait_seconds := float(_argument(arguments, "--wait", "3.0"))
|
||||
var output_path := _argument(arguments, "--output", "user://render_camera_occluders/report.json")
|
||||
var only_filter := _argument(arguments, "--only", "").to_lower()
|
||||
var manifest := _load_json(MANIFEST_PATH)
|
||||
var packed_scene: PackedScene = load(String(manifest.get("scene", "")))
|
||||
if packed_scene == null:
|
||||
push_error("CAMERA_OCCLUDER_PROBE: cannot load streaming scene")
|
||||
quit(1)
|
||||
return
|
||||
var world := packed_scene.instantiate() as Node3D
|
||||
var camera := Camera3D.new()
|
||||
camera.name = "OccluderProbeCamera"
|
||||
camera.current = true
|
||||
world.add_child(camera)
|
||||
world.set("camera_path", NodePath(camera.name))
|
||||
world.set("debug_streaming", false)
|
||||
get_root().add_child(world)
|
||||
await process_frame
|
||||
await process_frame
|
||||
|
||||
var results: Array[Dictionary] = []
|
||||
for checkpoint_variant in manifest.get("checkpoints", []):
|
||||
if not (checkpoint_variant is Dictionary):
|
||||
continue
|
||||
var checkpoint: Dictionary = checkpoint_variant
|
||||
if not checkpoint.has("reference_wow_camera"):
|
||||
continue
|
||||
var checkpoint_name := String(checkpoint.get("name", "checkpoint"))
|
||||
if not only_filter.is_empty() and not checkpoint_name.to_lower().contains(only_filter):
|
||||
continue
|
||||
var camera_position := _vector3(checkpoint.get("camera", []))
|
||||
var target_position := _vector3(checkpoint.get("target", []))
|
||||
camera.global_position = camera_position
|
||||
if world.has_method("_refresh_streaming_targets_at"):
|
||||
world.call("_refresh_streaming_targets_at", camera_position, true)
|
||||
await create_timer(maxf(0.1, wait_seconds)).timeout
|
||||
var geometry_nodes: Array[Node3D] = []
|
||||
_collect_geometry_nodes(world, geometry_nodes)
|
||||
var containing: Array[Dictionary] = []
|
||||
var intersecting: Array[Dictionary] = []
|
||||
for geometry_node in geometry_nodes:
|
||||
var world_aabb := _geometry_world_aabb(geometry_node)
|
||||
if world_aabb.size.is_zero_approx():
|
||||
continue
|
||||
var record := _geometry_record(geometry_node, world_aabb, camera_position)
|
||||
if world_aabb.has_point(camera_position):
|
||||
containing.append(record)
|
||||
var intersection = world_aabb.intersects_segment(camera_position, target_position)
|
||||
if intersection != null:
|
||||
record["intersection_distance"] = camera_position.distance_to(intersection as Vector3)
|
||||
intersecting.append(record)
|
||||
intersecting.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
|
||||
return float(a.intersection_distance) < float(b.intersection_distance))
|
||||
if intersecting.size() > MAX_REPORTED_INTERSECTIONS:
|
||||
intersecting.resize(MAX_REPORTED_INTERSECTIONS)
|
||||
var result := {
|
||||
"name": checkpoint_name,
|
||||
"geometry_node_count": geometry_nodes.size(),
|
||||
"camera_containing_geometry": containing,
|
||||
"segment_intersecting_geometry": intersecting,
|
||||
}
|
||||
results.append(result)
|
||||
print("CAMERA_OCCLUDERS name=%s geometry=%d containing=%d segment=%d" % [
|
||||
checkpoint_name, geometry_nodes.size(), containing.size(), intersecting.size()])
|
||||
|
||||
var report := {
|
||||
"schema_version": 1,
|
||||
"created_utc": Time.get_datetime_string_from_system(true, true),
|
||||
"wait_seconds": wait_seconds,
|
||||
"coverage": "scene_tree_only; RenderingServer RID-only instances are excluded",
|
||||
"results": results,
|
||||
}
|
||||
if not _write_json(output_path, report):
|
||||
quit(1)
|
||||
return
|
||||
world.queue_free()
|
||||
quit(0 if not results.is_empty() else 1)
|
||||
|
||||
|
||||
func _collect_geometry_nodes(node: Node, output: Array[Node3D]) -> void:
|
||||
if node is MeshInstance3D:
|
||||
var mesh_instance := node as MeshInstance3D
|
||||
if mesh_instance.mesh != null and not mesh_instance.name.begins_with("TileLOD"):
|
||||
output.append(mesh_instance)
|
||||
elif node is MultiMeshInstance3D:
|
||||
var multimesh_instance := node as MultiMeshInstance3D
|
||||
if multimesh_instance.multimesh != null:
|
||||
output.append(multimesh_instance)
|
||||
for child in node.get_children():
|
||||
_collect_geometry_nodes(child, output)
|
||||
|
||||
|
||||
func _geometry_world_aabb(node: Node3D) -> AABB:
|
||||
if node is MeshInstance3D:
|
||||
return node.global_transform * (node as MeshInstance3D).mesh.get_aabb()
|
||||
if node is MultiMeshInstance3D:
|
||||
return node.global_transform * (node as MultiMeshInstance3D).multimesh.get_aabb()
|
||||
return AABB()
|
||||
|
||||
|
||||
func _geometry_record(node: Node3D, world_aabb: AABB, camera_position: Vector3) -> Dictionary:
|
||||
var node_path := String(node.get_path())
|
||||
var category := "geometry"
|
||||
if node_path.contains("/M2s/") or node is MultiMeshInstance3D:
|
||||
category = "m2"
|
||||
elif node_path.contains("/WMOs/"):
|
||||
category = "wmo"
|
||||
return {
|
||||
"category": category,
|
||||
"node_path": node_path,
|
||||
"distance_to_center": camera_position.distance_to(world_aabb.get_center()),
|
||||
"aabb_position": _vector3_array(world_aabb.position),
|
||||
"aabb_size": _vector3_array(world_aabb.size),
|
||||
}
|
||||
|
||||
|
||||
func _vector3(value_variant) -> Vector3:
|
||||
if not (value_variant is Array) or value_variant.size() != 3:
|
||||
return Vector3.ZERO
|
||||
return Vector3(float(value_variant[0]), float(value_variant[1]), float(value_variant[2]))
|
||||
|
||||
|
||||
func _vector3_array(value: Vector3) -> Array[float]:
|
||||
return [value.x, value.y, value.z]
|
||||
|
||||
|
||||
func _argument(arguments: PackedStringArray, name: String, default_value: String) -> String:
|
||||
var index := arguments.find(name)
|
||||
if index >= 0 and index + 1 < arguments.size():
|
||||
return arguments[index + 1]
|
||||
return default_value
|
||||
|
||||
|
||||
func _load_json(path: String) -> Dictionary:
|
||||
var file := FileAccess.open(path, FileAccess.READ)
|
||||
if file == null:
|
||||
return {}
|
||||
var parsed = JSON.parse_string(file.get_as_text())
|
||||
return parsed if parsed is Dictionary else {}
|
||||
|
||||
|
||||
func _write_json(path: String, value: Dictionary) -> bool:
|
||||
var absolute_path := ProjectSettings.globalize_path(path)
|
||||
if DirAccess.make_dir_recursive_absolute(absolute_path.get_base_dir()) != OK:
|
||||
return false
|
||||
var file := FileAccess.open(absolute_path, FileAccess.WRITE)
|
||||
if file == null:
|
||||
return false
|
||||
file.store_string(JSON.stringify(value, " "))
|
||||
return true
|
||||
@@ -0,0 +1,250 @@
|
||||
extends SceneTree
|
||||
|
||||
## Measures camera clearance against the active rendered terrain mesh.
|
||||
## Usage: godot --path . --script res://src/tools/probe_render_terrain_height.gd --
|
||||
## [--wait 3.0] [--output user://render_terrain_height/report.json]
|
||||
|
||||
const MANIFEST_PATH := "res://src/tools/render_baseline_manifest.json"
|
||||
const TILE_SIZE := 533.33333
|
||||
const RAY_HEIGHT := 5000.0
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
_run_async.call_deferred()
|
||||
|
||||
|
||||
func _run_async() -> void:
|
||||
var arguments := OS.get_cmdline_user_args()
|
||||
var wait_seconds := float(_argument(arguments, "--wait", "3.0"))
|
||||
var output_path := _argument(arguments, "--output", "user://render_terrain_height/report.json")
|
||||
var only_filter := _argument(arguments, "--only", "").to_lower()
|
||||
var require_all := arguments.has("--require-all")
|
||||
var manifest := _load_json(MANIFEST_PATH)
|
||||
if manifest.is_empty():
|
||||
quit(1)
|
||||
return
|
||||
var packed_scene: PackedScene = load(String(manifest.get("scene", "")))
|
||||
if packed_scene == null:
|
||||
push_error("TERRAIN_HEIGHT_PROBE: cannot load streaming scene")
|
||||
quit(1)
|
||||
return
|
||||
var world := packed_scene.instantiate() as Node3D
|
||||
if world == null:
|
||||
push_error("TERRAIN_HEIGHT_PROBE: streaming scene root is not Node3D")
|
||||
quit(1)
|
||||
return
|
||||
var camera := Camera3D.new()
|
||||
camera.current = true
|
||||
world.add_child(camera)
|
||||
world.set("camera_path", NodePath(camera.name))
|
||||
world.set("debug_streaming", false)
|
||||
get_root().add_child(world)
|
||||
await process_frame
|
||||
await process_frame
|
||||
|
||||
var results: Array[Dictionary] = []
|
||||
for checkpoint_variant in manifest.get("checkpoints", []):
|
||||
if not (checkpoint_variant is Dictionary):
|
||||
continue
|
||||
var checkpoint: Dictionary = checkpoint_variant
|
||||
if not checkpoint.has("reference_wow_camera"):
|
||||
continue
|
||||
if not only_filter.is_empty() and not String(checkpoint.get("name", "")).to_lower().contains(only_filter):
|
||||
continue
|
||||
var camera_position := _vector3(checkpoint.get("camera", []))
|
||||
camera.global_position = camera_position
|
||||
if world.has_method("_refresh_streaming_targets_at"):
|
||||
world.call("_refresh_streaming_targets_at", camera_position, true)
|
||||
await create_timer(maxf(0.1, wait_seconds)).timeout
|
||||
var terrain_sample := _sample_terrain(world, camera_position)
|
||||
terrain_sample["name"] = checkpoint.get("name", "checkpoint")
|
||||
terrain_sample["camera_y"] = camera_position.y
|
||||
if terrain_sample.has("terrain_height"):
|
||||
terrain_sample["camera_clearance"] = camera_position.y - float(terrain_sample.terrain_height)
|
||||
results.append(terrain_sample)
|
||||
|
||||
var report := {
|
||||
"schema_version": 1,
|
||||
"created_utc": Time.get_datetime_string_from_system(true, true),
|
||||
"wait_seconds": wait_seconds,
|
||||
"results": results,
|
||||
}
|
||||
if not _write_json(output_path, report):
|
||||
quit(1)
|
||||
return
|
||||
var sampled_count := 0
|
||||
for result in results:
|
||||
if result.has("terrain_height"):
|
||||
sampled_count += 1
|
||||
print("TERRAIN_HEIGHT name=%s status=%s camera_y=%.3f terrain=%s clearance=%s" % [
|
||||
result.get("name", "checkpoint"),
|
||||
result.get("status", "unknown"),
|
||||
float(result.get("camera_y", 0.0)),
|
||||
str(result.get("terrain_height", "n/a")),
|
||||
str(result.get("camera_clearance", "n/a")),
|
||||
])
|
||||
print("TERRAIN_HEIGHT_PROBE sampled=%d total=%d report=%s" % [sampled_count, results.size(), output_path])
|
||||
world.queue_free()
|
||||
var failed := sampled_count == 0 or (require_all and sampled_count != results.size())
|
||||
quit(1 if failed else 0)
|
||||
|
||||
|
||||
func _sample_terrain(world: Node3D, world_position: Vector3) -> Dictionary:
|
||||
var tile_coordinate := Vector2i(
|
||||
int(floor(world_position.x / TILE_SIZE)),
|
||||
int(floor(world_position.z / TILE_SIZE)))
|
||||
var tile_states: Dictionary = world.get("_tile_states")
|
||||
var highest_terrain_height := -INF
|
||||
var intersected_tile_key := ""
|
||||
var ready_mesh_count := 0
|
||||
for tile_y in range(tile_coordinate.y - 1, tile_coordinate.y + 2):
|
||||
for tile_x in range(tile_coordinate.x - 1, tile_coordinate.x + 2):
|
||||
var tile_key := "%d_%d" % [tile_x, tile_y]
|
||||
if not tile_states.has(tile_key):
|
||||
continue
|
||||
var state: Dictionary = tile_states[tile_key]
|
||||
var terrain_height_variant = _intersect_terrain_state(state, world_position)
|
||||
if terrain_height_variant == null:
|
||||
continue
|
||||
ready_mesh_count += 1
|
||||
var terrain_height := float(terrain_height_variant)
|
||||
if terrain_height > highest_terrain_height:
|
||||
highest_terrain_height = terrain_height
|
||||
intersected_tile_key = tile_key
|
||||
if not is_finite(highest_terrain_height):
|
||||
var missing_result := {
|
||||
"status": "no_intersection" if ready_mesh_count > 0 else "mesh_not_ready",
|
||||
"tile": "%d_%d" % [tile_coordinate.x, tile_coordinate.y],
|
||||
}
|
||||
missing_result.merge(_tile_runtime_diagnostic(world, String(missing_result.tile), world_position), true)
|
||||
if bool(missing_result.get("quality_mesh_present", false)) or bool(missing_result.get("tile_lod_mesh_present", false)):
|
||||
missing_result["status"] = "no_intersection"
|
||||
missing_result.merge(_nearest_terrain_sample(world, world_position), true)
|
||||
if missing_result.get("nearest_sample_distance", null) != null:
|
||||
missing_result["status"] = "sampled_nearby"
|
||||
missing_result["terrain_height"] = float(missing_result.nearest_sample_height)
|
||||
return missing_result
|
||||
return {
|
||||
"status": "sampled",
|
||||
"tile": intersected_tile_key,
|
||||
"terrain_height": highest_terrain_height,
|
||||
}
|
||||
|
||||
|
||||
func _intersect_terrain_state(state: Dictionary, world_position: Vector3):
|
||||
var terrain_mesh: Mesh = state.get("quality_terrain_mesh", null)
|
||||
if terrain_mesh == null:
|
||||
terrain_mesh = state.get("tile_lod_mesh", null)
|
||||
if terrain_mesh == null:
|
||||
return null
|
||||
var triangle_mesh := terrain_mesh.generate_triangle_mesh()
|
||||
if triangle_mesh == null:
|
||||
return null
|
||||
var tile_root := state.get("root", null) as Node3D
|
||||
if tile_root == null:
|
||||
return null
|
||||
var inverse_transform := tile_root.global_transform.affine_inverse()
|
||||
var local_ray_origin := inverse_transform * Vector3(world_position.x, RAY_HEIGHT, world_position.z)
|
||||
var local_ray_direction := inverse_transform.basis * Vector3.DOWN
|
||||
var intersection: Dictionary = triangle_mesh.intersect_ray(local_ray_origin, local_ray_direction)
|
||||
if intersection.is_empty():
|
||||
return null
|
||||
var local_hit: Vector3 = intersection.get("position", Vector3.ZERO)
|
||||
var world_hit := tile_root.global_transform * local_hit
|
||||
return world_hit.y
|
||||
|
||||
|
||||
func _tile_runtime_diagnostic(world: Node3D, tile_key: String, world_position: Vector3) -> Dictionary:
|
||||
var available_tiles: Dictionary = world.get("_available_tiles")
|
||||
var loading_tasks: Dictionary = world.get("_tile_loading_tasks")
|
||||
var tile_states: Dictionary = world.get("_tile_states")
|
||||
var load_queue: Array = world.get("_tile_load_queue")
|
||||
var queued_index := -1
|
||||
for index in load_queue.size():
|
||||
var request: Dictionary = load_queue[index]
|
||||
if String(request.get("key", "")) == tile_key:
|
||||
queued_index = index
|
||||
break
|
||||
var diagnostic := {
|
||||
"available": available_tiles.has(tile_key),
|
||||
"queued_index": queued_index,
|
||||
"loading": loading_tasks.has(tile_key),
|
||||
"state_present": tile_states.has(tile_key),
|
||||
"load_queue_size": load_queue.size(),
|
||||
}
|
||||
if tile_states.has(tile_key):
|
||||
var state: Dictionary = tile_states[tile_key]
|
||||
var quality_mesh: Mesh = state.get("quality_terrain_mesh", null)
|
||||
var tile_lod_mesh: Mesh = state.get("tile_lod_mesh", null)
|
||||
diagnostic["quality_mesh_present"] = quality_mesh != null
|
||||
diagnostic["tile_lod_mesh_present"] = tile_lod_mesh != null
|
||||
diagnostic["quality_source"] = String(state.get("quality_terrain_source", ""))
|
||||
diagnostic["tile_lod"] = int(state.get("tile_lod", -1))
|
||||
var tile_root := state.get("root", null) as Node3D
|
||||
if tile_root != null:
|
||||
var local_position := tile_root.global_transform.affine_inverse() * world_position
|
||||
diagnostic["tile_root_position"] = _vector3_array(tile_root.global_position)
|
||||
diagnostic["probe_local_position"] = _vector3_array(local_position)
|
||||
var diagnostic_mesh := quality_mesh if quality_mesh != null else tile_lod_mesh
|
||||
if diagnostic_mesh != null:
|
||||
var mesh_aabb := diagnostic_mesh.get_aabb()
|
||||
diagnostic["mesh_aabb_position"] = _vector3_array(mesh_aabb.position)
|
||||
diagnostic["mesh_aabb_size"] = _vector3_array(mesh_aabb.size)
|
||||
return diagnostic
|
||||
|
||||
|
||||
func _nearest_terrain_sample(world: Node3D, world_position: Vector3) -> Dictionary:
|
||||
var tile_states: Dictionary = world.get("_tile_states")
|
||||
for radius in [2.0, 5.0, 10.0, 20.0, 40.0]:
|
||||
for offset in [Vector2(radius, 0.0), Vector2(-radius, 0.0), Vector2(0.0, radius), Vector2(0.0, -radius)]:
|
||||
var sample_position := world_position + Vector3(offset.x, 0.0, offset.y)
|
||||
var sample_tile := Vector2i(
|
||||
int(floor(sample_position.x / TILE_SIZE)),
|
||||
int(floor(sample_position.z / TILE_SIZE)))
|
||||
var sample_key := "%d_%d" % [sample_tile.x, sample_tile.y]
|
||||
if not tile_states.has(sample_key):
|
||||
continue
|
||||
var height_variant = _intersect_terrain_state(tile_states[sample_key], sample_position)
|
||||
if height_variant != null:
|
||||
return {
|
||||
"nearest_sample_distance": radius,
|
||||
"nearest_sample_height": float(height_variant),
|
||||
"nearest_sample_tile": sample_key,
|
||||
}
|
||||
return {"nearest_sample_distance": null}
|
||||
|
||||
|
||||
func _vector3_array(value: Vector3) -> Array[float]:
|
||||
return [value.x, value.y, value.z]
|
||||
|
||||
|
||||
func _vector3(value_variant) -> Vector3:
|
||||
if not (value_variant is Array) or value_variant.size() != 3:
|
||||
return Vector3.ZERO
|
||||
return Vector3(float(value_variant[0]), float(value_variant[1]), float(value_variant[2]))
|
||||
|
||||
|
||||
func _argument(arguments: PackedStringArray, name: String, default_value: String) -> String:
|
||||
var index := arguments.find(name)
|
||||
if index >= 0 and index + 1 < arguments.size():
|
||||
return arguments[index + 1]
|
||||
return default_value
|
||||
|
||||
|
||||
func _load_json(path: String) -> Dictionary:
|
||||
var file := FileAccess.open(path, FileAccess.READ)
|
||||
if file == null:
|
||||
return {}
|
||||
var parsed = JSON.parse_string(file.get_as_text())
|
||||
return parsed if parsed is Dictionary else {}
|
||||
|
||||
|
||||
func _write_json(path: String, value: Dictionary) -> bool:
|
||||
var absolute_path := ProjectSettings.globalize_path(path)
|
||||
if DirAccess.make_dir_recursive_absolute(absolute_path.get_base_dir()) != OK:
|
||||
return false
|
||||
var file := FileAccess.open(absolute_path, FileAccess.WRITE)
|
||||
if file == null:
|
||||
return false
|
||||
file.store_string(JSON.stringify(value, " "))
|
||||
return true
|
||||
@@ -5,6 +5,7 @@
|
||||
"map": "Azeroth",
|
||||
"quality_preset": "High",
|
||||
"viewport": [1280, 900],
|
||||
"fidelity_comparison_viewport": [2560, 1440],
|
||||
"camera_fov": 62.0,
|
||||
"default_wait_seconds": 8.0,
|
||||
"default_measure_seconds": 3.0,
|
||||
@@ -14,7 +15,10 @@
|
||||
"max_hitch_ms_max_regression_percent": 10.0,
|
||||
"load_time_ms_max_regression_percent": 10.0,
|
||||
"memory_bytes_max_regression_percent": 10.0,
|
||||
"visual_diff": "perceptual comparison required; exact PNG hashes are evidence, not an animated-scene pass criterion"
|
||||
"visual_mean_perceptual_error_max": 0.015,
|
||||
"visual_changed_pixel_ratio_max": 0.01,
|
||||
"visual_pixel_error_threshold": 0.04,
|
||||
"visual_diff": "automated thresholds require human approval; exact PNG hashes are evidence, not an animated-scene pass criterion"
|
||||
},
|
||||
"required_coverage": [
|
||||
"terrain",
|
||||
@@ -39,6 +43,7 @@
|
||||
"name": "elwynn_terrain_overview",
|
||||
"coverage": ["terrain"],
|
||||
"camera": [16680.0, 180.0, 26220.0],
|
||||
"reference_wow_camera": [-9153.334, 386.666, 180.0],
|
||||
"target": [16800.0, 62.0, 26400.0],
|
||||
"player": [16800.0, 58.0, 26400.0],
|
||||
"time_hours": 13.0
|
||||
@@ -47,6 +52,7 @@
|
||||
"name": "elwynn_adt_boundary",
|
||||
"coverage": ["adt_boundary"],
|
||||
"camera": [17020.0, 105.0, 26590.0],
|
||||
"reference_wow_camera": [-9523.334, 46.666, 105.0],
|
||||
"target": [17066.666, 62.0, 26666.666],
|
||||
"player": [17060.0, 58.0, 26660.0],
|
||||
"time_hours": 13.0
|
||||
@@ -54,7 +60,8 @@
|
||||
{
|
||||
"name": "goldshire_dense_m2",
|
||||
"coverage": ["dense_m2"],
|
||||
"camera": [16835.0, 135.0, 26435.0],
|
||||
"camera": [16956.666, 150.0, 26466.666],
|
||||
"reference_wow_camera": [-9400.0, 110.0, 150.0],
|
||||
"target": [17015.0, 62.0, 26525.0],
|
||||
"player": [17015.0, 58.0, 26525.0],
|
||||
"time_hours": 13.0
|
||||
@@ -62,7 +69,8 @@
|
||||
{
|
||||
"name": "goldshire_inn_large_wmo",
|
||||
"coverage": ["large_wmo"],
|
||||
"camera": [16942.0, 82.0, 26503.0],
|
||||
"camera": [17013.666, 72.0, 26451.666],
|
||||
"reference_wow_camera": [-9385.0, 53.0, 72.0],
|
||||
"target": [17042.27, 66.0, 26530.91],
|
||||
"player": [17042.27, 58.0, 26530.91],
|
||||
"time_hours": 13.0
|
||||
@@ -70,7 +78,8 @@
|
||||
{
|
||||
"name": "elwynn_waterfall_liquid",
|
||||
"coverage": ["liquid"],
|
||||
"camera": [16445.0, 125.0, 26295.0],
|
||||
"camera": [16481.666, 190.0, 26366.666],
|
||||
"reference_wow_camera": [-9300.0, 585.0, 190.0],
|
||||
"target": [16518.84, 68.0, 26427.27],
|
||||
"player": [16518.84, 55.0, 26427.27],
|
||||
"time_hours": 13.0
|
||||
|
||||
@@ -20,6 +20,8 @@ func _initialize() -> void:
|
||||
_expect(int(manifest.get("schema_version", 0)) == 1, "manifest schema must be version 1", failures)
|
||||
_expect(String(manifest.get("profile", "")) == "Blizzlike335", "manifest profile must be Blizzlike335", failures)
|
||||
_expect(ResourceLoader.exists(String(manifest.get("scene", ""))), "streaming scene must exist", failures)
|
||||
var fidelity_viewport: Array = manifest.get("fidelity_comparison_viewport", [])
|
||||
_expect(fidelity_viewport.size() == 2 and int(fidelity_viewport[0]) > 0 and int(fidelity_viewport[1]) > 0, "fidelity comparison viewport must contain positive width and height", failures)
|
||||
|
||||
var covered := {}
|
||||
var names := {}
|
||||
@@ -42,6 +44,11 @@ func _initialize() -> void:
|
||||
var required := String(required_variant)
|
||||
_expect(covered.has(required), "missing checkpoint coverage: %s" % required, failures)
|
||||
|
||||
var comparison_budgets: Dictionary = manifest.get("comparison_budgets", {})
|
||||
for threshold_name in ["visual_mean_perceptual_error_max", "visual_changed_pixel_ratio_max", "visual_pixel_error_threshold"]:
|
||||
var threshold := float(comparison_budgets.get(threshold_name, -1.0))
|
||||
_expect(threshold >= 0.0 and threshold <= 1.0, "%s must be between 0 and 1" % threshold_name, failures)
|
||||
|
||||
var contract: Dictionary = manifest.get("cache_contract", {})
|
||||
var actual_versions := {
|
||||
"baked_terrain": STREAMING_LOADER.REQUIRED_BAKED_TILE_FORMAT_VERSION,
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
extends SceneTree
|
||||
|
||||
## Verifies observed build 12340 camera coordinates against the current renderer
|
||||
## WoW X/Y/Z to Godot X/Y/Z convention. This diagnostic does not define the
|
||||
## future M01 production CoordinateMapper contract.
|
||||
|
||||
const MANIFEST_PATH := "res://src/tools/render_baseline_manifest.json"
|
||||
const WOW_WORLD_CENTER := 17066.666
|
||||
const MAXIMUM_POSITION_ERROR := 0.002
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
var manifest := _load_manifest()
|
||||
if manifest.is_empty():
|
||||
quit(1)
|
||||
return
|
||||
|
||||
var failures: Array[String] = []
|
||||
var calibrated_count := 0
|
||||
var maximum_round_trip_error := 0.0
|
||||
for checkpoint_variant in manifest.get("checkpoints", []):
|
||||
if not (checkpoint_variant is Dictionary):
|
||||
continue
|
||||
var checkpoint: Dictionary = checkpoint_variant
|
||||
var reference_wow_camera_variant = checkpoint.get("reference_wow_camera", null)
|
||||
if not (reference_wow_camera_variant is Array):
|
||||
continue
|
||||
var reference_wow_camera: Array = reference_wow_camera_variant
|
||||
var checkpoint_name := String(checkpoint.get("name", "checkpoint"))
|
||||
if reference_wow_camera.size() != 3:
|
||||
failures.append("%s reference_wow_camera must contain three coordinates" % checkpoint_name)
|
||||
continue
|
||||
var expected_godot_camera := _array_to_vector3(checkpoint.get("camera", []))
|
||||
var reference_wow_position := _array_to_vector3(reference_wow_camera)
|
||||
var mapped_godot_position := _wow_to_godot(reference_wow_position)
|
||||
var round_trip_wow_position := _godot_to_wow(mapped_godot_position)
|
||||
var mapping_error := mapped_godot_position.distance_to(expected_godot_camera)
|
||||
var round_trip_error := round_trip_wow_position.distance_to(reference_wow_position)
|
||||
maximum_round_trip_error = maxf(maximum_round_trip_error, maxf(mapping_error, round_trip_error))
|
||||
if mapping_error > MAXIMUM_POSITION_ERROR:
|
||||
failures.append("%s maps %.6f units away from manifest camera" % [checkpoint_name, mapping_error])
|
||||
if round_trip_error > MAXIMUM_POSITION_ERROR:
|
||||
failures.append("%s round-trip error is %.6f" % [checkpoint_name, round_trip_error])
|
||||
calibrated_count += 1
|
||||
|
||||
if calibrated_count < 5:
|
||||
failures.append("expected at least five build 12340 camera calibrations, found %d" % calibrated_count)
|
||||
if not failures.is_empty():
|
||||
for failure in failures:
|
||||
push_error("RENDER_COORDINATE_CALIBRATION: %s" % failure)
|
||||
quit(1)
|
||||
return
|
||||
print("RENDER_COORDINATE_CALIBRATION PASS points=%d maximum_error=%.6f" % [calibrated_count, maximum_round_trip_error])
|
||||
quit(0)
|
||||
|
||||
|
||||
func _wow_to_godot(wow_position: Vector3) -> Vector3:
|
||||
return Vector3(
|
||||
WOW_WORLD_CENTER - wow_position.y,
|
||||
wow_position.z,
|
||||
WOW_WORLD_CENTER - wow_position.x
|
||||
)
|
||||
|
||||
|
||||
func _godot_to_wow(godot_position: Vector3) -> Vector3:
|
||||
return Vector3(
|
||||
WOW_WORLD_CENTER - godot_position.z,
|
||||
WOW_WORLD_CENTER - godot_position.x,
|
||||
godot_position.y
|
||||
)
|
||||
|
||||
|
||||
func _array_to_vector3(value_variant) -> Vector3:
|
||||
if not (value_variant is Array) or value_variant.size() != 3:
|
||||
return Vector3.ZERO
|
||||
return Vector3(float(value_variant[0]), float(value_variant[1]), float(value_variant[2]))
|
||||
|
||||
|
||||
func _load_manifest() -> Dictionary:
|
||||
var manifest_file := FileAccess.open(MANIFEST_PATH, FileAccess.READ)
|
||||
if manifest_file == null:
|
||||
push_error("Cannot open renderer baseline manifest: %s" % MANIFEST_PATH)
|
||||
return {}
|
||||
var parsed = JSON.parse_string(manifest_file.get_as_text())
|
||||
if not (parsed is Dictionary):
|
||||
push_error("Renderer baseline manifest is not a JSON object")
|
||||
return {}
|
||||
return parsed
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
Последовательно закрыть всю матрицу `docs/CLIENT_FEATURES.md` до подтверждённой совместимости.
|
||||
|
||||
Работа выполняется completeness waves C1–C8 из [`DEVELOPMENT_ROADMAP.md`](DEVELOPMENT_ROADMAP.md) с обязательным чтением gameplay, UI/audio, server и quality subsystem plans. Этот target не разрешает массово реализовывать все пункты одновременно.
|
||||
|
||||
## Required order
|
||||
|
||||
- [ ] Combat, spells, casting, cooldowns, auras, death/resurrection.
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
# OpenWC Full Development Roadmap
|
||||
|
||||
## Назначение
|
||||
|
||||
Этот документ — полная инженерная карта разработки OpenWC. Статусы и последовательность исполняемых milestones остаются в [`README.md`](README.md); детальные subsystem plans находятся в [`roadmap/`](roadmap/). При расхождении текущий target определяет, что делать сейчас, а этот roadmap — как решение должно развиваться целиком.
|
||||
|
||||
Godot-specific implementation rules обязательны из [`../docs/GODOT_BEST_PRACTICES.md`](../docs/GODOT_BEST_PRACTICES.md).
|
||||
|
||||
## Итоговый продукт
|
||||
|
||||
OpenWC должен предоставить четыре согласованных режима:
|
||||
|
||||
1. `Blizzlike335` — клиент WoW 3.3.5a build 12340 с максимально точным наблюдаемым поведением.
|
||||
2. `Enhanced` — opt-in графические, UX и accessibility улучшения, не меняющие wire protocol и server authority без отдельного profile.
|
||||
3. `AuthoringStudio` — инструменты мира, контента, БД и playtest внутри Godot Editor.
|
||||
4. `HeadlessTools` — import, validation, bake, packet replay, content build и CI без GUI.
|
||||
|
||||
Один кодовый путь должен обслуживать runtime и editor preview через публичные contracts. Generated preview nodes, DB records и caches не являются источником истины.
|
||||
|
||||
## Текущее положение
|
||||
|
||||
- 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.
|
||||
- Renderer baseline M00 создан; до `DONE` не хватает paired comparison с оригинальным клиентом и закрытия известных diagnostic gaps.
|
||||
- Gameplay domain, network protocol, production UI/Lua, audio orchestration и server adapters в основном предстоит реализовать.
|
||||
- Editor plugin пока решает extraction/preview задачи, но не является полноценной authoring platform.
|
||||
|
||||
## Инженерные программы
|
||||
|
||||
| Код | Программа | Детальный план |
|
||||
|---|---|---|
|
||||
| FND | Foundation, application и data | [01-foundation-and-data.md](roadmap/01-foundation-and-data.md) |
|
||||
| RND | Renderer и graphics | [02-rendering-and-graphics.md](roadmap/02-rendering-and-graphics.md) |
|
||||
| NET | Protocol, session и synchronization | [03-network-and-session.md](roadmap/03-network-and-session.md) |
|
||||
| GMP | Gameplay systems | [04-gameplay-systems.md](roadmap/04-gameplay-systems.md) |
|
||||
| UIA | UI, Lua, addons, audio | [05-ui-lua-audio.md](roadmap/05-ui-lua-audio.md) |
|
||||
| EDT | Godot Editor и content authoring | [06-editor-and-content-tools.md](roadmap/06-editor-and-content-tools.md) |
|
||||
| SRV | TrinityCore/AzerothCore integration | [07-server-emulator-integration.md](roadmap/07-server-emulator-integration.md) |
|
||||
| QAR | Fidelity, tests, CI и releases | [08-quality-and-release.md](roadmap/08-quality-and-release.md) |
|
||||
|
||||
## Dependency graph
|
||||
|
||||
```text
|
||||
M00 renderer baseline
|
||||
└─ M01 coordinates/contracts
|
||||
└─ M02 input/movement/camera/presentation split
|
||||
└─ M03 renderer facade
|
||||
├─ M04 editor shell → M05 content → M06 server adapters → M07 world editor
|
||||
└─ M08 network → M09 gameplay domain → M10 playable client
|
||||
M07 + M10 → M11 quest end-to-end
|
||||
└─ M12 completeness waves
|
||||
└─ M13 dungeon authoring
|
||||
```
|
||||
|
||||
M12 не является одним большим этапом. Он выполняется последовательными completeness waves:
|
||||
|
||||
1. `C1 Core loop` — combat, spells, auras, death, loot, inventory, quests.
|
||||
2. `C2 Character` — creation/customization, equipment, animation, pets, mounts.
|
||||
3. `C3 World` — map/minimap, exploration, taxi, transports, weather, audio.
|
||||
4. `C4 Services` — gossip, vendors, trainers, bank, mail, auction, professions.
|
||||
5. `C5 Social` — chat, friends, ignore, party, raid, guild, calendar.
|
||||
6. `C6 Instances/PvP` — lockouts, LFG, vehicles, battlegrounds, arenas, outdoor PvP.
|
||||
7. `C7 UI/addons` — FrameXML, Lua 5.1.1, secure execution, default UI, compatibility tiers.
|
||||
8. `C8 Fidelity/polish` — complete render/material/audio coverage, recovery, accessibility, localization и performance.
|
||||
|
||||
Каждая wave должна завершаться работающим gameplay scenario, а не количеством реализованных opcodes или таблиц.
|
||||
|
||||
## Milestone delivery matrix
|
||||
|
||||
| Milestone | Основные work packages | Обязательный результат |
|
||||
|---|---|---|
|
||||
| M00 | RND baseline, QAR metrics/fidelity lab | Воспроизводимые checkpoints и gaps |
|
||||
| M01 | FND typed coordinates/IDs/contracts | Server/ADT/Godot round-trip и StreamingFocus |
|
||||
| M02 | FND composition, GMP movement, RND presenters | Player split без visual/control regression |
|
||||
| M03 | RND facade/planner/scheduler, FND jobs | Renderer доступен через API и сохраняет budgets |
|
||||
| M04 | EDT plugin shell, QAR editor tests | Reload-safe workspace, commands, undo/recovery |
|
||||
| M05 | FND content schemas/cache, EDT project | Deterministic Content Project build/migration |
|
||||
| M06 | SRV environments/snapshots/adapters | Two-core read/diff/dry-run round-trip |
|
||||
| M07 | EDT world editing/navigation/deploy | NPC placement на dev-core без ручного SQL |
|
||||
| M08 | NET transport/auth/protocol/entities | Headless auth → world и deterministic replay |
|
||||
| M09 | GMP world state/movement/targeting | Packet replay → deterministic gameplay snapshot |
|
||||
| M10 | FND app lifecycle, RND/UIA presentation | Игрок входит, видит entities и перемещается |
|
||||
| M11 | GMP quests, EDT quest tools, SRV deploy, UIA quest UI | Author → deploy → complete quest своим клиентом |
|
||||
| M12 C1–C8 | NET/GMP/RND/UIA completeness + QAR | Feature matrix `Compatible/Verified` по waves |
|
||||
| M13 | EDT dungeon/encounter, SRV modules, GMP instances | Custom dungeon group playtest end-to-end |
|
||||
|
||||
## Recommended implementation rhythm
|
||||
|
||||
Каждая feature внутри milestone проходит один и тот же цикл:
|
||||
|
||||
1. `Research` — original behavior, core expectations, готовые tools/references.
|
||||
2. `Contract` — owner, types, commands/events, capabilities и error states.
|
||||
3. `Fixture` — golden input/output или reproducible live scenario до production code.
|
||||
4. `Pure implementation` — parser/reducer/validator без Node where possible.
|
||||
5. `Adapter` — network, renderer, UI, Editor или server boundary.
|
||||
6. `Vertical integration` — минимальный observable player/author outcome.
|
||||
7. `Failure/recovery` — malformed, cancel, disconnect, rollback, missing asset.
|
||||
8. `Fidelity/performance` — comparison evidence и budgets.
|
||||
9. `Documentation` — coverage status, known gaps, tooling decision и target evidence.
|
||||
|
||||
## Cross-cutting contracts
|
||||
|
||||
- `BuildProfile` — `Blizzlike335`, `Enhanced`, authoring и test capabilities.
|
||||
- `CoordinateMapper` — единственное преобразование WoW/Godot/ADT/server coordinates.
|
||||
- `AssetRepository` — original assets, overlays, caches и provenance.
|
||||
- `ProtocolProfile` — build, opcodes, update fields, codecs и server capabilities.
|
||||
- `WorldState` — authoritative client-side projection без Node dependencies.
|
||||
- `WorldRenderFacade` — presentation boundary.
|
||||
- `ViewModel/Intent` — единственная граница UI с gameplay.
|
||||
- `LuaRuntime` — заменяемый Lua backend.
|
||||
- `ContentProject` — stable IDs, schemas, dependencies и user-authored source.
|
||||
- `ServerSchemaAdapter` — canonical content ↔ core schema/change set.
|
||||
- `JobScheduler` — bounded background work, cancellation, progress и main-thread finalize.
|
||||
- `Diagnostic` — code, severity, location, context, suggested fix и correlation ID.
|
||||
|
||||
## Архитектурные gates
|
||||
|
||||
Перед переходом между крупными фазами обязательны:
|
||||
|
||||
- `Architecture gate`: запрещённые зависимости отсутствуют.
|
||||
- `Fidelity gate`: есть comparison evidence, а gaps зарегистрированы.
|
||||
- `Data gate`: schema/version/migration и deterministic build проверены.
|
||||
- `Performance gate`: budgets измерены на cold/warm и stress scenarios.
|
||||
- `Recovery gate`: failure/reconnect/cancel/rollback проверены.
|
||||
- `Authoring gate`: изменения можно создать, проверить, diff, undo и воспроизвести.
|
||||
- `Server gate`: обе схемы не предполагаются одинаковыми, capabilities обнаружены явно.
|
||||
|
||||
## Правило улучшений
|
||||
|
||||
Современное улучшение допускается, если:
|
||||
|
||||
1. исходное поведение `Blizzlike335` уже измерено или gap отмечен;
|
||||
2. улучшение включается отдельным setting/profile;
|
||||
3. оно не меняет protocol/server authority скрыто;
|
||||
4. есть before/after visual/performance/accessibility evidence;
|
||||
5. Content Project указывает required capability и fallback.
|
||||
|
||||
Примеры допустимых opt-in улучшений: modern shadows, higher terrain texture resolution, anisotropy, enhanced water, longer draw distance, FSR/upscaling, color-blind palettes, scalable UI, richer editor diagnostics. Новые gameplay rules требуют server module/profile, а не только client toggle.
|
||||
|
||||
## Definition of complete client
|
||||
|
||||
Клиент считается полным не тогда, когда перечислены все packet handlers, а когда:
|
||||
|
||||
- все строки [`../docs/CLIENT_FEATURES.md`](../docs/CLIENT_FEATURES.md) имеют evidence-backed статус;
|
||||
- основные сценарии оригинального клиента воспроизводятся end-to-end;
|
||||
- default UI и выбранные addons проходят compatibility tiers;
|
||||
- supported TrinityCore/AzerothCore profiles проходят contract matrix;
|
||||
- renderer, audio, input, localization и recovery имеют утверждённые gaps/budgets;
|
||||
- AuthoringStudio может создать, проверить, развернуть и протестировать новый quest/location/dungeon package;
|
||||
- release не содержит proprietary assets и требует легально полученную установку клиента.
|
||||
@@ -4,12 +4,18 @@
|
||||
|
||||
Этот каталог задаёт обязательную последовательность реализации. Архитектура описана в `docs/`, фактическое состояние renderer — в `RENDER.md`, а здесь находятся исполняемые milestones.
|
||||
|
||||
Полная инженерная декомпозиция всех подсистем находится в [`DEVELOPMENT_ROADMAP.md`](DEVELOPMENT_ROADMAP.md). Перед работой над milestone агент читает соответствующий subsystem plan из `targets/roadmap/`, но статус меняет только в исполняемом target-файле M00–M13.
|
||||
|
||||
Параллельная командная работа выполняется по [`../docs/TEAM_WORKFLOW.md`](../docs/TEAM_WORKFLOW.md): отдельные worktrees, claims, exclusive paths, contract-first и интегратор milestone.
|
||||
|
||||
## Current target
|
||||
|
||||
`M00` — [00-render-baseline.md](00-render-baseline.md)
|
||||
|
||||
Одновременно `ACTIVE` может быть только одна цель. Следующая цель становится `ACTIVE` после появления валидной `OPENWC_TARGET_DONE` метки у предыдущей.
|
||||
|
||||
Статусные метки применяются только к исполняемым файлам `targets/[0-9][0-9]-*.md`. `DEVELOPMENT_ROADMAP.md` и `targets/roadmap/` являются нормативной декомпозицией, но не создают параллельную систему статусов.
|
||||
|
||||
## Definition of fidelity 1:1
|
||||
|
||||
`1:1` означает совпадение наблюдаемого поведения с оригинальным клиентом WoW 3.3.5a build 12340 для выбранного сценария:
|
||||
@@ -42,6 +48,8 @@
|
||||
| M12 | [WoW 3.3.5a completeness](12-client-completeness.md) | M11 | OPEN |
|
||||
| M13 | [Dungeon authoring](13-dungeon-authoring.md) | M12 foundation | OPEN |
|
||||
|
||||
M12 выполняется восемью completeness waves, определёнными в [`DEVELOPMENT_ROADMAP.md`](DEVELOPMENT_ROADMAP.md), а не одной массовой реализацией.
|
||||
|
||||
M07 и M08 могут выполняться параллельными командами только после M06/M03 соответственно и только если пользователь явно разрешил параллельную работу. Их результаты сходятся в M11.
|
||||
|
||||
`recast-rs` запланирован как optional navigation backend в M05, исследовательский CLI spike и world validation в M07, затем полноценная dungeon navigation validation в M13. Он не блокирует M00–M04 и не считается автоматически совместимым с server `mmaps`.
|
||||
@@ -59,6 +67,9 @@ Wowser зарегистрирован как независимый MIT referenc
|
||||
- renderer checkpoints при изменении рендера;
|
||||
- отсутствие новых нарушений dependency rules;
|
||||
- обновлённые документы и diagnostics;
|
||||
- выполнен documentation gate: inline public API, module spec, inputs/outputs и обязательные diagrams;
|
||||
- выполнен coding gate: явные имена, KISS, отсутствие premature abstraction и необоснованной optimization;
|
||||
- проходят `tools/verify_coordination.ps1` и `tools/verify_documentation.ps1`;
|
||||
- воспроизводимость без proprietary данных в Git.
|
||||
|
||||
## Evidence format
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
# FND — Foundation, Application and Data
|
||||
|
||||
## Цель
|
||||
|
||||
Создать стабильную платформу, на которой renderer, client runtime и Godot Editor могут развиваться независимо, используя одинаковые типы и build artifacts.
|
||||
|
||||
## FND-1. Project profiles и configuration
|
||||
|
||||
- Ввести `BuildProfile` и `CapabilitySet`.
|
||||
- Отделить user settings, developer settings, server connection profiles и content project settings.
|
||||
- Secrets хранить вне Git/Resource; поддержать environment/OS credential provider.
|
||||
- Настройки иметь schema version, defaults, validation, migration и reset-by-section.
|
||||
- `Blizzlike335` сделать неизменяемым базовым профилем; enhanced settings наследуют его явно.
|
||||
|
||||
## FND-2. Application lifecycle
|
||||
|
||||
- Реализовать state machine `Boot → DataCheck → Login → Realm → Character → Loading → InWorld → Disconnect/Recovery`.
|
||||
- Editor, sandbox и headless modes запускать разными composition roots.
|
||||
- Dependency injection выполнять composition layer; не использовать scene lookup как service locator.
|
||||
- Autoload ограничить действительно process-wide lifecycle services. Gameplay, renderer и editor sessions должны инстанцироваться и освобождаться явно.
|
||||
- Каждое состояние иметь cancellation, timeout, diagnostics и deterministic cleanup.
|
||||
|
||||
## FND-3. Domain primitives
|
||||
|
||||
- Typed IDs: content, server entry, entity, GUID, map, area, spell, item, quest, display и sound.
|
||||
- Typed coordinates и orientation; запретить `Vector3` как межслойный wire/domain contract.
|
||||
- Value objects для locale, money, duration, percentage, level, power type и flags.
|
||||
- Commands/events/read models не наследуют Node/Resource.
|
||||
- Serialization version и unknown-field policy определить до сохранения snapshots/replays.
|
||||
|
||||
## FND-4. Asset repository
|
||||
|
||||
- Описать archive chain/locale/patch precedence для 3.3.5a.
|
||||
- Отделить `original source → parsed canonical data → imported Godot resource → runtime cache`.
|
||||
- Ввести provenance: source archive/path/hash, parser version, import profile и dependency hashes.
|
||||
- Не изменять extracted upstream files; custom content подключать ordered overlays.
|
||||
- Dedup textures/materials/meshes по canonical key, а не по случайной Node lifetime.
|
||||
- Missing/corrupt asset policy: placeholder только в dev profile, structured error в fidelity runs.
|
||||
|
||||
## FND-5. Binary formats и typed tables
|
||||
|
||||
- MPQ: archive ordering, locale, listfile absence, encrypted/compressed entries.
|
||||
- BLP: paletted/DXT, alpha depths, mip chains, color space и wrap/filter metadata.
|
||||
- DBC: schema descriptors, localized strings, relationships, hot reload только в authoring mode.
|
||||
- WDT/WDL/ADT: tile existence, terrain, holes, layers, placements, liquids, distant data.
|
||||
- M2/SKIN/WMO: versioned structs, bounds, batches, materials, animation, attachments, portals.
|
||||
- Audio/interface assets: MP3/WAV, fonts, XML, Lua, TOC и SavedVariables formats.
|
||||
- Parser API возвращает typed result + diagnostics; partial parse не маскируется как success.
|
||||
- Differential tests сравнивают текущие parsers с независимыми implementations (`warcraft-rs`, `wow_dbc`, WowDBDefs-driven schemas) на одинаковых fixtures; reference disagreement регистрируется, а не выбирается по большинству.
|
||||
|
||||
## FND-6. Cache and build pipeline
|
||||
|
||||
- Единый content-addressed cache manifest для terrain/M2/WMO/characters/audio/navigation/UI.
|
||||
- Atomic staging → verify → publish; crash не оставляет валидно выглядящий partial artifact.
|
||||
- Cache version включает schema, parser, shader/material contract и profile.
|
||||
- Headless commands используют те же services, что Editor actions.
|
||||
- Добавить dependency graph и incremental rebuild по hash.
|
||||
- Папки generated/cache исключить из Git и при необходимости из Godot import через `.gdignore`.
|
||||
|
||||
## FND-7. Native boundary
|
||||
|
||||
- GDExtension использовать для binary parsing, compression/crypto, large buffer transforms и измеренных hot paths.
|
||||
- GDScript использовать для orchestration, Editor UI и presentation composition.
|
||||
- Перед добавлением Rust/C/C++ dependency фиксировать ABI boundary и ownership.
|
||||
- Через native boundary передавать packed arrays/immutable result objects, избегать вызова на каждый vertex/instance.
|
||||
- Версию Godot/GDExtension API закрепить; обновление проходит compatibility matrix.
|
||||
|
||||
## FND-8. Jobs and concurrency
|
||||
|
||||
- Общий `JobScheduler`: priority, bounded capacity, cancellation, progress, correlation ID.
|
||||
- Pure CPU work выполняется workers; scene tree и GPU resources финализируются main thread budget.
|
||||
- Один resource не модифицируется конкурентно; shared mutable collections защищаются.
|
||||
- Каждый submitted worker task дожидается completion/cleanup.
|
||||
- Session/world unload отменяет jobs и освобождает RIDs/resources детерминированно.
|
||||
|
||||
## Deliverables
|
||||
|
||||
- Foundation package без renderer/network dependencies.
|
||||
- Headless data probe и cache inspector.
|
||||
- Coordinate/parser/cache fixtures.
|
||||
- Architecture dependency checker.
|
||||
- Документированные memory/thread ownership rules.
|
||||
|
||||
## Gate
|
||||
|
||||
Одинаковые входы дают одинаковые manifests; application modes собираются разными roots; domain тестируется без SceneTree; cancellation и shutdown не оставляют jobs/RIDs.
|
||||
@@ -0,0 +1,102 @@
|
||||
# RND — Rendering and Graphics
|
||||
|
||||
## Цель
|
||||
|
||||
Воспроизвести визуальные правила WoW 3.3.5a и дать opt-in улучшения, сохранив frame pacing на больших мирах Godot.
|
||||
|
||||
## RND-1. Renderer architecture
|
||||
|
||||
- `WorldRenderFacade` принимает focus, environment snapshot и entity presentation commands.
|
||||
- `StreamingTargetPlanner` вычисляет desired state без Node creation.
|
||||
- `RenderBudgetScheduler` ограничивает main-thread finalize по типам операций.
|
||||
- Terrain, M2, WMO, liquid, sky, character, effects и debug overlays — отдельные services.
|
||||
- RenderingServer/RIDs применяются только там, где profiler доказал overhead scene tree.
|
||||
- Ownership каждого RID и cache entry документируется; world unload проверяет leaks.
|
||||
|
||||
## RND-2. World streaming
|
||||
|
||||
- WDT-driven tile catalog, priority по position/direction/velocity/camera frustum.
|
||||
- Prewarm, retain и hysteresis исключают churn на ADT boundaries.
|
||||
- Separate budgets для I/O, parse, mesh/material load, main-thread attach и eviction.
|
||||
- Cold/warm cache scenarios, teleport, rapid flight и camera rotation stress.
|
||||
- Cancellation stale jobs при teleport/map change.
|
||||
- Streaming focus не обязан быть Camera3D: player, spectator, editor viewport, capture tool.
|
||||
|
||||
## RND-3. Terrain
|
||||
|
||||
- Точная 145-vertex MCNK topology, holes, normals и coordinate seams.
|
||||
- Texture layer order, MCAL alpha variants, animation flags, repeat и mip behavior.
|
||||
- Control-splat/array implementation как Godot optimization с fidelity tests.
|
||||
- WDL/distant terrain и hierarchical LOD.
|
||||
- Terrain collision/query representation отдельно от visual mesh.
|
||||
- Editing path: sculpt, smooth, flatten, holes, texture paint, seam repair и rebake.
|
||||
|
||||
## RND-4. M2
|
||||
|
||||
- Static geometry, SKIN batches, blend modes, flags, UV transforms и texture lookup.
|
||||
- Native pivot-based skeletal animation, bone palettes, global sequences и animation lookup.
|
||||
- Billboards, attachments, key bones, events, cameras и bounding volumes.
|
||||
- Particles, ribbons, texture animation и material combiners.
|
||||
- Static doodads группировать spatial MultiMesh cells; animated/interactive entities не смешивать со static batching.
|
||||
- MultiMesh buffer updates выполнять батчем; cell size выбирать по culling, а не только draw-call minimum.
|
||||
|
||||
## RND-5. WMO
|
||||
|
||||
- Root/group materials, batches, vertex colors, multiple UV sets и doodad sets.
|
||||
- Portals/rooms, indoor/outdoor, BSP/collision, fog и group visibility.
|
||||
- MLIQ, skybox, lights и ambient color semantics.
|
||||
- Lightweight render cache для больших WMO и incremental group attach.
|
||||
- Editor tools для group/portal/doodad validation.
|
||||
|
||||
## RND-6. Characters and creatures
|
||||
|
||||
- Race/gender/customization from DBC.
|
||||
- Geosets, skin regions, hair/facial features и composited textures.
|
||||
- Equipment component models/textures, attachment points, sheath, enchant visuals.
|
||||
- Animation state resolver: locomotion, combat, casting, emotes, death, swim, mount, vehicle.
|
||||
- CreatureDisplayInfo/Extra/ModelData, NPC equipment, pets и shapeshift.
|
||||
- Character preview uses same presenter as world runtime.
|
||||
|
||||
## RND-7. Liquids
|
||||
|
||||
- MH2O/MCLQ layers, existence masks, heights, depth и liquid type.
|
||||
- Отдельные profiles water/ocean/magma/slime.
|
||||
- Blizzlike material: texture animation, alpha, lighting и fog behavior.
|
||||
- Enhanced material: normals, reflections, depth/shore blending — opt-in.
|
||||
- Liquid collision/swim state принадлежит gameplay/physics query, не shader.
|
||||
|
||||
## RND-8. Environment
|
||||
|
||||
- DBC-driven Light/LightParams/LightIntBand/LightFloatBand interpolation.
|
||||
- Server time, day/night, zone/subzone transitions.
|
||||
- Skyboxes, fog, ambient/directional light, weather visual state.
|
||||
- Blizzlike cheap shadow policy; enhanced selective character/object shadows отдельно.
|
||||
- Camera far plane, fog и streaming distances согласованы профилем.
|
||||
|
||||
## RND-9. Effects and presentation
|
||||
|
||||
- SpellVisual/SpellVisualKit-driven cast/impact/aura effects.
|
||||
- Projectiles, decals, dynamic objects, combat text и selection circles.
|
||||
- Weather particles и world emitters.
|
||||
- Cinematic/model cameras и loading transitions.
|
||||
- Effects have pooling, lifetime, visibility и per-frame budgets.
|
||||
|
||||
## RND-10. Graphical extensions
|
||||
|
||||
- Higher-resolution content overlays без модификации originals.
|
||||
- Anisotropy, extended draw distance, improved water/shadows, post-processing и upscaling.
|
||||
- Optional PBR approximation only when it does not replace Blizzlike material profile.
|
||||
- Graphics settings expose measurable cost and fallback.
|
||||
- Editor comparison viewport: Blizzlike/Enhanced side-by-side, screenshot и metric capture.
|
||||
|
||||
## Verification
|
||||
|
||||
- Paired checkpoints с original 3.3.5a по position/time/weather/camera.
|
||||
- Material/animation fixtures from independent references.
|
||||
- Perceptual diff плюс human approval для intentional deviations.
|
||||
- p50/p95/p99, max hitch, draw calls, visible objects, GPU/CPU memory и RID leak checks.
|
||||
- D3D12/Vulkan and quality profile matrix.
|
||||
|
||||
## Gate
|
||||
|
||||
Видимый мир не деградирует при декомпозиции; under-foot/near-field quality готова до появления в кадре; long traversal не создаёт recurring hitches/leaks; Enhanced всегда отключаем.
|
||||
@@ -0,0 +1,94 @@
|
||||
# NET — Network, Protocol and Session
|
||||
|
||||
## Цель
|
||||
|
||||
Реализовать точный клиентский протокол WoW 3.3.5a build 12340, изолировав транспорт и packet layout от gameplay/UI.
|
||||
|
||||
## NET-1. Transport
|
||||
|
||||
- Native TCP auth/world connections, DNS/IPv4/IPv6 policy и configurable endpoints.
|
||||
- Non-blocking read/write, bounded buffers, packet fragmentation/coalescing.
|
||||
- Cancellation, connect/read/write timeouts, keepalive и orderly shutdown.
|
||||
- Network thread работает с bytes; domain events применяются game thread.
|
||||
- Proxy mode только как explicit development adapter, не основной transport.
|
||||
|
||||
## NET-2. Authentication
|
||||
|
||||
- Logon challenge/proof SRP6 values и exact endian/normalization rules.
|
||||
- Realm list parsing, build/locale/platform fields и error mapping.
|
||||
- World auth digest, session key и header encryption state.
|
||||
- Credentials не логируются; session material очищается при disconnect.
|
||||
- Golden vectors и сравнение с TrinityCore/AzerothCore/Wowser/Whoa implementations.
|
||||
|
||||
## NET-3. Packet framework
|
||||
|
||||
- `PacketReader/Writer` с checked boundaries и explicit endianness.
|
||||
- Typed opcode registry generated/validated для build 12340.
|
||||
- Header framing, ARC4/drop policy, zlib compressed packets.
|
||||
- Packed GUID, bit masks, CString/fixed strings и localized text.
|
||||
- Unknown/unsupported opcode telemetry rate-limited; policy зависит от criticality.
|
||||
- Packet handlers возвращают domain events, не меняют scenes/UI.
|
||||
|
||||
## NET-4. Update fields and entity lifecycle
|
||||
|
||||
- Generated object/item/container/unit/player/gameobject/dynamicobject/corpse field descriptors.
|
||||
- `SMSG_UPDATE_OBJECT` blocks: values, movement, create, create2, out-of-range, near objects.
|
||||
- Partial mask application сохраняет неизвестные/необновлённые поля корректно.
|
||||
- Entity type/entry/display/faction/health/power/flags/ownership relationships.
|
||||
- Despawn/out-of-range/map transfer не оставляют presentation entities.
|
||||
|
||||
## NET-5. Movement synchronization
|
||||
|
||||
- Полный movement flags set, timestamps, position/orientation, pitch, fall/jump, spline elevation.
|
||||
- Start/stop/strafe/turn/pitch/jump/fall/land/swim/fly/heartbeat packets.
|
||||
- Transport GUID/seat/relative coordinates и server spline movement.
|
||||
- Client prediction history, acknowledgements и reconciliation metrics.
|
||||
- Teleport/worldport handshake и map transfer.
|
||||
- Anti-cheat-sensitive fields формируются строго по profile; client не симулирует authoritative NPC path.
|
||||
|
||||
## NET-6. Session domains
|
||||
|
||||
- Character enum/create/delete/rename/login/logout.
|
||||
- Time/account data/tutorial/action bars/initial spells.
|
||||
- Chat/channel/social/group/guild.
|
||||
- Combat/spells/auras/threat.
|
||||
- Items/inventory/loot/trade/vendor/mail/auction.
|
||||
- Quests/gossip/POI/taxi.
|
||||
- Instances/LFG/PvP/vehicles/calendar.
|
||||
- Каждый domain имеет отдельный handler module, fixtures и unsupported coverage report.
|
||||
|
||||
## NET-7. Reliability and diagnostics
|
||||
|
||||
- State-aware disconnect reason и recovery path.
|
||||
- Reconnect только когда protocol/server допускает; иначе безопасный возврат к login.
|
||||
- Packet trace modes: metadata-only default, redacted payload для tests, encrypted secrets never.
|
||||
- Correlation IDs session/request/entity.
|
||||
- Replay transport воспроизводит timing или deterministic step mode.
|
||||
- Network impairment harness: latency, jitter, loss, duplication, fragmentation и abrupt close.
|
||||
|
||||
## NET-8. Core compatibility
|
||||
|
||||
- Baseline protocol один, но server capabilities/quirks задаются adapter profile.
|
||||
- Не ветвить handlers по строке `if azerothcore` без documented capability.
|
||||
- Test matrix фиксирует core commit, DB revision, modules и config.
|
||||
- Custom opcodes располагаются в namespaced extension profile и не включены в Blizzlike335.
|
||||
|
||||
## NET-9. Warden compatibility
|
||||
|
||||
- Зафиксировать фактические требования Warden у supported TrinityCore/AzerothCore profiles.
|
||||
- Реализовывать только compatibility с тестовыми/private server environments и документированным build 12340 flow; OpenWC не предназначен для official/retail servers.
|
||||
- Module challenge/response, crypto/state и platform behavior изолировать в optional security adapter.
|
||||
- Любое исполнение загружаемого module code sandboxed, size/hash checked и отключаемо в development profile.
|
||||
- Добавить fixtures и failure UX для unsupported/invalid challenge; не использовать subsystem для обхода чужих защитных механизмов.
|
||||
|
||||
## Tools and references
|
||||
|
||||
- TrinityCore/AzerothCore source — authoritative server-side packet expectations.
|
||||
- Whoa — reverse-engineered client semantics.
|
||||
- Wowser — независимый auth/world proof-of-concept и test ideas.
|
||||
- WoWee/WowUnreal — coverage и lifecycle comparison.
|
||||
- Packet captures используются только легально, минимально и с redaction.
|
||||
|
||||
## Gate
|
||||
|
||||
Headless client проходит auth → realm → character → world; replay создаёт одинаковые domain events; malformed packets не читаются за границы; secrets отсутствуют в logs; обе core configurations проходят contract suite.
|
||||
@@ -0,0 +1,112 @@
|
||||
# GMP — Gameplay Systems
|
||||
|
||||
## Цель
|
||||
|
||||
Создать детерминированную клиентскую проекцию серверного gameplay и полный набор player intents WoW 3.3.5a без переноса authoritative правил на клиент.
|
||||
|
||||
## GMP-1. World model
|
||||
|
||||
- Typed entity registry и object hierarchy.
|
||||
- Components/read models: identity, transform, unit, player, item, gameobject, corpse, dynamic object.
|
||||
- Ownership, charm, pet, target, creator, transport и group relationships.
|
||||
- Reducers применяют domain events; presentation подписывается на changesets.
|
||||
- Snapshot/replay позволяют восстановить состояние без live socket.
|
||||
|
||||
## GMP-2. Input and movement
|
||||
|
||||
- Remappable action layer, keyboard/mouse и optional gamepad/accessibility mappings.
|
||||
- Walk/run/backpedal/strafe/turn/jump/fall/swim/fly/autorun.
|
||||
- Camera-relative или character-relative policy строго по original behavior.
|
||||
- Ground/collision query abstraction, slopes, water transition и falling.
|
||||
- Prediction/reconciliation только локального movement; corrections имеют smoothing/snap thresholds.
|
||||
- Debug flight/speed отделены от production profile.
|
||||
|
||||
## GMP-3. Selection and interaction
|
||||
|
||||
- Mouse/world picking, tab target, target/focus и target-of-target.
|
||||
- Hostility/faction/dead/visibility/selectability filters.
|
||||
- Facing, range, line-of-sight и interaction intents.
|
||||
- NPC flags определяют available services; client не выдумывает доступную функцию.
|
||||
- Gameobject use, gossip, trainer, vendor, mailbox, auctioneer, binder и flight master.
|
||||
|
||||
## GMP-4. Combat
|
||||
|
||||
- Auto attack/auto shot, swing state и attack stop.
|
||||
- Damage/heal/miss/absorb/resist/block/crit feedback.
|
||||
- Threat presentation, combat state, evade и unit flags.
|
||||
- Resources, regeneration presentation и combo points/runes where applicable.
|
||||
- Combat log canonical events отделены от floating text/UI formatting.
|
||||
- Server validates hit, damage и death; client показывает pending/cast state.
|
||||
|
||||
## GMP-5. Spells and auras
|
||||
|
||||
- Spellbook, ranks, passive/active, known/disabled spells.
|
||||
- Target requirements, range, facing, resource и client-predictable validation messages.
|
||||
- Cast/channel/interrupt/pushback, GCD и cooldowns.
|
||||
- Aura add/update/remove, stacks, duration, caster, dispel type и visibility.
|
||||
- Totems, shapeshift, pets, tracking, tradeskill casts.
|
||||
- Spell visual/audio descriptors separate from spell mechanics state.
|
||||
|
||||
## GMP-6. Death and recovery
|
||||
|
||||
- Death/release spirit, ghost, corpse, graveyard, reclaim delay.
|
||||
- Resurrection request/accept, spirit healer penalties.
|
||||
- Durability loss/repair и instance wipe/reset presentation.
|
||||
- Failure during map transfer/reconnect does not duplicate corpse/player state.
|
||||
|
||||
## GMP-7. Items and economy
|
||||
|
||||
- Inventory slots, bags, bank, keyring, equipment, ammo и currencies.
|
||||
- Stack/split/swap/equip/use/destroy with pending/server-confirmed states.
|
||||
- Loot windows, group rolls, master loot, disenchant и loot release.
|
||||
- Item fields, random properties, enchants, gems, durability, sets и cooldowns.
|
||||
- Vendor/buyback/repair, trade, mail, auction и guild bank.
|
||||
- Money formatting/display separated from authoritative transaction.
|
||||
|
||||
## GMP-8. Quests and progression
|
||||
|
||||
- Quest availability, accept, abandon, track, objectives, complete и reward choice.
|
||||
- Kill, item, gameobject, explore, spell, escort, timed, reputation objectives.
|
||||
- Chains, prerequisites, exclusive groups, repeatable/daily/weekly и conditions.
|
||||
- XP/rested/level, reputation, honor, achievements.
|
||||
- Skills, talents, glyphs, professions, recipes и trainers.
|
||||
- Quest state drives view models; authoring graph compiles to server-supported representation.
|
||||
|
||||
## GMP-9. World activities
|
||||
|
||||
- Area/subzone/exploration state и rest areas.
|
||||
- Taxi discovery/routes/flight, transports, elevators и boats.
|
||||
- Fishing, gathering, tracking, world events и scripted gameobjects.
|
||||
- Weather/time events feed render/audio but are owned by world state.
|
||||
- Minimap/world map POI and quest markers are projections, not quest logic.
|
||||
|
||||
## GMP-10. Social
|
||||
|
||||
- Chat types, channels, whispers, system messages и links.
|
||||
- Friends, ignore, who, AFK/DND.
|
||||
- Party/raid invites, leader, roles, loot rules, ready check, raid targets.
|
||||
- Guild roster, ranks, notes, MOTD, event/calendar.
|
||||
- Presence/reconnect updates and privacy/error handling.
|
||||
|
||||
## GMP-11. Instances, PvP and vehicles
|
||||
|
||||
- Difficulty, instance binds, lockouts, encounters, reset и transfer aborted.
|
||||
- LFG queue/proposal/roles/teleport/vote kick/rewards.
|
||||
- Duel, PvP flags, battleground queue/status/score/resurrection и arena teams.
|
||||
- Outdoor PvP objectives.
|
||||
- Vehicle seats, enter/exit/switch, possession action bar и transport-relative movement.
|
||||
|
||||
## GMP-12. Cinematics and scripted presentation
|
||||
|
||||
- Cinematic/movie start/stop, camera control и skippability.
|
||||
- Server-driven scripted sequences reflected as safe presentation commands.
|
||||
- Loading screens and map transitions preserve session state.
|
||||
- Gameplay input contexts prevent actions during restricted states.
|
||||
|
||||
## Testing order
|
||||
|
||||
Для каждой системы: reducer unit tests → packet/domain contract → offline scenario → live core integration → original-client comparison → latency/reconnect scenario.
|
||||
|
||||
## Gate
|
||||
|
||||
Gameplay state воспроизводим replay-ом; UI/render не владеют authoritative values; все intents имеют pending/confirmed/rejected lifecycle; core gameplay loop проходит end-to-end без оригинального клиента.
|
||||
@@ -0,0 +1,97 @@
|
||||
# UIA — UI, Lua, Addons and Audio
|
||||
|
||||
## Цель
|
||||
|
||||
Воспроизвести UI/addon semantics клиента 3.3.5a, сохранив возможность modern UI overlays и полноценную audio presentation.
|
||||
|
||||
## UIA-1. UI architecture
|
||||
|
||||
- Screen flow отделён от in-world HUD.
|
||||
- UI читает immutable view models и отправляет intents.
|
||||
- Godot Control tree является presentation backend, а не gameplay model.
|
||||
- Input contexts: login, character, world, chat, modal, targeting, vehicle, cinematic.
|
||||
- Scale, safe area, resolution, font и localization учитываются на уровне layout system.
|
||||
|
||||
## UIA-2. Frontend screens
|
||||
|
||||
- Data/license check, login, realm list и connection errors.
|
||||
- Character selection/create/delete/rename.
|
||||
- Race/class/customization previews тем же character renderer.
|
||||
- Loading screens по map/instance, progress и cancellation policy.
|
||||
- Disconnect/reconnect dialogs и account/server notices.
|
||||
|
||||
## UIA-3. FrameXML
|
||||
|
||||
- TOC parser: metadata, dependencies, optional deps, SavedVariables и load order.
|
||||
- XML includes, templates, `virtual`, inheritance и named/anonymous objects.
|
||||
- Frames, Regions, Textures, FontStrings, Layers и Scripts.
|
||||
- Anchors, dimensions, strata, frame level, draw layers, clipping и scale.
|
||||
- Widget types: Button, CheckButton, EditBox, Slider, StatusBar, ScrollFrame, MessageFrame, Tooltip, Cooldown, Model и Minimap.
|
||||
- Handler scripts: OnLoad, OnEvent, OnUpdate, mouse/keyboard/drag/show/hide/value events.
|
||||
|
||||
## UIA-4. Lua runtime
|
||||
|
||||
- Внутренний `LuaRuntime` позволяет сравнить/заменить PUC-Rio 5.1.1 и `rilua`.
|
||||
- Restricted libraries, byte strings, number semantics, globals, `bit` и error formatting.
|
||||
- VM lifetime: one UI environment per client session/profile with controlled reset.
|
||||
- Incremental GC имеет frame budget и metrics.
|
||||
- Native functions возвращают stable Lua values, а не Godot/Rust/C++ objects напрямую.
|
||||
- VM backend выбирается ADR после official + WoW-specific corpus.
|
||||
|
||||
## UIA-5. WoW API and event system
|
||||
|
||||
- API inventory строится из 3.3.5a FrameXML/Lua usage и независимых references.
|
||||
- Реализация группируется: unit, spell, aura, item, quest, group, chat, map, CVar, bindings, frames.
|
||||
- Event dispatcher имеет deterministic ordering, argument contracts и registration lifecycle.
|
||||
- `OnUpdate` throttling не меняет original semantics в Blizzlike335.
|
||||
- API, требующий server state, читает gameplay read model или создаёт intent; raw packets недоступны.
|
||||
- Missing API выдаёт диагностируемую ошибку/coverage report, а не silent nil where incompatible.
|
||||
|
||||
## UIA-6. Security, taint and addons
|
||||
|
||||
- Secure/insecure execution origins.
|
||||
- Protected functions/actions, secure templates и combat lockdown.
|
||||
- Taint propagation through variables/tables/functions в пределах подтверждённой 3.3.5a модели.
|
||||
- Addon memory/CPU/error isolation и configurable script limits без скрытого изменения default behavior.
|
||||
- SavedVariables account/character scopes, serialization, corruption recovery и atomic writes.
|
||||
- Addon compatibility tiers: loads, renders, functional, secure-combat verified.
|
||||
- Headless addon runner и UI inspector входят в AuthoringStudio/Test Center.
|
||||
|
||||
## UIA-7. Default UI coverage
|
||||
|
||||
- Unit/player/target/focus/party/raid frames.
|
||||
- Action bars, stance/pet/vehicle bars, cooldowns и keybindings.
|
||||
- Buffs/debuffs, cast bars, combat text и errors.
|
||||
- Bags, character, spellbook, talents, glyphs, skills, professions.
|
||||
- Quest log/tracker/gossip, map/minimap/taxi.
|
||||
- Chat, social, guild, calendar, LFG, PvP score.
|
||||
- Vendor, trainer, bank, mail, auction, trade, loot и roll.
|
||||
- Game menu, settings, macros, addons и help.
|
||||
|
||||
## UIA-8. Audio
|
||||
|
||||
- Typed DBC wrappers for SoundEntries, ZoneMusic, IntroMusic, Ambience and related tables.
|
||||
- Decoder/streaming/cache for MP3/WAV with category budgets.
|
||||
- Music day/night/zone transitions and crossfade.
|
||||
- Ambient loops, random emitters, indoor/outdoor и weather.
|
||||
- Positional world sounds, footsteps, water, spells, combat, NPC voice и UI feedback.
|
||||
- Audio events come from presentation descriptors; UI/gameplay do not manipulate players directly.
|
||||
- Device change, focus/mute policy, category volumes и missing sound diagnostics.
|
||||
|
||||
## UIA-9. Localization and accessibility
|
||||
|
||||
- Locale-aware fonts, fallback, text encoding и layout expansion.
|
||||
- Keybindings fully remappable; mouse sensitivity/inversion and optional gamepad.
|
||||
- UI scale, contrast/color-blind profiles, subtitle/text alternatives where possible.
|
||||
- Critical state does not rely only on color/audio.
|
||||
- Enhanced accessibility remains compatible with server and can coexist with Blizzlike visuals.
|
||||
|
||||
## References
|
||||
|
||||
- Original 3.3.5a FrameXML/Lua extracted from legally owned data is primary behavior corpus.
|
||||
- `rilua`/PUC-Rio are VM candidates/oracles, not complete WoW UI.
|
||||
- WowBench/Wowser/WowUnreal/WoWee provide API/event/loader test ideas.
|
||||
|
||||
## Gate
|
||||
|
||||
Default UI boots deterministically; selected real addons pass declared tiers; secure actions fail correctly during combat; Lua GC/OnUpdate remain within budgets; UI is replaceable without changing gameplay/network.
|
||||
@@ -0,0 +1,109 @@
|
||||
# EDT — Godot Editor and Content Authoring
|
||||
|
||||
## Цель
|
||||
|
||||
Превратить Godot Editor в единую среду расширения мира, gameplay, UI, audio и graphics с безопасным build/deploy/playtest workflow.
|
||||
|
||||
## EDT-1. Plugin platform
|
||||
|
||||
- `addons/openwc_editor` регистрирует workspace, docks, inspector/import/gizmo/debugger plugins.
|
||||
- `_enter_tree/_exit_tree` симметричны: все controls/plugins/signals/jobs снимаются без leaks.
|
||||
- UI вызывает authoring application services; не пишет SQL/files/scene tree напрямую.
|
||||
- Editor selection отделена от runtime preview nodes.
|
||||
- Shared services получают context через dependency injection, не global editor singleton.
|
||||
|
||||
## EDT-2. Workspace and UX
|
||||
|
||||
- World viewport, Content Browser, Outliner, Inspector, Validation, History, Build, Database, Test Center.
|
||||
- Search/filter/favorites/recent items и stable links между сущностями.
|
||||
- Multi-selection и bulk edits показывают scope/diff.
|
||||
- Diagnostics имеют severity/code/location/fix; double-click открывает field/node/world position.
|
||||
- Layout/workspace preferences сохраняются отдельно от Content Project.
|
||||
|
||||
## EDT-3. Commands, undo and recovery
|
||||
|
||||
- Любая mutation — command с execute/undo/description/affected IDs.
|
||||
- Terrain strokes агрегируются в bounded deltas; большие операции используют snapshots/chunks.
|
||||
- Autosave recovery journal атомарный и имеет schema version.
|
||||
- External changes обнаруживаются через hashes; merge/overwrite требует решения пользователя.
|
||||
- Quick Fix всегда previewable и undoable.
|
||||
|
||||
## EDT-4. Import and asset browser
|
||||
|
||||
- MPQ/archive browser, locale/patch source provenance и extraction jobs.
|
||||
- Preview BLP, DBC rows, M2 animations/materials, WMO groups/portals, ADT layers и audio.
|
||||
- Godot import plugins применяются только к authoring source formats, где lifecycle `.import` полезен.
|
||||
- Raw extracted corpus не должен вызывать неконтролируемый массовый Godot reimport.
|
||||
- Batch conversion использует staging, cancellation, cache hash и report.
|
||||
|
||||
## EDT-5. World editing
|
||||
|
||||
- Map/tile/chunk navigation и coordinate overlays.
|
||||
- Terrain sculpt/smooth/flatten/noise/stamps/holes.
|
||||
- Texture layers, alpha painting, shading и liquids.
|
||||
- M2/WMO placement, snapping, transforms, doodad sets и unique ID allocation.
|
||||
- Creature/gameobject spawns, groups, formations, patrol/escort paths.
|
||||
- Areas, triggers, graveyards, teleports, taxi/transport points, sound/weather volumes.
|
||||
- Collision/navmesh/streaming/render-cost visualization.
|
||||
|
||||
## EDT-6. Data editors
|
||||
|
||||
- Creature/gameobject templates, models, equipment, movement and text.
|
||||
- Item/loot/vendor/trainer/profession editors.
|
||||
- Quest form + dependency/progression graph + localization.
|
||||
- Gossip/conditions/SmartAI graph with raw representation inspector.
|
||||
- Spawn/pool/game event/phase editor.
|
||||
- Dungeon package, encounter state graph, doors/triggers/lockouts/loot.
|
||||
- UI/addon manifest, CVar/keybinding и audio zone configuration.
|
||||
|
||||
## EDT-7. Navigation
|
||||
|
||||
- `recast-rs` optional offline backend behind navigation build interface.
|
||||
- Bake profiles: agent size/climb/slope/cell/region/tile parameters.
|
||||
- Walkability, disconnected islands, path cost, off-mesh links и dynamic door states.
|
||||
- Quest giver/objective/turn-in reachability; patrol/escort validation.
|
||||
- OpenWC cache отделён от server mmap until compatibility proven.
|
||||
|
||||
## EDT-8. Graphics extension tools
|
||||
|
||||
- Material/shader profile editor с Blizzlike/Enhanced comparison.
|
||||
- Texture replacement/upscale overlays и mip/alpha/color-space validation.
|
||||
- Water/sky/weather/light preview with fixed time and reference capture.
|
||||
- Character/equipment/animation/attachment preview.
|
||||
- Spell visual/particle/ribbon authoring where formats permit.
|
||||
- Quality preset cost preview и automatic render checkpoint generation.
|
||||
|
||||
## EDT-9. Content Project
|
||||
|
||||
- Git-friendly files, schema version, symbolic IDs, dependencies и capability requirements.
|
||||
- Original data referenced, never copied silently.
|
||||
- Overlay priority/conflict report и semantic merges.
|
||||
- Deterministic numeric ID allocation per server profile.
|
||||
- Localization bundles, scripts, client assets, tests и package manifests.
|
||||
- Schema migrations with backup, fixture and loss policy.
|
||||
|
||||
## EDT-10. Build, deploy and playtest
|
||||
|
||||
```text
|
||||
Validate → Compile → Bake → Diff → Package → Dry-run
|
||||
→ Apply dev DB/module/assets → Start/reload core → Launch client at context
|
||||
→ Collect logs/results → Roll back disposable environment
|
||||
```
|
||||
|
||||
- Jobs cancellable, progress-aware and identical to headless CI services.
|
||||
- Production target physically/logically separated and disabled by default.
|
||||
- Server reload used only for tables known to support it; otherwise restart.
|
||||
- Playtest bundle records content/core/client revisions and profile.
|
||||
- One-click convenience never hides SQL/module/client artifact diff.
|
||||
|
||||
## EDT-11. Extensibility
|
||||
|
||||
- `ContentTypeDescriptor`: schema, icon, inspector, validator, compiler, presenter.
|
||||
- `EditorTool`: selection contract, command factory, gizmo/overlay.
|
||||
- `BuildStep`: inputs/outputs/cache key/cancellation/diagnostics.
|
||||
- Plugin declares API version, dependencies, permissions and migrations.
|
||||
- User plugins cannot mutate production DB or original assets outside approved services.
|
||||
|
||||
## Best-practice gate
|
||||
|
||||
Plugin reload clean; undo/save/reload deterministic; long jobs do not block Editor; preview nodes disposable; every deployment reproducible headless; common scenarios require no manual SQL.
|
||||
@@ -0,0 +1,84 @@
|
||||
# SRV — TrinityCore and AzerothCore Integration
|
||||
|
||||
## Цель
|
||||
|
||||
Поддержать server emulators как versioned backends: protocol-compatible runtime, безопасное редактирование данных и расширение mechanics через modules/scripts.
|
||||
|
||||
## SRV-1. Environment model
|
||||
|
||||
- `ServerEnvironment`: family, core commit, DB revision, modules, config profile, endpoints и data artifact versions.
|
||||
- Отдельные disposable, local-dev, shared-test, staging и production classes.
|
||||
- Credentials/secrets вне Content Project.
|
||||
- Capability discovery проверяет schema fingerprint и optional modules/tables/hooks.
|
||||
- Unknown environment всегда read-only.
|
||||
|
||||
## SRV-2. Database snapshots
|
||||
|
||||
- Read-only import world schema subsets с provenance/checksum.
|
||||
- Auth/characters DB открываются только для нужных test/admin сценариев и никогда не становятся content source.
|
||||
- Snapshot filters reproducible; personal/account data excluded.
|
||||
- Canonical entities preserve supported fields and explicitly record unsupported/raw extras.
|
||||
- Incremental snapshot возможен, но full consistency snapshot остаётся verification path.
|
||||
|
||||
## SRV-3. Canonical mappings
|
||||
|
||||
- Creature/gameobject templates, models, spawns, addons, formations, movement, texts.
|
||||
- Quests, relations, objectives, POI, conditions, gossip.
|
||||
- Loot/reference loot, vendors, trainers, items, reputation.
|
||||
- SmartAI, script names, waypoint/script tables.
|
||||
- Instances, encounters, LFG, graveyards, teleports, game events, weather, transports.
|
||||
- Каждая mapping имеет per-core capability table и round-trip tests.
|
||||
|
||||
## SRV-4. Change sets and SQL
|
||||
|
||||
- Immutable change set: target fingerprint, preconditions, ordered operations, checksum.
|
||||
- SQL generation соответствует conventions целевого core/profile.
|
||||
- Custom SQL хранится в version control и compatible custom/module directory, не только применяется напрямую.
|
||||
- Dry-run на disposable DB, targeted post-apply queries, core startup smoke.
|
||||
- Idempotent migration или безопасное precondition failure.
|
||||
- Rollback где возможен; destructive/irreversible operations требуют backup и явного подтверждения.
|
||||
|
||||
## SRV-5. Scripts and modules
|
||||
|
||||
- DB/SmartAI для выразимых data-driven mechanics.
|
||||
- C++ module/script skeleton для mechanics, требующих server hooks.
|
||||
- AzerothCore modules namespaced config/SQL/hooks; core patch — только если hook принципиально отсутствует.
|
||||
- TrinityCore adapter следует его текущим script/customization conventions отдельно.
|
||||
- Build artifact включает source, config.dist, SQL, tests и compatibility manifest.
|
||||
- Client extension capability договаривается явно; hidden custom opcode запрещён.
|
||||
|
||||
## SRV-6. Server data artifacts
|
||||
|
||||
- DBC/maps/vmaps/mmaps versions связываются с ContentBundleManifest.
|
||||
- Custom map требует согласованного client terrain, server maps/collision/pathfinding и DB Map/Area data.
|
||||
- OpenWC navmesh не объявляется mmap до binary/query/live behavior compatibility.
|
||||
- Asset/map IDs распределяются стабильно и проверяются на collision.
|
||||
- Client/server package mismatch блокирует playtest с actionable diagnostic.
|
||||
|
||||
## SRV-7. Protocol compatibility
|
||||
|
||||
- Baseline 3.3.5a build 12340 остаётся общим.
|
||||
- Core-specific quirks описываются named capabilities и fixtures.
|
||||
- Custom extensions используют versioned handshake/profile и fallback/rejection.
|
||||
- Server authority одинаково соблюдается для обеих families.
|
||||
- Compatibility matrix закрепляет tested commits, а не расплывчатое «поддерживает AzerothCore».
|
||||
|
||||
## SRV-8. Test environments
|
||||
|
||||
- Docker/managed local environment для repeatable DB/core setup, где поддерживается.
|
||||
- Seed accounts/characters synthetic и не содержат реальные credentials.
|
||||
- Reset DB через assembler/base + project migrations.
|
||||
- Logs world/auth/database/client собираются с correlation metadata.
|
||||
- DB-only change test, module build test, startup smoke, scripted client scenario и cleanup.
|
||||
|
||||
## SRV-9. Existing tools
|
||||
|
||||
- Keira3 — reference для field semantics, SQL generation и AzerothCore DB UX.
|
||||
- AzerothCore wiki/schema/module template — primary backend documentation.
|
||||
- TrinityCore source/schema/tools — primary для Trinity profile.
|
||||
- Noggit/WoW modding tools — client-world authoring reference, но не server schema oracle.
|
||||
- OpenWC Editor объединяет workflow, но сохраняет raw SQL preview и ссылки на upstream docs.
|
||||
|
||||
## Gate
|
||||
|
||||
Одна canonical сущность проходит import → edit → diff → dry-run → apply → reimport на каждом supported profile без silent loss; module/content packages воспроизводимы; production write disabled by default; test environment очищается.
|
||||
@@ -0,0 +1,121 @@
|
||||
# QAR — Fidelity, Quality, CI and Release
|
||||
|
||||
## Цель
|
||||
|
||||
Сделать точность, производительность и расширяемость проверяемыми свойствами, а не обещаниями.
|
||||
|
||||
## QAR-1. Fidelity lab
|
||||
|
||||
- Каталог сценариев original client 3.3.5a: setup, input, server state, expected output.
|
||||
- Paired screenshots/video/log/packet/state evidence с provenance.
|
||||
- Поведение классифицируется: `Unknown`, `Observed`, `Matched`, `Intentional deviation`, `Blocked`.
|
||||
- Independent references могут подсказать hypothesis, но не повышают статус без evidence.
|
||||
- Blizzlike bugs/exploits обрабатываются policy decision: protocol compatibility отдельно от harmful behavior.
|
||||
|
||||
## QAR-2. Test layers
|
||||
|
||||
- Native unit tests: parsers, crypto, compression, coordinates, math, caches.
|
||||
- GDScript/domain unit tests: reducers, validators, commands, view models.
|
||||
- Contract tests: packets, DB adapters, Lua API, content schemas, renderer facade.
|
||||
- Scene tests: lifecycle, input, UI, renderer attachment, EditorPlugin reload.
|
||||
- Integration: disposable core/DB, import/build/deploy, login/world/gameplay.
|
||||
- E2E: leveling loop, dungeon, PvP/service flows и authoring vertical slices.
|
||||
- Visual/performance and exploratory design tests complement automation.
|
||||
|
||||
## QAR-3. Godot testing stack
|
||||
|
||||
- Оценить GdUnit4 как candidate для Godot 4.6 GDScript/scene/CI tests.
|
||||
- Native GDExtension tests используют подходящий C++ test runner отдельно от engine-internal doctest unless Godot itself is rebuilt.
|
||||
- Headless commands возвращают non-zero и machine-readable JUnit/JSON reports.
|
||||
- Scene tests не зависят от proprietary assets: synthetic fixtures or locally resolved optional corpus.
|
||||
- Flaky retries не превращают failure в success; instability измеряется и исправляется.
|
||||
|
||||
## QAR-4. Golden and property testing
|
||||
|
||||
- Golden binary structures and exact-byte packet vectors.
|
||||
- Parser round-trip where format permits; malformed/truncated/fuzz corpus.
|
||||
- Coordinate transform property tests and known world points.
|
||||
- Deterministic content manifests, SQL diff and migration before/after fixtures.
|
||||
- Lua oracle corpus, navmesh reference counts/queries, material descriptor mappings.
|
||||
|
||||
## QAR-5. Performance budgets
|
||||
|
||||
- Startup/data check/login/world entry.
|
||||
- Streaming cold/warm traversal and teleport.
|
||||
- CPU/GPU frame p50/p95/p99/max hitch, draw calls, memory и descriptors.
|
||||
- Network decode/apply queues and reconciliation error.
|
||||
- UI `OnUpdate`, event fan-out, Lua allocations/GC.
|
||||
- Editor import/bake/validation latency and cancellation responsiveness.
|
||||
- Server deployment/playtest turnaround.
|
||||
- Budgets hardware/profile-specific and trend tracked.
|
||||
|
||||
## QAR-6. Observability
|
||||
|
||||
- Structured logging categories and stable diagnostic codes.
|
||||
- Correlation IDs session/job/entity/content change set.
|
||||
- Runtime metrics: queues, entities, cache hit, resource counts, RIDs, packet rate.
|
||||
- Editor metrics: jobs, diagnostics, artifact hashes, DB operations.
|
||||
- Debug overlays and Godot profiler/visual profiler capture workflow.
|
||||
- Payload, credentials, chat/personal content redacted by default.
|
||||
|
||||
## QAR-7. CI pipeline
|
||||
|
||||
Pull request gates:
|
||||
|
||||
1. formatting/static analysis/document links/target marker validation;
|
||||
2. native build and unit tests;
|
||||
3. headless Godot load and GDScript tests;
|
||||
4. affected parser/protocol/content/adapter contracts;
|
||||
5. deterministic artifact check;
|
||||
6. renderer smoke if visual code changed;
|
||||
7. license/dependency audit.
|
||||
|
||||
Nightly/weekly:
|
||||
|
||||
- disposable TrinityCore/AzerothCore integration;
|
||||
- full packet/Lua/parser replay corpus;
|
||||
- visual checkpoints on available GPU runners;
|
||||
- long traversal, reconnect, cache migration and soak;
|
||||
- previous schema/Godot/core compatibility matrix.
|
||||
|
||||
## QAR-8. Security and robustness
|
||||
|
||||
- Bounds checked binary parsing and fuzzing.
|
||||
- Lua sandbox/taint/protected actions.
|
||||
- DB least privilege, read-only default, production protection.
|
||||
- Untrusted addon/content/plugin permission model.
|
||||
- Dependency license/advisory/source pin audit.
|
||||
- Atomic files, backup, recovery and rollback.
|
||||
- No proprietary assets, secrets or production dumps in repository/artifacts.
|
||||
|
||||
## QAR-9. Documentation and decisions
|
||||
|
||||
- Architecture/API/schema changes update docs and ADR в том же work package.
|
||||
- Каждый самостоятельный module имеет specification по `docs/DOCUMENTATION_STANDARD.md`: purpose, boundaries, public API, inputs/outputs, data flow, ownership, errors, tests и source map.
|
||||
- Data-flow diagram обязательна всегда; state/sequence/dependency diagrams обязательны по сложности/lifecycle rules стандарта.
|
||||
- Inline docs являются источником точной сигнатуры, module spec — источником концепции и фактического статуса.
|
||||
- Tooling candidates tracked in `docs/TOOLING_CATALOG.md` with status and pinned version.
|
||||
- Feature coverage links implementation, tests, fidelity evidence and known gaps.
|
||||
- Generated API docs for documented GDScript/GDExtension public surface включаются в quality gate, когда pipeline подготовлен.
|
||||
|
||||
## QAR-9A. Code clarity and KISS
|
||||
|
||||
- Naming и simplicity проверяются по `docs/CODING_STANDARD.md`.
|
||||
- Длинное точное имя предпочтительнее неочевидного сокращения; length не является целью сама по себе.
|
||||
- Complexity metrics используются как сигнал для review, а не автоматическая причина переписывания domain-heavy code.
|
||||
- Premature generic frameworks, service locators, duplicate registries и unmeasured native optimizations блокируют integration.
|
||||
- Неизбежно сложный parser/protocol/streaming/UI security code обязан быть изолирован, иметь reference fixtures и диаграмму.
|
||||
- Contributor workflow describes smallest reproducible diagnostic commands.
|
||||
|
||||
## QAR-10. Packaging and release
|
||||
|
||||
- Client ships without Blizzard data and guides user to select legally owned install.
|
||||
- Import/cache first-run is resumable and reports disk/time requirements.
|
||||
- Platform packages include matching GDExtension libraries and dependency notices.
|
||||
- Content package and server module releases version independently with compatibility manifest.
|
||||
- Save/settings/content schema migrations tested from supported previous versions.
|
||||
- Release channels: developer, preview, stable; fidelity gaps visible in notes.
|
||||
|
||||
## Exit gate
|
||||
|
||||
Release candidate passes required core/platform/profile matrix; no critical unowned diagnostic; performance regression within approved bounds; artifacts reproducible and license-clean; known fidelity deviations documented.
|
||||
@@ -5,6 +5,10 @@ param(
|
||||
[string]$CacheState = "existing",
|
||||
[double]$WaitSeconds = 8.0,
|
||||
[double]$MeasureSeconds = 3.0,
|
||||
[string]$ReferenceCheckpointDirectory,
|
||||
[string]$VisualComparisonReport = "user://render_baseline/visual_comparison.json",
|
||||
[int]$VisualComparisonWidth = 2560,
|
||||
[int]$VisualComparisonHeight = 1440,
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
@@ -42,6 +46,7 @@ try {
|
||||
Invoke-GodotStep "render-materials" @("--headless", "--path", ".", "--script", "res://src/tools/verify_render_materials.gd")
|
||||
Invoke-GodotStep "m2-unique-dedupe" @("--headless", "--path", ".", "--script", "res://src/tools/verify_m2_unique_dedupe.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")
|
||||
|
||||
$captureArgs = @(
|
||||
"--path", ".",
|
||||
@@ -57,6 +62,33 @@ try {
|
||||
$captureArgs = @("--headless") + $captureArgs + @("--dry-run")
|
||||
}
|
||||
Invoke-GodotStep "capture" $captureArgs
|
||||
if ($ReferenceCheckpointDirectory) {
|
||||
if ($DryRun) {
|
||||
throw "Reference checkpoint comparison requires PNG capture; remove -DryRun"
|
||||
}
|
||||
$visualCandidateOutput = "$Output/visual_comparison_candidates"
|
||||
$visualCaptureArgs = @(
|
||||
"--path", ".",
|
||||
"--script", "res://src/tools/capture_render_checkpoints.gd",
|
||||
"--",
|
||||
"--output", $visualCandidateOutput,
|
||||
"--cache-state", $CacheState,
|
||||
"--revision", $revision,
|
||||
"--wait", $WaitSeconds.ToString([Globalization.CultureInfo]::InvariantCulture),
|
||||
"--measure", $MeasureSeconds.ToString([Globalization.CultureInfo]::InvariantCulture),
|
||||
"--viewport-width", $VisualComparisonWidth.ToString(),
|
||||
"--viewport-height", $VisualComparisonHeight.ToString()
|
||||
)
|
||||
Invoke-GodotStep "visual-capture" $visualCaptureArgs
|
||||
Invoke-GodotStep "visual-comparison" @(
|
||||
"--headless", "--path", ".",
|
||||
"--script", "res://src/tools/compare_render_checkpoints.gd",
|
||||
"--",
|
||||
"--reference", $ReferenceCheckpointDirectory,
|
||||
"--candidate", $visualCandidateOutput,
|
||||
"--output", $VisualComparisonReport
|
||||
)
|
||||
}
|
||||
Write-Host "Renderer baseline completed. Report: $Output/report.json"
|
||||
} finally {
|
||||
Pop-Location
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ReferenceDirectory,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Checkpoint,
|
||||
[string]$GodotPath,
|
||||
[string]$Output = "user://render_camera_pose_sweep",
|
||||
[double[]]$YawOffsets = @(-10.0, 0.0, 10.0),
|
||||
[double[]]$PitchOffsets = @(-10.0, 0.0, 10.0),
|
||||
[double]$CameraFov = 62.0,
|
||||
[double[]]$CameraFovValues = @(),
|
||||
[int]$ViewportWidth = 1280,
|
||||
[int]$ViewportHeight = 900,
|
||||
[int[]]$ComparisonRegion = @(),
|
||||
[double]$WaitSeconds = 8.0,
|
||||
[double]$MeasureSeconds = 0.1,
|
||||
[switch]$PlanOnly
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||
if (-not $GodotPath) {
|
||||
$godotCommand = Get-Command godot -ErrorAction SilentlyContinue
|
||||
if ($godotCommand) {
|
||||
$GodotPath = $godotCommand.Source
|
||||
} else {
|
||||
$GodotPath = Join-Path $env:TEMP "godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe"
|
||||
}
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $GodotPath)) {
|
||||
throw "Godot executable not found: $GodotPath"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $ReferenceDirectory)) {
|
||||
throw "Reference directory not found: $ReferenceDirectory"
|
||||
}
|
||||
if ($ComparisonRegion.Count -notin @(0, 4)) {
|
||||
throw "ComparisonRegion requires x,y,width,height"
|
||||
}
|
||||
|
||||
$invariantCulture = [Globalization.CultureInfo]::InvariantCulture
|
||||
$isJointFovSweep = $CameraFovValues.Count -gt 0
|
||||
$effectiveCameraFovValues = if ($isJointFovSweep) { $CameraFovValues } else { @($CameraFov) }
|
||||
$waitSecondsText = $WaitSeconds.ToString($invariantCulture)
|
||||
$measureSecondsText = $MeasureSeconds.ToString($invariantCulture)
|
||||
$absoluteOutput = if ($Output.StartsWith("user://")) {
|
||||
Join-Path $env:APPDATA "Godot\app_userdata\OpenWC\$($Output.Substring(7))"
|
||||
} else {
|
||||
[IO.Path]::GetFullPath((Join-Path $repoRoot $Output))
|
||||
}
|
||||
$ranking = [Collections.Generic.List[object]]::new()
|
||||
|
||||
Push-Location $repoRoot
|
||||
try {
|
||||
foreach ($cameraFovValue in $effectiveCameraFovValues) {
|
||||
foreach ($yawOffset in $YawOffsets) {
|
||||
foreach ($pitchOffset in $PitchOffsets) {
|
||||
$cameraFovText = $cameraFovValue.ToString("0.###", $invariantCulture)
|
||||
$yawText = $yawOffset.ToString("0.###", $invariantCulture)
|
||||
$pitchText = $pitchOffset.ToString("0.###", $invariantCulture)
|
||||
$candidateName = "yaw_$($yawText.Replace('-', 'n').Replace('.', '_'))__pitch_$($pitchText.Replace('-', 'n').Replace('.', '_'))"
|
||||
if ($isJointFovSweep) {
|
||||
$candidateName = "fov_$($cameraFovText.Replace('.', '_'))__$candidateName"
|
||||
}
|
||||
$candidateOutput = "$Output/$candidateName"
|
||||
$comparisonReport = Join-Path $absoluteOutput "$candidateName.json"
|
||||
|
||||
if ($PlanOnly) {
|
||||
$ranking.Add([pscustomobject]@{
|
||||
yaw_offset_degrees = $yawOffset
|
||||
pitch_offset_degrees = $pitchOffset
|
||||
camera_fov_degrees = $cameraFovValue
|
||||
candidate_directory = $candidateOutput
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
$captureArguments = @(
|
||||
"--path", ".", "--script", "res://src/tools/capture_render_checkpoints.gd", "--",
|
||||
"--output", $candidateOutput, "--only", $Checkpoint, "--single-pass",
|
||||
"--camera-fov", $cameraFovText,
|
||||
"--camera-yaw-offset", $yawText, "--camera-pitch-offset", $pitchText,
|
||||
"--viewport-width", $ViewportWidth, "--viewport-height", $ViewportHeight,
|
||||
"--wait", $waitSecondsText, "--measure", $measureSecondsText
|
||||
)
|
||||
$captureProcess = Start-Process -FilePath $GodotPath -ArgumentList $captureArguments -Wait -PassThru
|
||||
if ($captureProcess.ExitCode -ne 0) {
|
||||
throw "Capture failed for yaw=$yawText pitch=$pitchText"
|
||||
}
|
||||
|
||||
$comparisonArguments = @(
|
||||
"--headless", "--path", ".", "--script", "res://src/tools/compare_render_checkpoints.gd", "--",
|
||||
"--reference", $ReferenceDirectory, "--candidate", $candidateOutput,
|
||||
"--only", $Checkpoint, "--output", $comparisonReport
|
||||
)
|
||||
if ($ComparisonRegion.Count -eq 4) {
|
||||
$comparisonArguments += @("--region", ($ComparisonRegion -join ","))
|
||||
}
|
||||
$comparisonProcess = Start-Process -FilePath $GodotPath -ArgumentList $comparisonArguments -Wait -PassThru -NoNewWindow
|
||||
if ($comparisonProcess.ExitCode -notin @(0, 1)) {
|
||||
throw "Comparison failed for yaw=$yawText pitch=$pitchText"
|
||||
}
|
||||
|
||||
$comparison = Get-Content -Raw -LiteralPath $comparisonReport | ConvertFrom-Json
|
||||
if ($comparison.compared_count -lt 1) {
|
||||
throw "No paired image was compared for yaw=$yawText pitch=$pitchText"
|
||||
}
|
||||
$metricResults = @($comparison.results | Where-Object { $null -ne $_.mean_perceptual_error })
|
||||
if ($metricResults.Count -ne $comparison.compared_count) {
|
||||
$statuses = ($comparison.results.status | Sort-Object -Unique) -join ","
|
||||
throw "Comparison produced no numeric metric for yaw=$yawText pitch=$pitchText statuses=$statuses"
|
||||
}
|
||||
$meanError = ($metricResults | Measure-Object -Property mean_perceptual_error -Average).Average
|
||||
$changedRatio = ($metricResults | Measure-Object -Property changed_pixel_ratio -Average).Average
|
||||
$ranking.Add([pscustomobject]@{
|
||||
yaw_offset_degrees = $yawOffset
|
||||
pitch_offset_degrees = $pitchOffset
|
||||
camera_fov_degrees = $cameraFovValue
|
||||
mean_perceptual_error = $meanError
|
||||
changed_pixel_ratio = $changedRatio
|
||||
candidate_directory = $candidateOutput
|
||||
comparison_report = $comparisonReport
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rankedCandidates = if ($PlanOnly) {
|
||||
@($ranking)
|
||||
} else {
|
||||
@($ranking | Sort-Object mean_perceptual_error, changed_pixel_ratio)
|
||||
}
|
||||
$rankingPath = Join-Path $absoluteOutput "ranking.json"
|
||||
$rankingDocument = [ordered]@{
|
||||
schema_version = 1
|
||||
checkpoint = $Checkpoint
|
||||
reference_directory = [IO.Path]::GetFullPath($ReferenceDirectory)
|
||||
viewport = @($ViewportWidth, $ViewportHeight)
|
||||
wait_seconds = $WaitSeconds
|
||||
measure_seconds = $MeasureSeconds
|
||||
comparison_region = @($ComparisonRegion)
|
||||
candidates = $rankedCandidates
|
||||
}
|
||||
New-Item -ItemType Directory -Force -Path $absoluteOutput | Out-Null
|
||||
$rankingDocument | ConvertTo-Json -Depth 6 | Set-Content -Encoding UTF8 -LiteralPath $rankingPath
|
||||
$rankedCandidates | Format-Table camera_fov_degrees, yaw_offset_degrees, pitch_offset_degrees, mean_perceptual_error, changed_pixel_ratio
|
||||
$completionKind = if ($PlanOnly) { "plan" } else { "ranking" }
|
||||
Write-Output "Camera pose sweep $completionKind completed: $rankingPath"
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
param(
|
||||
[string]$Root = (Split-Path -Parent $PSScriptRoot)
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$failures = [System.Collections.Generic.List[string]]::new()
|
||||
$warnings = [System.Collections.Generic.List[string]]::new()
|
||||
|
||||
$targetsDir = Join-Path $Root 'targets'
|
||||
$targetIndex = Join-Path $targetsDir 'README.md'
|
||||
$targetFiles = Get-ChildItem -LiteralPath $targetsDir -File |
|
||||
Where-Object { $_.Name -match '^\d\d-.*\.md$' } |
|
||||
Sort-Object Name
|
||||
|
||||
$activeTargets = [System.Collections.Generic.List[object]]::new()
|
||||
$statusPattern = '<!-- OPENWC_TARGET:(OPEN|ACTIVE|BLOCKED|DONE) -->'
|
||||
|
||||
foreach ($file in $targetFiles) {
|
||||
$content = Get-Content -Raw -Encoding UTF8 -LiteralPath $file.FullName
|
||||
$matches = [regex]::Matches($content, $statusPattern)
|
||||
if ($matches.Count -ne 1) {
|
||||
$failures.Add("$($file.Name): expected exactly one target status marker, found $($matches.Count)")
|
||||
continue
|
||||
}
|
||||
if ($matches[0].Groups[1].Value -eq 'ACTIVE') {
|
||||
$activeTargets.Add($file)
|
||||
}
|
||||
}
|
||||
|
||||
if ($activeTargets.Count -ne 1) {
|
||||
$failures.Add("Expected exactly one ACTIVE target, found $($activeTargets.Count)")
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $targetIndex)) {
|
||||
$failures.Add('targets/README.md is missing')
|
||||
} else {
|
||||
$indexContent = Get-Content -Raw -Encoding UTF8 -LiteralPath $targetIndex
|
||||
$currentMatch = [regex]::Match($indexContent, '(?s)## Current target\s+`(M\d\d)`[^\r\n]*\[[^\]]+\]\(([^)]+)\)')
|
||||
if (-not $currentMatch.Success) {
|
||||
$failures.Add('Could not parse Current target from targets/README.md')
|
||||
} elseif ($activeTargets.Count -eq 1) {
|
||||
$expectedId = $currentMatch.Groups[1].Value
|
||||
$expectedFile = $currentMatch.Groups[2].Value
|
||||
$activeFile = $activeTargets[0].Name
|
||||
$activeId = 'M' + $activeFile.Substring(0, 2)
|
||||
if ($expectedId -ne $activeId -or $expectedFile -ne $activeFile) {
|
||||
$failures.Add("Current target points to $expectedId/$expectedFile but ACTIVE is $activeId/$activeFile")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$claimsDir = Join-Path $Root 'coordination\claims'
|
||||
$claimIds = @{}
|
||||
$claimPattern = '<!-- OPENWC_CLAIM:([A-Z0-9-]+):([a-z0-9-]+):(\d{4}-\d{2}-\d{2}) -->'
|
||||
|
||||
if (Test-Path -LiteralPath $claimsDir) {
|
||||
$claimFiles = Get-ChildItem -LiteralPath $claimsDir -Filter '*.md' -File |
|
||||
Where-Object { $_.Name -ne 'README.md' }
|
||||
foreach ($file in $claimFiles) {
|
||||
$content = Get-Content -Raw -Encoding UTF8 -LiteralPath $file.FullName
|
||||
$matches = [regex]::Matches($content, $claimPattern)
|
||||
if ($matches.Count -ne 1) {
|
||||
$failures.Add("coordination/claims/$($file.Name): expected exactly one valid OPENWC_CLAIM marker")
|
||||
continue
|
||||
}
|
||||
$claimId = $matches[0].Groups[1].Value
|
||||
if ($claimIds.ContainsKey($claimId)) {
|
||||
$failures.Add("Duplicate claim ID $claimId in $($claimIds[$claimId]) and $($file.Name)")
|
||||
} else {
|
||||
$claimIds[$claimId] = $file.Name
|
||||
}
|
||||
$expiryText = $matches[0].Groups[3].Value
|
||||
$expiry = [datetime]::ParseExact($expiryText, 'yyyy-MM-dd', [Globalization.CultureInfo]::InvariantCulture)
|
||||
if ($expiry.Date -lt (Get-Date).ToUniversalTime().Date) {
|
||||
$warnings.Add("Claim $claimId expired on $expiryText")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($warning in $warnings) {
|
||||
Write-Warning $warning
|
||||
}
|
||||
|
||||
if ($failures.Count -gt 0) {
|
||||
foreach ($failure in $failures) {
|
||||
Write-Error $failure
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Output "Coordination check passed: targets=$($targetFiles.Count) active=$($activeTargets.Count) fallback_claims=$($claimIds.Count) warnings=$($warnings.Count)"
|
||||
exit 0
|
||||
@@ -0,0 +1,105 @@
|
||||
param(
|
||||
[string]$Root = (Split-Path -Parent $PSScriptRoot)
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$failures = [System.Collections.Generic.List[string]]::new()
|
||||
|
||||
$requiredFiles = @(
|
||||
'AGENTS.md',
|
||||
'docs\README.md',
|
||||
'docs\CODING_STANDARD.md',
|
||||
'docs\DOCUMENTATION_STANDARD.md',
|
||||
'docs\modules\README.md',
|
||||
'docs\modules\TEMPLATE.md',
|
||||
'targets\DEVELOPMENT_ROADMAP.md'
|
||||
)
|
||||
|
||||
foreach ($relative in $requiredFiles) {
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $Root $relative))) {
|
||||
$failures.Add("Missing required documentation file: $relative")
|
||||
}
|
||||
}
|
||||
|
||||
$agentGuide = Join-Path $Root 'AGENTS.md'
|
||||
if (Test-Path -LiteralPath $agentGuide) {
|
||||
$agentContent = Get-Content -Raw -Encoding UTF8 -LiteralPath $agentGuide
|
||||
if ($agentContent -notmatch 'DOCUMENTATION_STANDARD\.md') {
|
||||
$failures.Add('AGENTS.md does not reference docs/DOCUMENTATION_STANDARD.md')
|
||||
}
|
||||
if ($agentContent -notmatch 'CODING_STANDARD\.md') {
|
||||
$failures.Add('AGENTS.md does not reference docs/CODING_STANDARD.md')
|
||||
}
|
||||
}
|
||||
|
||||
$requiredHeadings = @(
|
||||
'## Metadata',
|
||||
'## Purpose',
|
||||
'## Non-goals',
|
||||
'## Context and boundaries',
|
||||
'## Public API',
|
||||
'## Inputs and outputs',
|
||||
'## Data flow',
|
||||
'## Ownership, threading and resources',
|
||||
'## Errors, cancellation and recovery',
|
||||
'## Verification',
|
||||
'## Known gaps and risks',
|
||||
'## Source map'
|
||||
)
|
||||
|
||||
$modulesDir = Join-Path $Root 'docs\modules'
|
||||
$moduleCount = 0
|
||||
if (Test-Path -LiteralPath $modulesDir) {
|
||||
$moduleFiles = Get-ChildItem -LiteralPath $modulesDir -Filter '*.md' -File |
|
||||
Where-Object { $_.Name -notin @('README.md', 'TEMPLATE.md') }
|
||||
foreach ($file in $moduleFiles) {
|
||||
++$moduleCount
|
||||
$content = Get-Content -Raw -Encoding UTF8 -LiteralPath $file.FullName
|
||||
foreach ($heading in $requiredHeadings) {
|
||||
if (-not $content.Contains($heading)) {
|
||||
$failures.Add("docs/modules/$($file.Name): missing heading '$heading'")
|
||||
}
|
||||
}
|
||||
if ($content -notmatch '(?s)```mermaid\s+flowchart') {
|
||||
$failures.Add("docs/modules/$($file.Name): missing Mermaid data-flow diagram")
|
||||
}
|
||||
if ($content -notmatch '\| Status \|') {
|
||||
$failures.Add("docs/modules/$($file.Name): missing Status metadata")
|
||||
}
|
||||
if ($content -notmatch '\| Last verified \|') {
|
||||
$failures.Add("docs/modules/$($file.Name): missing Last verified metadata")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$documentationRoots = @('docs', 'targets', 'coordination')
|
||||
foreach ($relativeRoot in $documentationRoots) {
|
||||
$path = Join-Path $Root $relativeRoot
|
||||
if (-not (Test-Path -LiteralPath $path)) {
|
||||
continue
|
||||
}
|
||||
foreach ($file in Get-ChildItem -LiteralPath $path -Recurse -Filter '*.md' -File) {
|
||||
$content = Get-Content -Raw -Encoding UTF8 -LiteralPath $file.FullName
|
||||
foreach ($match in [regex]::Matches($content, '\[[^\]]+\]\(([^)#]+)(?:#[^)]+)?\)')) {
|
||||
$target = $match.Groups[1].Value
|
||||
if ($target -match '^(https?:|mailto:)') {
|
||||
continue
|
||||
}
|
||||
$resolved = Join-Path $file.DirectoryName $target
|
||||
if (-not (Test-Path -LiteralPath $resolved)) {
|
||||
$relativeFile = Resolve-Path -LiteralPath $file.FullName -Relative
|
||||
$failures.Add("$relativeFile`: broken relative link '$target'")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($failures.Count -gt 0) {
|
||||
foreach ($failure in $failures) {
|
||||
Write-Error $failure
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Output "Documentation check passed: module_specs=$moduleCount required_files=$($requiredFiles.Count)"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user