Compare commits

..

18 Commits

Author SHA1 Message Date
sindoring 1fd87b3132 обновление 2026-09-05 13:02:03 +04:00
sindoring bc4440562d обновление документации и сабмодулей 2026-08-12 15:53:15 +04:00
sindoring 5b73ddadc9 обновление аудита 2026-08-12 14:15:22 +04:00
sindoring a11eb50219 актуализация референсов 2026-08-12 14:02:08 +04:00
sindoring adc441ffca coord(M04): accept renderer roadmap migration 2026-08-02 17:42:18 +04:00
sindoring afbc4faab3 coord(M04): hand off renderer roadmap migration 2026-08-02 17:41:41 +04:00
sindoring f77f50c0f9 docs(M04): prioritize renderer fidelity milestone
Work-Package: M04-RND-FIDELITY-ROADMAP-001
Agent: sindo-main-codex-renderer-roadmap
Tests: coordination, documentation, target sequence, old-link and diff gates pass
Fidelity: original build-12340 captures are authoritative; Noggit is secondary reference
2026-08-02 17:41:18 +04:00
sindoring 4369dd3c91 coord(M04): claim renderer fidelity roadmap migration 2026-08-02 15:44:52 +04:00
sindoring fe6d0deab9 Record M03 closeout integration 2026-08-02 15:29:34 +04:00
sindoring 203d40bd0e Mark M03 renderer milestone complete 2026-08-02 15:28:40 +04:00
sindoring 3bf1c2c657 Complete M03 renderer closeout validation 2026-08-02 15:28:10 +04:00
sindoring d9d20cb53f qar: add M03 renderer closeout gates 2026-08-01 16:49:43 +04:00
sindoring ef6d324b4f coordination: claim M03 integrator closeout 2026-08-01 14:54:00 +04:00
sindoring f978a16806 targets: accept WMO scene instance factory
Work-Package: M03-RND-WMO-SCENE-INSTANCE-FACTORY-001
Agent: sindo-main-codex
Tests: post-merge WMO/placement/facade/internal-access/manifest/docs/coordination smoke passed
Fidelity: valid cached/live behavior preserved; invalid-root lifetime leak fixed
2026-08-01 10:51:35 +04:00
sindoring 541279ed45 merge: WMO scene instance factory
Work-Package: M03-RND-WMO-SCENE-INSTANCE-FACTORY-001
Agent: sindo-main-codex
Tests: 65/66 headless verifiers; proprietary ADT probe unavailable; checkpoint dry-run 7/7
Fidelity: valid cached/live behavior preserved; invalid-root leak fixed
2026-08-01 10:50:26 +04:00
sindoring 8da41dc17c coordination: hand off WMO scene instance factory
Work-Package: M03-RND-WMO-SCENE-INSTANCE-FACTORY-001
Agent: sindo-main-codex
Tests: documented in handoff
Fidelity: valid cached/live behavior retained; invalid-root leak fixed
2026-08-01 10:50:16 +04:00
sindoring 7e97b19095 render: extract WMO scene instance factory
Work-Package: M03-RND-WMO-SCENE-INSTANCE-FACTORY-001
Agent: sindo-main-codex
Tests: 65/66 headless verifiers; proprietary ADT probe unavailable; checkpoint dry-run 7/7; docs and coordination passed
Fidelity: valid cached/live naming, placement, validation and Resource identity preserved; invalid-root leak fixed
2026-08-01 10:49:56 +04:00
sindoring 07521ee6a4 coordination: claim WMO scene instance factory
Work-Package: M03-RND-WMO-SCENE-INSTANCE-FACTORY-001
Agent: sindo-main-codex
Tests: not run (coordination only)
Fidelity: behavior-preserving extraction planned
2026-08-01 10:42:14 +04:00
66 changed files with 2409 additions and 194 deletions
+28
View File
@@ -5,3 +5,31 @@
path = third_party/godot-cpp path = third_party/godot-cpp
url = https://github.com/godotengine/godot-cpp url = https://github.com/godotengine/godot-cpp
branch = 4.5 branch = 4.5
[submodule "reference/open-realm"]
path = reference/open-realm
url = https://github.com/corepunch/open-realm.git
branch = main
[submodule "reference/whoa"]
path = reference/whoa
url = https://github.com/whoahq/whoa
branch = master
[submodule "reference/WoWee"]
path = reference/WoWee
url = https://github.com/Kelsidavis/WoWee.git
branch = master
[submodule "reference/WowUnreal"]
path = reference/WowUnreal
url = https://github.com/Clancey/WowUnreal
branch = main
[submodule "reference/wow.export"]
path = reference/wow.export
url = https://github.com/Kruithne/wow.export.git
branch = main
[submodule "reference/blender-wow-studio/pywowlib"]
path = reference/blender-wow-studio-3.4-1.1.0_Experimental/io_scene_wmo/pywowlib
url = https://github.com/wowdev/pywowlib.git
branch = master
[submodule "reference/benilla"]
path = reference/benilla
url = https://github.com/samwhosung/benilla.git
branch = main
+62
View File
@@ -686,6 +686,42 @@ SKYBOX_MODEL ...
- полноценный liquid rendering там не реализован; - полноценный liquid rendering там не реализован;
- skybox/liquid не стоит напрямую переносить как готовый код. - skybox/liquid не стоит напрямую переносить как готовый код.
По WoWee (reviewed update `626243e937fb93965fa583a6507ed5a1aa7dda4b` →
`607ea3b8369851014721416293f8e95dfbe64eec`, 2026-09-05):
- сильнейший M04 signal — не новый renderer целиком, а узкие regression fixtures:
M2 global-sequence/color-alpha sampling, `$FSD` event timing, 8x8 liquid masks,
rotated WMO liquid projection и CPU vertex/shader interface checks;
- общий M2+skin/external-`.anim` resolver и selective animation loading полезны как
decomposition, но должны использовать существующие OpenWC repository/worker/
main-thread finalization boundaries;
- track sampler пока линейно обрабатывает Hermite/Bezier, а particle/ribbon path
содержит flame/smoke и orientation heuristics; это не fidelity oracle. Для
effect architecture Benilla остаётся более полным secondary reference;
- placement rotation order для наклонённых doodads в WoWee явно не решён.
OpenWC не меняет calibrated MDDF/MODF transforms без tilted build-12340 fixture;
- real-asset tests, которые превращают отсутствие assets в success, не считаются
evidence. OpenWC fixture обязан иметь provenance/hash и явно fail/skip-report;
- modified MIT license WoWee запрещает использование как основы/компонента
commercial video game без разрешения: используем только независимо проверенные
идеи и tests, не копируем/не вендорим код без legal review.
По Benilla (pinned research commit
`bc1a2428dd7e00ca8abbfb8f0bf53750dae7b123`):
- полезна граница `world renderer -> asset/formats` без зависимости от game/UI;
- M2 animation разделяет selection policy и renderer machinery: pose sampling,
parent-order composition, global sequences, billboard replacement, attachment
anchors и palette upload;
- particles и ribbons симулируются на CPU для temporal fidelity, но записывают
геометрию в один shared effect vertex/index stream с сортировкой, batching и
camera-relative upload вместо отдельных dynamic Mesh/Material на emitter;
- WMO portal flood, material pass ordering, fog/blend policy, effect lifecycle и
corpus/probe tooling являются полезными sources для M04 fixtures;
- это Vanilla 1.12.1/Bevy reference, не build-12340 oracle: record layouts,
shader flags, effect timing и performance должны быть повторно проверены в
Godot против оригинального WoW 3.3.5a.
По WoW 3.3.5a: По WoW 3.3.5a:
- старый клиент не рендерил все как modern physically based renderer; - старый клиент не рендерил все как modern physically based renderer;
@@ -1491,6 +1527,32 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
queues and permits remain loader-owned. Synthetic traversal timing is not queues and permits remain loader-owned. Synthetic traversal timing is not
private-asset visual, leak, GPU or p95/p99 evidence. private-asset visual, leak, GPU or p95/p99 evidence.
## 2026-08-01 WMO Scene Instance Factory Extraction
- `WmoSceneInstanceFactory` now owns cached PackedScene instantiation/currentness
validation and live-prototype duplication with shared basename/placement rules.
- Cached validation still precedes placement; live duplicates still skip the
scene-cache validator. Accepted descendant Resources retain exact identity.
- Invalid non-Node3D cached roots are now freed synchronously, closing an
error-path lifetime leak that normal scene-cache admission already prevents.
- Source lookup, ResourceLoader, runtime preparation, attachment, registry,
queues and permits remain loader-owned. Synthetic factory timing is not
private-asset visual, leak/GPU or p95/p99 evidence.
## 2026-08-02 M03 Renderer Closeout
- M03 preserves the M00 `High` topology and batching while enforcing four
CPU-only worker boundaries, fifteen main-thread finalization lanes and seven
explicit cache versions through the renderer closeout contract verifier.
- Performance acceptance uses exact-cache paired M00/M03 captures plus a second
ten-second window. A metric must exceed its unchanged 10% budget in both
protocols to be a repeatable regression; the closeout result is `0/84`.
- Native M2 startup no longer copies an ArrayMesh that is immediately discarded.
It creates an instance-local mesh, reapplies shared Materials and performs one
phased rebuild before attachment; `_ready()` is idempotent afterward.
- Checkpoint evidence is asset-backed but is not an original-client pixel-parity
claim. Long traversal and original-client approval remain release gates.
## Practical Rule For Future Work ## Practical Rule For Future Work
If something improves quality but creates visible hitch, it is not done. Move it to bake/cache/background work, split finalization over frames, or prewarm it before the player can see it. If something improves quality but creates visible hitch, it is not done. Move it to bake/cache/background work, split finalization over frames, or prewarm it before the player can see it.
@@ -0,0 +1,80 @@
# M03-QAR-INTEGRATOR-CLOSEOUT-001
<!-- OPENWC_CLAIM:M03-QAR-INTEGRATOR-CLOSEOUT-001:sindo-main-codex-m03-integrator:2026-08-03 -->
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-QAR-INTEGRATOR-CLOSEOUT-001:203d40b -->
## Owner
- Agent ID: `sindo-main-codex-m03-integrator`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex-m03-integrator/m03-closeout`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-closeout`
## Outcome
Close M03 only after an overall renderer performance comparison, cache-version
and main-thread-finalization contract gates, the complete autonomous verifier
suite, documentation/coordination gates and integrator review all pass.
## Non-goals
- Change renderer visuals, placements, materials, animations or quality policy.
- Add new renderer features or advance to M04/M08.
- Claim original-client visual parity beyond existing M00 evidence.
## Paths
- Exclusive: M03 closeout verifier/comparator, closeout specification, this claim
- Shared: renderer verification runner, renderer/module registries, `RENDER.md`,
`targets/03-renderer-facade.md`, `targets/README.md`
## Contracts and data
- Compare identical checkpoint/pass pairs against the accepted M00 report.
- Require matching environment/profile/cache state and exact cache version keys.
- Enforce the accepted 10% ceilings for p95, p99, max hitch, load time and memory.
- Verify GPU/SceneTree finalization remains main-thread and budget-gated by source.
- Keep all generated/private reports outside Git; commit only aggregate evidence.
## Dependencies
- Requires: all accepted M03 facade/planner/scheduler and terrain/M2/WMO/liquid packages
- Blocks: M03 DONE marker and activation of the next user-selected plan
## Verification
- Full cold/warm renderer capture; deterministic performance comparison; complete
headless verifier suite; project/editor parse; baseline manifest/checkpoint dry
run; dependency, documentation and coordination gates.
## Documentation deliverables
- Inline comparator/gate docs; closeout module specification with API/I/O,
ownership and sequence/dependency diagrams; renderer docs and final M03 Evidence.
## Status
- State: integrated
- Done: renderer closeout gates, exact-cache performance evidence, full verifier
suite, documentation, M03 DONE transition and M04 administrative activation
- Next: receive the user's plan correction before beginning M04 work
- Blocked by:
<!-- OPENWC_HANDOFF:READY:M03-QAR-INTEGRATOR-CLOSEOUT-001:203d40b -->
## Handoff
- Commits: `d9d20cb`, `3bf1c2c`, `203d40b`; fast-forwarded to `master`.
- Verification: all `67/67` autonomous Godot verifiers; M2 playback `20`
cases; renderer contracts `workers=4 frame_steps=15 cache_versions=7
nested_glb=1`; performance stability `84` metrics with `0` repeatable
regressions; documentation, coordination and diff gates passed.
- Fidelity: exact cache inventory and asset-backed checkpoints cover terrain,
ADT boundary, dense M2, large WMO, liquid, native animation and dusk sky.
This is extraction/performance evidence, not original-client pixel parity.
- Remaining risks: original-client visual approval, long-traversal descriptor
pressure, scaling native animation beyond CPU deformation and interruption of
already in-flight worker jobs remain later release concerns.
- Documentation: added the renderer closeout module specification with data-flow
and sequence diagrams; updated testing policy, renderer source maps, M2
animation ownership/sequence documentation, `RENDER.md` and M03 Evidence.
@@ -0,0 +1,90 @@
# M03-RND-WMO-SCENE-INSTANCE-FACTORY-001
<!-- OPENWC_CLAIM:M03-RND-WMO-SCENE-INSTANCE-FACTORY-001:sindo-main-codex:2026-08-03 -->
## Owner
- Agent ID: `sindo-main-codex`
- Target: M03 Renderer Facade and Safe Extraction
- Branch: `work/sindo-main-codex/m03-wmo-scene-instance-factory`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m03-wmo-scene-instance-factory`
## Outcome
Move WMO cached-scene instantiation/currentness validation and live-prototype
duplication with shared name/placement application into one main-thread factory.
## Non-goals
- Change cache lookup/admission, ResourceLoader or prototype construction.
- Change runtime Mesh/material/occluder/shadow preparation.
- Change attachment, placement registry, queues, permits or lifetime.
- Change scene cache version rules or placement formulas.
## Paths
- Exclusive: `src/render/wmo/wmo_scene_instance_factory.gd`,
`src/tools/verify_wmo_scene_instance_factory.gd`,
`docs/modules/wmo-scene-instance-factory.md`, this claim
- Shared: loader, adjacent WMO verifier/spec, renderer registry/specification,
`RENDER.md`, M03 Evidence
## Contracts and data
- Null scenes/prototypes and non-Node3D instantiation/duplication return null.
- Cached instances failing the injected currentness validator are synchronously freed.
- Accepted instances retain exact descendant Resource identities.
- Both paths apply `relative_path.get_file().get_basename()` and the exact
placement-resolver Transform3D.
- The factory returns detached roots and retains no Node or Resource.
## Dependencies
- Requires: accepted WMO placement resolver and runtime scene preparer
- Blocks: remaining loader-owned WMO cache/prototype orchestration cleanup
## Verification
- Synthetic cached/live type, validation, freeing, exact identity, name,
transform, dependency/order, ownership, source and bounded-timing contracts;
adjacent WMO/renderer gates and checkpoint dry-run.
## Documentation deliverables
- Inline API docs; module API/I/O/ownership; data-flow/sequence/dependency
diagrams; adjacent renderer docs and M03 Evidence.
## Status
- State: ready
- Done: implementation, verification and documentation
- Next: integrator review and merge
- Blocked by:
## Handoff
- Commit: `7e97b19` (`render: extract WMO scene instance factory`)
- Results: factory PASS `cases=41 iterations=1000 elapsed_ms=5.297`;
placement resolver dependency PASS `cases=10 iterations=20000
elapsed_ms=28.740`; suite `65/66` with no unexpected failures and only the
proprietary ADT placement probe unavailable; editor parse had zero script
diagnostics; checkpoint dry-run retained `7/7`; documentation passed with
`52` module specifications; coordination passed with `77` historical warnings.
- Fidelity: valid cached/live cache-validation distinction, basename, exact
Transform3D and descendant Resource identity are unchanged. Invalid non-Node3D
cached roots are now freed synchronously, fixing an error-path leak without
changing valid visible output.
- Remaining risks: instantiation/duplication remains synchronous; no private WMO
portal/room, visual, long-traversal, leak/GPU or p95/p99 evidence.
- Documentation: new full factory module specification; placement resolver
consumers/sequence, renderer registry/source map and `RENDER.md` updated.
<!-- OPENWC_HANDOFF:READY:M03-RND-WMO-SCENE-INSTANCE-FACTORY-001:7e97b19 -->
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-WMO-SCENE-INSTANCE-FACTORY-001:541279e -->
- Merge: `541279e` (`merge: WMO scene instance factory`)
- Post-merge: factory PASS `cases=41 iterations=1000 elapsed_ms=5.308`;
placement resolver `cases=10`/`31.207ms`; runtime preparation/finalization,
adjacent WMO services, shutdown, materials, facade, internal-access `30`,
manifest `7/7`, documentation `52` and coordination passed.
@@ -0,0 +1,95 @@
# M04-RND-FIDELITY-ROADMAP-001
<!-- OPENWC_CLAIM:M04-RND-FIDELITY-ROADMAP-001:sindo-main-codex-renderer-roadmap:2026-08-04 -->
<!-- OPENWC_INTEGRATION:ACCEPTED:M04-RND-FIDELITY-ROADMAP-001:afbc4fa -->
## Owner
- Agent ID: `sindo-main-codex-renderer-roadmap`
- Target: M04 Renderer Fidelity and Graphics Foundation
- Branch: `work/sindo-main-codex-renderer-roadmap/m04-renderer-fidelity-plan`
- Worktree: `C:\Users\sindo\open-wc-worktrees\m04-renderer-fidelity-plan`
## Outcome
Insert an evidence-driven renderer-fidelity milestone immediately after M03 and
shift the existing M04–M13 executable targets to M05–M14 without losing their
content, dependencies or status semantics.
## Non-goals
- Implement renderer behavior or claim visual parity in this package.
- Capture or commit proprietary original-client screenshots.
- Begin the shifted Editor, content, network or gameplay milestones.
## Paths
- Exclusive: `targets/04-renderer-fidelity.md`, this claim
- Shared/hotspot: all numbered target filenames and cross-target dependencies,
`targets/README.md`, `targets/DEVELOPMENT_ROADMAP.md`, `targets/roadmap/`,
`docs/ROADMAP.md`, renderer/testing/feature documentation where milestone IDs
or fidelity gates are normative
## Contracts and data
- M03 remains `DONE`; new M04 becomes the only `ACTIVE` target.
- Existing executable targets shift monotonically: old M04–M13 become M05–M14.
- The new renderer gate uses original build-12340 captures as the authoritative
oracle and treats Noggit as a secondary composition/reference tool.
- Proprietary images remain outside Git; manifests, metadata, hashes, metrics and
human approval records are repository evidence.
- `Blizzlike335` and opt-in enhanced/racing graphics remain explicit profiles.
## Dependencies
- Requires: completed M03 renderer facade and closeout contracts.
- Blocks: renderer-fidelity implementation and all shifted milestones.
## Verification
- Coordination and documentation gates; target marker/index consistency; link
and milestone-reference audit; `git diff --check`.
## Fidelity evidence
- Planning evidence only: exact local client build `3.3.5.12340`, seven existing
paired checkpoints and the user-proposed two-viewpoint-per-location corpus.
No new parity claim is made.
## Documentation deliverables
- Executable M04 target with reference, static, temporal, subsystem, performance
and acceptance gates; shifted target dependencies; development/subsystem
roadmap and testing/feature-map alignment.
## Status
- State: integrated
- Done: inserted M04 renderer fidelity, shifted old M04–M13 to M05–M14,
updated dependencies, normative roadmaps, profile boundaries and test policy
- Next: begin the M04 reference manifest/capture contract work package when the
user supplies or approves the original-client CSV/capture corpus
- Blocked by:
<!-- OPENWC_HANDOFF:READY:M04-RND-FIDELITY-ROADMAP-001:f77f50c -->
## Handoff
- Commit: `f77f50c` on
`work/sindo-main-codex-renderer-roadmap/m04-renderer-fidelity-plan`.
- Outcome: M03 remains `DONE`; new M04 is the only `ACTIVE` target; Editor,
content, server, world editor, network, gameplay, playable client, quest,
completeness and dungeon targets moved intact to M05–M14.
- Contracts: original build-12340 captures are authoritative; Noggit is a
secondary composition reference; proprietary pixels remain outside Git;
Blizzlike/Enhanced/Racing graphics profiles are explicit boundaries.
- Verification: coordination passed `targets=15 active=1`; documentation passed
`module_specs=53 required_files=7`; target sequence passed `M00–M14`; old
executable-target links `0`; `git diff --check` passed.
- Fidelity: planning and corpus provenance only; no renderer implementation or
new parity claim is included.
- Documentation: executable M04, target index/dependency graph, renderer
subsystem plan, high-level roadmap, architecture, testing/tooling/coding/
documentation policies and affected future milestone references updated.
- Cache/migration: no runtime/cache/data format changes. The exact CSV schema is
intentionally deferred to the first M04 contract work package.
+12
View File
@@ -9,6 +9,12 @@ OpenWC состоит из двух продуктов на общей плат
Оба продукта используют общие канонические модели, импорт данных и renderer preview, но не разделяют UI, lifecycle и права на изменение данных. Оба продукта используют общие канонические модели, импорт данных и renderer preview, но не разделяют UI, lifecycle и права на изменение данных.
Планируемый racing fork является третьим потребителем graphics foundation. Он
может использовать world streaming, materials, lighting, shadows, liquids,
characters, animation и effects contracts, но не зависит от WoW network,
gameplay state или proprietary asset repository. Его улучшения выбираются
отдельным `Racing` graphics profile и не меняют `Blizzlike335`.
## Архитектурные принципы ## Архитектурные принципы
1. Сервер авторитетен для боя, ресурсов, инвентаря, квестового прогресса и общего мира. 1. Сервер авторитетен для боя, ресурсов, инвентаря, квестового прогресса и общего мира.
@@ -57,6 +63,11 @@ TrinityCore/AzerothCore ◄── Network Adapter ◄── Runtime Client
Renderer получает `StreamingFocus`, `WorldVisualSnapshot` и presentation-команды. Он НЕ ДОЛЖЕН читать packets, SQL или gameplay input. Renderer получает `StreamingFocus`, `WorldVisualSnapshot` и presentation-команды. Он НЕ ДОЛЖЕН читать packets, SQL или gameplay input.
`GraphicsProfile` выбирается на composition boundary и задаёт material,
lighting, shadow, liquid, effects, distance и post-processing capabilities.
Внутренние shader/services не смешивают Blizzlike/Enhanced/Racing policy через
неявные глобальные switches.
### UI ### UI
Владеет login/realm/character screens, HUD и FrameXML/Lua-compatible presentation. UI читает immutable view models и отправляет intents. Lua API получает capability-based facade; прямой доступ к network, filesystem и editor API запрещён. Владеет login/realm/character screens, HUD и FrameXML/Lua-compatible presentation. UI читает immutable view models и отправляет intents. Lua API получает capability-based facade; прямой доступ к network, filesystem и editor API запрещён.
@@ -157,6 +168,7 @@ addons/
- `ServerSchemaAdapter` — inspect, import, diff, generate, validate. - `ServerSchemaAdapter` — inspect, import, diff, generate, validate.
- `ContentTypeDescriptor` — schema, inspector, validator, compiler. - `ContentTypeDescriptor` — schema, inspector, validator, compiler.
- `WorldRenderer` — streaming focus и entity presentation. - `WorldRenderer` — streaming focus и entity presentation.
- `GraphicsProfile` — explicit Blizzlike/Enhanced/Racing visual capabilities.
- `GameplaySystem` — commands/events без scene dependency. - `GameplaySystem` — commands/events без scene dependency.
- `EditorTool` — selection, command creation и gizmo, без прямой записи. - `EditorTool` — selection, command creation и gizmo, без прямой записи.
- `TestFixtureProvider` — обезличенные packets, DB snapshots и content fixtures. - `TestFixtureProvider` — обезличенные packets, DB snapshots и content fixtures.
+1 -1
View File
@@ -197,7 +197,7 @@ func request_streaming_tile_load(request: StreamingTileLoadRequest) -> void:
- Предпочитать именованные predicates длинным boolean expressions. - Предпочитать именованные predicates длинным boolean expressions.
- Branch по capability/profile должен быть локальным и типизированным. - Branch по capability/profile должен быть локальным и типизированным.
- Не распространять `if core == "azerothcore"` по проекту; использовать adapter/capability. - Не распространять `if core == "azerothcore"` по проекту; использовать adapter/capability.
- Не смешивать Blizzlike и Enhanced branches в каждом shader/service: выбирать profile/strategy на boundary. - Не смешивать Blizzlike, Enhanced и Racing branches в каждом shader/service: выбирать profile/strategy на boundary.
- Pattern/table-driven mapping предпочтительнее сотен одинаковых `if`, если таблица остаётся читаемой и валидируемой. - Pattern/table-driven mapping предпочтительнее сотен одинаковых `if`, если таблица остаётся читаемой и валидируемой.
## Comments ## Comments
+1 -1
View File
@@ -181,7 +181,7 @@ stateDiagram-v2
- Uniform/global parameter: coordinate/color space, range, units и producer. - Uniform/global parameter: coordinate/color space, range, units и producer.
- Material profile: supported WoW shader/blend modes и approximations. - Material profile: supported WoW shader/blend modes и approximations.
- Expensive branch/texture dependency имеет cost/fallback note. - Expensive branch/texture dependency имеет cost/fallback note.
- Blizzlike и Enhanced behavior документируются отдельно. - Blizzlike, Enhanced и Racing behavior документируются отдельно.
### Network codecs ### Network codecs
+1 -1
View File
@@ -61,7 +61,7 @@ Gameplay state, network session, renderer world, editor session, caches конк
- Visibility ranges/HLOD, occlusion и automatic mesh LOD использовать совместно по профилю. - Visibility ranges/HLOD, occlusion и automatic mesh LOD использовать совместно по профилю.
- Unique materials/textures минимизировать; descriptor/resource counts являются budget metric. - Unique materials/textures минимизировать; descriptor/resource counts являются budget metric.
- RenderingServer RIDs имеют явного владельца и освобождаются в deterministic shutdown test. - RenderingServer RIDs имеют явного владельца и освобождаются в deterministic shutdown test.
- Shader/material profiles разделяют Blizzlike и Enhanced; runtime не компилирует тяжёлые варианты при пересечении ADT boundary. - Shader/material profiles разделяют Blizzlike, Enhanced и Racing; runtime не компилирует тяжёлые варианты при пересечении ADT boundary.
## EditorPlugin lifecycle ## EditorPlugin lifecycle
+15 -1
View File
@@ -2,6 +2,11 @@
Reference-код используется для исследования форматов, поведения и архитектурных вариантов. Он не определяет API OpenWC и не копируется без проверки лицензии, корректности и соответствия Godot. Reference-код используется для исследования форматов, поведения и архитектурных вариантов. Он не определяет API OpenWC и не копируется без проверки лицензии, корректности и соответствия Godot.
Актуальные branches и pinned commits локальных Git-референсов зафиксированы в
[`reference/README.md`](../reference/README.md#git-reference-revisions). Gitlinks
точно фиксируют ревизию, а `.gitmodules` задаёт canonical remote и ветку
для контролируемого обновления.
## Основные источники ## Основные источники
### OpenWC renderer ### OpenWC renderer
@@ -17,12 +22,13 @@ Reference-код используется для исследования фор
### WoWee ### WoWee
- Исследование обновлено до `master` commit `607ea3b8369851014721416293f8e95dfbe64eec` (2026-09-05), относительно прежнего reviewed pin `8456c236b57140e98667d6d8188f5cd1cc226daf`.
- `reference/WoWee/docs/architecture.md` — разделение renderer/network/game/UI/pipeline. - `reference/WoWee/docs/architecture.md` — разделение renderer/network/game/UI/pipeline.
- `reference/WoWee/tools/editor/FORMAT_SPEC.md` — open formats, coordinates, collision, packaging и SQL export. - `reference/WoWee/tools/editor/FORMAT_SPEC.md` — open formats, coordinates, collision, packaging и SQL export.
- `reference/WoWee/TESTING.md` — единая точка запуска тестов, fixtures, sanitizers и CI discipline. - `reference/WoWee/TESTING.md` — единая точка запуска тестов, fixtures, sanitizers и CI discipline.
- `reference/WoWee/EXPANSION_GUIDE.md` — protocol/data profile separation. - `reference/WoWee/EXPANSION_GUIDE.md` — protocol/data profile separation.
Используем: детерминированные authoring formats, headless parity, adapter profiles и validation-first pipeline. Не принимаем автоматически конкретные форматы или заявленную полноту реализации. Особенно полезны: WotLK M2 track/footstep fixtures, liquid mask/surface-grid tests, shader-interface checks, retained widget tree, единый XML→Lua `CreateFrame` path, поэлементный takeover default UI и большая система headless/static FrameXML/Lua audits. Не переносим монолитный Lua/game binding, unknown-API fallback, Vulkan-specific ownership или native-padding `.w*` formats. Particle/render rules, placement rotations и UI semantics требуют независимых build-12340 fixtures; часть real-asset tests WoWee пропускает отсутствие assets как success. Лицензия содержит дополнительный запрет commercial-game use, поэтому WoWee остаётся research-only reference без копирования или вендоринга кода. Полная evaluation card: [`TOOLING_CATALOG.md`](TOOLING_CATALOG.md#wowee--карточка-референса).
### Noggit Red ### Noggit Red
@@ -51,6 +57,14 @@ Reference-код используется для исследования фор
Не переносим напрямую старый browser stack, WebSocket proxy, React/Three.js abstractions или pipeline server. Заявленное поведение проверяем по TrinityCore/AzerothCore и оригинальному клиенту; proof-of-concept не является спецификацией полноты. Не переносим напрямую старый browser stack, WebSocket proxy, React/Three.js abstractions или pipeline server. Заявленное поведение проверяем по TrinityCore/AzerothCore и оригинальному клиенту; proof-of-concept не является спецификацией полноты.
### Benilla
- [`reference/benilla`](../reference/benilla) ([upstream](https://github.com/samwhosung/benilla)) — pinned research submodule с независимым клиентом WoW 1.12.1 build 5875 на Rust/Bevy; исследование OpenWC зафиксировано на commit `bc1a2428dd7e00ca8abbfb8f0bf53750dae7b123`.
- Renderer references: M2 pose/palette animation, global sequences, material/pass ordering, общий dynamic-effect stream, particles, model particles, ribbons, WMO portal visibility, lighting, sky/weather и streaming.
- UI references: отдельный engine-free TOC/FrameXML/Lua core, template/layout/widget model, deterministic event/`OnUpdate` ordering, legacy+modern handler arguments, sandbox, SavedVariables и большой набор compatibility tests.
Используем decomposition, algorithms, diagnostics и test ideas. Не переносим Bevy/ECS architecture и не считаем Vanilla behavior доказательством WotLK: M2/DBC/layout/API/security differences повторно проверяются по WoW 3.3.5a build 12340. Benilla пока не является oracle для third-party addons или secure/taint semantics; его Lua 5.1 runtime содержит compatibility work для Lua 5.0 target. Полная evaluation card находится в [`TOOLING_CATALOG.md`](TOOLING_CATALOG.md#benilla--карточка-референса).
### recast-rs ### recast-rs
- [`wowemulation-dev/recast-rs`](https://github.com/wowemulation-dev/recast-rs) — Rust-порт Recast/Detour: navmesh generation, tiled pathfinding, spatial queries, crowd simulation и dynamic obstacles. - [`wowemulation-dev/recast-rs`](https://github.com/wowemulation-dev/recast-rs) — Rust-порт Recast/Detour: navmesh generation, tiled pathfinding, spatial queries, crowd simulation и dynamic obstacles.
+27 -14
View File
@@ -18,7 +18,20 @@
Готово, когда render sandbox сохраняет текущее качество, а domain тестируется без scene tree. Готово, когда render sandbox сохраняет текущее качество, а domain тестируется без scene tree.
## M1 — Content Project и Editor shell ## M1 — Renderer fidelity и graphics foundation
- Original-client build-12340 corpus: две базовые позиции выбранной локации,
settings/time/weather/camera metadata, static и temporal evidence.
- Точные color, terrain, M2/WMO material, sky/fog/weather и shadow rules.
- MH2O/MCLQ/MLIQ liquids, GPU animation, characters, particles и ribbons.
- Раздельные `Blizzlike335` и opt-in `Enhanced/Racing` graphics profiles.
- Paired semantic-region comparison, human approval и traversal/GPU budgets.
Готово, когда утверждённая capture matrix не содержит неклассифицированных
визуально различимых gaps, а общие graphics contracts пригодны клиенту, Editor
preview и racing fork.
## M2 — Content Project и Editor shell
- `addons/openwc_editor` с workspace и docks. - `addons/openwc_editor` с workspace и docks.
- Content Project schema, stable IDs, save/load/migration. - Content Project schema, stable IDs, save/load/migration.
@@ -28,7 +41,7 @@
Готово, когда небольшой synthetic project можно создать, изменить, undo, перезапустить Editor и получить идентичное состояние. Готово, когда небольшой synthetic project можно создать, изменить, undo, перезапустить Editor и получить идентичное состояние.
## M2 — Server inspector и adapters ## M3 — Server inspector и adapters
- TrinityCore/AzerothCore connection profiles. - TrinityCore/AzerothCore connection profiles.
- Schema detection и capabilities. - Schema detection и capabilities.
@@ -38,7 +51,7 @@
Готово, когда одна сущность round-trip проходит оба поддерживаемых adapter profile без молчаливой потери данных. Готово, когда одна сущность round-trip проходит оба поддерживаемых adapter profile без молчаливой потери данных.
## M3 — World Editor MVP ## M4 — World Editor MVP
- Map viewport и coordinate overlays. - Map viewport и coordinate overlays.
- Server spawn visualization. - Server spawn visualization.
@@ -48,7 +61,7 @@
Готово, когда NPC размещается в Editor и появляется на тестовом core в ожидаемой позиции. Готово, когда NPC размещается в Editor и появляется на тестовом core в ожидаемой позиции.
## M4 — Quest vertical slice ## M5 — Quest vertical slice
- Quest form и chain graph. - Quest form и chain graph.
- Kill/collect/explore objectives, giver/ender, rewards и localization. - Kill/collect/explore objectives, giver/ender, rewards и localization.
@@ -57,7 +70,7 @@
Готово, когда созданный в Editor квест полностью проходится клиентом. Готово, когда созданный в Editor квест полностью проходится клиентом.
## M5 — Playable network client ## M6 — Playable network client
- Auth, realm, character selection и world session. - Auth, realm, character selection и world session.
- Entity/update fields и world spawn. - Entity/update fields и world spawn.
@@ -66,7 +79,7 @@
Готово, когда клиент стабильно входит в мир, перемещается и видит синхронизированные entities. Готово, когда клиент стабильно входит в мир, перемещается и видит синхронизированные entities.
## M6 — Core gameplay ## M7 — Core gameplay
- Combat/spells/auras/death. - Combat/spells/auras/death.
- Inventory/equipment/loot/vendors. - Inventory/equipment/loot/vendors.
@@ -76,7 +89,7 @@
Готово, когда базовый leveling loop проходит без внешнего клиента. Готово, когда базовый leveling loop проходит без внешнего клиента.
## M7 — Dungeon authoring ## M8 — Dungeon authoring
- DungeonPackage, encounters, triggers, doors и spawn groups. - DungeonPackage, encounters, triggers, doors и spawn groups.
- SmartAI/script skeleton generation. - SmartAI/script skeleton generation.
@@ -85,7 +98,7 @@
Готово, когда custom dungeon собирается, разворачивается и проходится группой на test core. Готово, когда custom dungeon собирается, разворачивается и проходится группой на test core.
## M8 — Compatibility и completeness ## M9 — Compatibility и completeness
- Feature matrix WoW 3.3.5a. - Feature matrix WoW 3.3.5a.
- Addon compatibility tiers. - Addon compatibility tiers.
@@ -98,17 +111,17 @@
При равной ценности порядок такой: При равной ценности порядок такой:
1. безопасность данных и воспроизводимость; 1. безопасность данных и воспроизводимость;
2. корректность протокола и authoritative state; 2. original-client renderer fidelity и frame pacing текущего M04;
3. пользовательский vertical slice; 3. корректность протокола и authoritative state;
4. diagnostics и testability; 4. пользовательский vertical slice;
5. frame pacing; 5. diagnostics и testability;
6. визуальная точность и polish. 6. opt-in визуальные улучшения после Blizzlike evidence.
## Не делать раньше времени ## Не делать раньше времени
- прямую запись в production DB; - прямую запись в production DB;
- универсальный visual scripting для любой C++ механики; - универсальный visual scripting для любой C++ механики;
- массовую реализацию Lua API без работающего UI slice; - массовую реализацию Lua API без работающего UI slice;
- большой rewrite существующего renderer; - полный custom renderer до bounded shader/backend spike и profiler evidence;
- multi-expansion abstraction до устойчивого профиля 3.3.5a; - multi-expansion abstraction до устойчивого профиля 3.3.5a;
- proprietary asset packaging в репозитории. - proprietary asset packaging в репозитории.
+3 -3
View File
@@ -44,8 +44,8 @@ ID записывается в claim, ветке, PR/MR и handoff. Нельзя
```text ```text
M01-FND-COORDS-001 M01-FND-COORDS-001
M03-RND-SCHEDULER-001 M03-RND-SCHEDULER-001
M08-NET-SRP-001 M09-NET-SRP-001
M12-UIA-LUA-SPIKE-001 M13-UIA-LUA-SPIKE-001
``` ```
Program codes определены в [`../targets/DEVELOPMENT_ROADMAP.md`](../targets/DEVELOPMENT_ROADMAP.md). Program codes определены в [`../targets/DEVELOPMENT_ROADMAP.md`](../targets/DEVELOPMENT_ROADMAP.md).
@@ -214,7 +214,7 @@ Merge order:
```text ```text
fnd(M01): add canonical coordinate mapper fnd(M01): add canonical coordinate mapper
net(M08): decode auth challenge safely net(M09): decode auth challenge safely
rnd(M03): extract streaming target planner rnd(M03): extract streaming target planner
test(M00): add paired checkpoint manifest test(M00): add paired checkpoint manifest
``` ```
+36
View File
@@ -54,6 +54,42 @@
- dense WMO/M2, water, character equipment и UI scale matrices. - dense WMO/M2, water, character equipment и UI scale matrices.
- navmesh overlay checkpoints и bake/query budgets для больших tiles/dungeons. - navmesh overlay checkpoints и bake/query budgets для больших tiles/dungeons.
Для M03 renderer closeout сравнение выполняется на точных M00/M03 revisions с
одинаковыми viewport, rendering backend и полным cache inventory. Короткий
протокол агрегирует повторные captures медианой каждого показателя; независимый
протокол использует увеличенное десятисекундное окно. Регрессия считается
воспроизводимой, только если один и тот же checkpoint/pass/metric превышает
неизменённый 10% budget в обоих протоколах:
```powershell
tools/compare_render_performance.ps1 -BaselineReport <m00-reports> -CandidateReport <m03-reports> -OutputReport <repeated.json>
tools/compare_render_performance.ps1 -BaselineReport <m00-long.json> -CandidateReport <m03-long.json> -OutputReport <long.json>
tools/verify_render_performance_stability.ps1 -RepeatedSampleComparison <repeated.json> -LongWindowComparison <long.json> -OutputReport <stability.json>
```
Локальные превышения одного протокола сохраняются как diagnostics; gate падает
только на повторяемой регрессии. Полный контракт и схема evidence описаны в
[`modules/renderer-closeout-verification.md`](modules/renderer-closeout-verification.md).
M04 renderer fidelity использует оригинальный клиент build 12340 как
authoritative visual oracle. Noggit допускается как дополнительный reference
композиции/placements и Editor UX, но не подтверждает lighting, shadows, liquids,
materials, animation или effects.
Reference corpus импортируется через versioned CSV manifest. Для каждой
выбранной локации базово снимаются `wide` и `ground` viewpoints; отдельные
specialized captures добавляются для уникальных interior, liquid, shadow,
character и effect policies. Capture metadata фиксирует WoW/server coordinates,
доступные camera fields, time, weather, graphics profile, viewport, artifact
name и provenance. Отсутствующее значение хранится как `Unknown`, а не
восстанавливается предположением.
Proprietary screenshots/video остаются вне Git. Repository evidence включает
schema, SHA-256, static/temporal metrics, semantic region classification и human
approval. Static parity требует согласованных geometry/framing и material/light/
shadow/liquid regions; temporal parity отдельно проверяет phase, duration,
trajectory, UV motion и emitter lifetime.
### Navigation compatibility ### Navigation compatibility
- Golden synthetic meshes проверяют slope, climb, radius erosion, holes, tiled seams и off-mesh connections. - Golden synthetic meshes проверяют slope, climb, radius erosion, holes, tiled seams и off-mesh connections.
+40 -2
View File
@@ -40,13 +40,15 @@ Decision/ADR:
| OpenWC native loaders | ADOPTED | MPQ/BLP/ADT/WDT/M2/WMO | Текущий import/render pipeline | Неполная fidelity форматов | | OpenWC native loaders | ADOPTED | MPQ/BLP/ADT/WDT/M2/WMO | Текущий import/render pipeline | Неполная fidelity форматов |
| StormLib | ADOPTED | MPQ | Чтение архивов через native extension | Version/license/update audit | | StormLib | ADOPTED | MPQ | Чтение архивов через native extension | Version/license/update audit |
| WowUnreal | REFERENCE | Полный клиент | Coverage, acceptance criteria, networking/UI research | Unreal-specific design | | WowUnreal | REFERENCE | Полный клиент | Coverage, acceptance criteria, networking/UI research | Unreal-specific design |
| WoWee | REFERENCE | Клиент/editor/formats | Architecture, editor workflows, tests, open formats | Заявления требуют независимой проверки | | WoWee | REFERENCE | WotLK client/render/UI/editor | M2/liquid fixtures, retained FrameXML UI, Lua diagnostics, progressive UI takeover и authoring validation | Modified MIT запрещает commercial-game use; fidelity claims требуют build-12340 oracle |
| Noggit Red | REFERENCE | World editor | Terrain/placement UX, UID workflows | Не Godot architecture | | Original WoW 3.3.5a build 12340 client | ADOPTED | Visual/behavior oracle | Private static/temporal capture corpus, settings and paired comparison | Proprietary artifacts remain outside Git; camera/settings provenance must be explicit |
| Noggit Red | REFERENCE | World editor | Terrain/placement UX, UID workflows и secondary render-composition reference | Не Godot architecture; не authoritative lighting/material/shadow/liquid oracle |
| open-realm | REFERENCE | Formats/runtime | Независимая проверка parsers/render behavior | Другая архитектура и coverage | | open-realm | REFERENCE | Formats/runtime | Независимая проверка parsers/render behavior | Другая архитектура и coverage |
| whoa | REFERENCE | Client behavior | 3.3.5a runtime semantics и fixtures | Лицензия и переносимость отдельных решений | | whoa | REFERENCE | Client behavior | 3.3.5a runtime semantics и fixtures | Лицензия и переносимость отдельных решений |
| wow.export | REFERENCE | Asset conversion | M2/WMO/material/export edge cases | Web-specific pipeline | | 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 | | 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, неполный клиент | | [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, неполный клиент |
| [Benilla](https://github.com/samwhosung/benilla) | REFERENCE | WoW 1.12.1 client/render/UI | M2 GPU animation, materials, particles/ribbons, WMO portals, FrameXML/Lua architecture и compatibility-test ideas | Vanilla build 5875 и Bevy-specific implementation не доказывают WotLK build-12340 fidelity |
| [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 | | [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 | | [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 | | [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 |
@@ -57,6 +59,23 @@ Decision/ADR:
| [Keira3](https://github.com/azerothcore/Keira3) | REFERENCE | AzerothCore DB editor | Field semantics, SQL generation и DB editor UX | AGPL; schema-specific web architecture | | [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 | | [WowBench](https://sourceforge.net/projects/wowbench/) | REFERENCE | WoW UI/API | Offline XML/Lua API emulation и addon test ideas | Старый и неполный implementation |
## WoWee — карточка референса
- **Name / URL:** [WoWee](https://github.com/WoWee-Dev/WoWee), локально `reference/WoWee`.
- **Status:** `REFERENCE`; код не подключается как dependency и не вендорится.
- **Reviewed update:** `master` от `626243e937fb93965fa583a6507ed5a1aa7dda4b` до `8456c236b57140e98667d6d8188f5cd1cc226daf` (2026-08-12): 2557 commits. Raw diff содержит 1622 files, 164940 additions и 1595923 deletions, но объём deletions в основном создают удалённые generated SQL/data/build artifacts; полезный signal сосредоточен в renderer, UI/FrameXML, pipeline и tests.
- **Checkout refresh:** текущий upstream checkout — `607ea3b8369851014721416293f8e95dfbe64eec` (2026-09-05). Это обновление pins не является новой fidelity evaluation; релевантные изменения требуют отдельного bounded review перед использованием.
- **Renderer findings:** новый общий M2 track sampler отделяет global-sequence time от animation sequence и добавляет finite-value guards, но Hermite/Bezier пока линейно аппроксимируются. Централизованный M2+skin/external-`.anim` loader и selective animation loading полезны как pattern для границы parser/resolver. Новые water-mask и water-surface-grid tests фиксируют LSB-first 8x8 chunk masks, solid fallback, rotated WMO liquid projection, inclusive far edge и degenerate axes. Vertex-layout tests сверяют CPU declarations с shader inputs. GPU lifetime/deferred-release решения полезны концептуально, но Vulkan implementation не переносится в Godot.
- **Animation/effects evidence:** WotLK asset tests покрывают color/alpha tracks, независимый wrap global sequences и `$FSD` footstep events. Отсутствующие private assets в части тестов дают `SUCCEED`, поэтому зелёный run не доказывает, что fixture реально исполнялся. Particle/ribbon delta в основном добавляет sampling, batching, descriptor reuse и diagnostics; flame/smoke/ribbon поведение содержит эвристики и не заменяет build-12340 capture oracle. Для архитектуры shared effect stream Benilla остаётся более сильным secondary reference.
- **Placement warning:** WoWee прямо оставляет rotation order наклонённых doodad placements нерешённым; имеющийся test различает только upright yaw. OpenWC не меняет calibrated transforms без tilted `MDDF`/`MODF` fixture из build 12340.
- **UI/FrameXML findings:** retained widget tree отделён от renderer, сохраняет WoW bottom-left/y-up coordinates до единственного draw-boundary flip и покрывает anchors, draw order, hit testing, visibility, scroll, controls и ownership headless tests. XML компилируется в Lua и проходит тот же `CreateFrame`/template path, что Lua-created widgets. Progressive takeover передаёт отдельные default-UI элементы FrameXML с явными bridges для portrait/model/minimap/world-map content. Это сильная модель поэтапной миграции M05, но не готовая архитектура OpenWC.
- **Lua/tooling findings:** runtime ограничивает стандартные библиотеки, ставит instruction-hook timeout, копирует listener list перед dispatch, ограничивает event recursion, нормализует event argument types и диагностирует stack/source/line. Большой headless corpus и статические audits проверяют XML emission, templates, `$parent`, handler arity, event order/arity, missing APIs, nil arithmetic, globals и keybinding takeover. Однако `lua_engine.cpp` монолитен, unknown-API fallback маскирует отсутствующие контракты, а настоящих taint/secure execution semantics нет; OpenWC сохраняет отдельный `LuaRuntime`, view models/intents и fail-closed compatibility tiers.
- **Pipeline/editor findings:** общий bounded binary-I/O layer, finite vertex sanitization, DXT block tests, streaming manifest parse и единые validation/save reports полезны как test/validation patterns. Собственные `.w*` formats зависят от native padding/endianness и не подходят как canonical portable OpenWC artifacts без отдельной спецификации.
- **License/data risk:** repository LICENSE — MIT с дополнительным запретом использовать software как основу или компонент commercial video games без письменного разрешения; original music имеет отдельный all-rights-reserved notice. Поэтому допустимы research, decomposition и независимо реализованные fixtures; копирование/вендоринг кода требует предварительного legal review.
- **Fidelity limits:** проект WotLK-aware и использует build 12340, но остаётся active WIP. В исследованном diff нет систематического paired original-client visual corpus; часть tests повторяет internal implementation, а README перечисляет runtime regressions. WoWee — источник гипотез и test cases, не oracle. Oracle OpenWC остаётся оригинальный клиент 3.3.5a build 12340.
- **Bounded adoption:** для M04 приоритетны обязательные (не silent-skip) build-12340 fixtures для M2 global-sequence/color-alpha sampling, MH2O/MCLQ/MLIQ masks и rotated WMO water, CPU-mesh/shader interface verification и tilted placement order. Для M05 — engine-free widget/layout core, единый XML/Lua creation path, progressive takeover и headless audit taxonomy. Любое поведение принимается только после paired capture/fixture с provenance и hash.
- **Decision / ADR:** `REFERENCE`, не dependency. ADR нужен, если WoWee-inspired решение меняет публичный renderer/UI/Lua contract, artifact schema, engine boundary или вводит third-party code.
## rilua — карточка кандидата ## rilua — карточка кандидата
- **Problem solved:** Lua 5.1.1 VM, bytecode, embedding и официальный compatibility corpus для addon runtime. - **Problem solved:** Lua 5.1.1 VM, bytecode, embedding и официальный compatibility corpus для addon runtime.
@@ -77,6 +96,25 @@ Decision/ADR:
- **Known gaps:** proof-of-concept, неполный gameplay/UI, browser WebSocket proxy вместо native TCP, устаревший JS ecosystem (React 0.14/Three.js 0.77 era), архитектура не переносится напрямую в Godot. - **Known gaps:** proof-of-concept, неполный gameplay/UI, browser WebSocket proxy вместо native TCP, устаревший JS ecosystem (React 0.14/Three.js 0.77 era), архитектура не переносится напрямую в Godot.
- **Decision policy:** использовать для cross-check и test ideas; не добавлять Node/Web dependencies и не копировать browser-specific abstractions в OpenWC. - **Decision policy:** использовать для cross-check и test ideas; не добавлять Node/Web dependencies и не копировать browser-specific abstractions в OpenWC.
## Benilla — карточка референса
- **Name / URL:** [samwhosung/benilla](https://github.com/samwhosung/benilla), локально `reference/benilla`.
- **Status:** `REFERENCE`; исходники доступны как pinned research submodule, но не подключены к build/runtime как dependency и не вендорятся.
- **Pinned research revision:** `bc1a2428dd7e00ca8abbfb8f0bf53750dae7b123` (проверен 2026-09-05). Репозиторий публикуется автором как squashed snapshots из private tree, поэтому при повторном исследовании revision фиксируется заново.
- **Problem studied:** полный независимый клиент WoW 1.12.1 build 5875 на Rust/Bevy, включая streamed world renderer, M2 animation/materials, WMO portal visibility, liquids/sky/weather, particles/ribbons/spell visuals и собственный TOC/FrameXML/Lua UI runtime.
- **Planned boundary:** использовать только как независимый источник алгоритмов, decomposition, diagnostics и test cases. OpenWC сохраняет свои `WorldRenderFacade`, Godot main-thread/finalization boundaries, `LuaRuntime`, immutable UI view models/intents и build-12340 contracts; прямой Bevy/ECS или Rust-type bridge не вводится.
- **Useful renderer source map:** `crates/benilla-world/src/model_render*`, `rig_anim*`, `rig_palette*`, `particles*`, `ribbons.rs`, `wmo_portal*`, `lighting*`, `weather*`, `terrain_stream*` и `shaders/wow_effect.wgsl`; format-side M2 animation/particle/ribbon tracks находятся в `crates/benilla-formats/src/models/anim.rs`, `particles.rs` и `ribbons.rs`.
- **Useful UI/Lua source map:** engine-free crate `crates/benilla-ui`: `toc.rs`, `framexml.rs`, `loader/*`, `layout.rs`, `widget/*`, `order.rs`, `script/event.rs`, `script/tick.rs`, `script/saved.rs`, sandbox/stdlib bindings и compatibility tests. Особенно полезны сохранение document/load order, bottom-up `OnLoad`, nested-safe restore legacy globals `this/event/argN`, deterministic event/`OnUpdate` order и разделение plain host state от engine adapter.
- **License:** `MIT OR Apache-2.0`; конкретное заимствование кода требует сохранения license/attribution и отдельной проверки совместимости с лицензиями OpenWC и third-party dependencies.
- **Platforms/toolchain:** stable Rust, Bevy `0.18.1`, `mlua 0.11` с vendored Lua 5.1 и локальным `lua-src` patch pipeline. Это research stack, не предлагаемый OpenWC toolchain.
- **Fidelity evidence:** исходники содержат подробные build-5875 byte-law annotations, corpus tests, parser fixtures и temporal/render probes. Сам приватный reverse-engineering corpus не входит в repository, а опубликованный код ориентирован на Vanilla 1.12.1, поэтому ни один его результат сам по себе не является evidence для WoW 3.3.5a build 12340.
- **Known gaps:** README прямо ограничивает Lua built-in UI и пока не заявляет third-party addon support; taint/secure execution отсутствуют; runtime использует Lua 5.1 с точечными Lua-5.0 compatibility patches для Vanilla; renderer и scheduling зависят от Bevy render phases/ECS. Layouts, M2 records, shader flags, spell visuals, UI API и security semantics должны повторно проверяться для WotLK.
- **Security/data risks:** Lua sandbox удаляет filesystem/OS/package/debug/native reach и принимает только text chunks, но без taint/protected-action модели этого недостаточно для 3.3.5a addons. Proprietary client assets и private RE artifacts не копируются; тесты OpenWC используют легально полученные локальные data и разрешённые metadata/fixtures.
- **Performance evidence:** Benilla документирует collapsed rig pose/palette arrays вместо десятков тысяч bone entities и один shared effect vertex/index stream вместо per-emitter dynamic meshes; source comments приводят локальные measurements (включая около 145 mesh changes/frame до shared stream). Эти числа не воспроизведены на Godot и служат только гипотезой для bounded spike.
- **Spike and acceptance criteria:** (1) renderer — на build-12340 M2 fixtures сравнить bone palette/global sequences, material ordering, particle plane/sphere/spline emitters, head/tail/model particles и ribbons по phase/duration/lifetime; затем проверить Godot-friendly shared effect buffer/pool без нарушения M03 budgets; (2) UI — прогнать WotLK TOC/FrameXML/Lua corpus на document order, inheritance, anchors, handler calling convention, event order, `OnUpdate`, SavedVariables, restricted libraries и error text; secure actions/taint имеют отдельный обязательный corpus. Любое принятое решение требует original-client paired evidence, p50/p95/p99 и regression fixtures.
- **Pinned version/update policy:** исследование и ссылки привязаны к указанному commit. Обновление проводится вручную по diff релевантных directories; moving `main` не становится новым oracle автоматически.
- **Decision / ADR:** `REFERENCE`, не dependency. ADR требуется только если Benilla-inspired решение меняет публичный renderer/effects/UI/Lua contract, cache/schema, engine boundary или добавляет dependency.
## recast-rs — карточка кандидата ## recast-rs — карточка кандидата
- **Problem solved:** Recast navmesh generation, Detour queries, tiled navigation и dynamic obstacles. - **Problem solved:** Recast navmesh generation, Detour queries, tiled navigation и dynamic obstacles.
+2
View File
@@ -52,9 +52,11 @@
| WMO runtime Mesh finalizer | Implemented extraction | [`wmo-runtime-mesh-finalizer.md`](wmo-runtime-mesh-finalizer.md) | | WMO runtime Mesh finalizer | Implemented extraction | [`wmo-runtime-mesh-finalizer.md`](wmo-runtime-mesh-finalizer.md) |
| WMO render group materializer | Implemented extraction | [`wmo-render-group-materializer.md`](wmo-render-group-materializer.md) | | WMO render group materializer | Implemented extraction | [`wmo-render-group-materializer.md`](wmo-render-group-materializer.md) |
| WMO runtime scene preparer | Implemented extraction | [`wmo-runtime-scene-preparer.md`](wmo-runtime-scene-preparer.md) | | WMO runtime scene preparer | Implemented extraction | [`wmo-runtime-scene-preparer.md`](wmo-runtime-scene-preparer.md) |
| WMO scene instance factory | Implemented extraction | [`wmo-scene-instance-factory.md`](wmo-scene-instance-factory.md) |
| Third-person camera | Implemented | [`third-person-camera.md`](third-person-camera.md) | | Third-person camera | Implemented | [`third-person-camera.md`](third-person-camera.md) |
| Character presentation | Implemented boundary / Partial fidelity | [`character-presentation.md`](character-presentation.md) | | Character presentation | Implemented boundary / Partial fidelity | [`character-presentation.md`](character-presentation.md) |
| Renderer | Partial | [`world-renderer.md`](world-renderer.md), [`../../RENDER.md`](../../RENDER.md) | | Renderer | Partial | [`world-renderer.md`](world-renderer.md), [`../../RENDER.md`](../../RENDER.md) |
| Renderer closeout verification | Implemented | [`renderer-closeout-verification.md`](renderer-closeout-verification.md) |
| World entity presentation | Implemented boundary / Prototype visuals | [`world-entity-presentation.md`](world-entity-presentation.md) | | World entity presentation | Implemented boundary / Prototype visuals | [`world-entity-presentation.md`](world-entity-presentation.md) |
| Streaming target planner | Implemented | [`streaming-target-planner.md`](streaming-target-planner.md) | | Streaming target planner | Implemented | [`streaming-target-planner.md`](streaming-target-planner.md) |
| Render budget scheduler | Implemented | [`render-budget-scheduler.md`](render-budget-scheduler.md) | | Render budget scheduler | Implemented | [`render-budget-scheduler.md`](render-budget-scheduler.md) |
+1 -1
View File
@@ -241,7 +241,7 @@ runtime data migration.
| Current starter outfit | Partial | Existing resolver reused unchanged | Needs extracted DBC fixture and client comparison | | Current starter outfit | Partial | Existing resolver reused unchanged | Needs extracted DBC fixture and client comparison |
| Current skin/geoset composition | Partial | Existing components reused | Full equipment/customization fidelity incomplete | | Current skin/geoset composition | Partial | Existing components reused | Full equipment/customization fidelity incomplete |
| Build-12340 animation semantics | Planned | No original-client fixture | Capture animation IDs/transitions/timing | | Build-12340 animation semantics | Planned | No original-client fixture | Capture animation IDs/transitions/timing |
| Runtime equipment/network updates | Planned | No snapshot contract | M08/M09/M12 work | | Runtime equipment/network updates | Planned | No snapshot contract | M09/M10/M13 work |
## Known gaps and risks ## Known gaps and risks
+3 -3
View File
@@ -237,7 +237,7 @@ separate versioned movement snapshot contract.
- Terrain/collision policy can consume displacement without changing input or velocity ownership. - Terrain/collision policy can consume displacement without changing input or velocity ownership.
- A future server-aware predictor may replace this controller behind the scene composition boundary. - A future server-aware predictor may replace this controller behind the scene composition boundary.
- A typed movement snapshot may expose deterministic replay state when M08/M09 require it. - A typed movement snapshot may expose deterministic replay state when M09/M10 require it.
- A future application profile can map to this narrow capability value without - A future application profile can map to this narrow capability value without
coupling the movement controller to the application shell. coupling the movement controller to the application shell.
@@ -252,8 +252,8 @@ separate versioned movement snapshot contract.
| Typed sprint/free-flight exclusion | Implemented | Pure and real-scene profile regressions | Application shell must select profile explicitly later | | Typed sprint/free-flight exclusion | Implemented | Pure and real-scene profile regressions | Application shell must select profile explicitly later |
| Terrain height query | Implemented | Typed `TerrainQuery` and injected player regression | Ground-snap policy remains scene-owned | | Terrain height query | Implemented | Typed `TerrainQuery` and injected player regression | Ground-snap policy remains scene-owned |
| Terrain collision movement policy | Planned | Height-only query does not model collision | Add slopes/holes/collision later | | Terrain collision movement policy | Planned | Height-only query does not model collision | Add slopes/holes/collision later |
| Jump/fall/swim | Planned | M02/M09 roadmap | Requires terrain/liquid and server contracts | | Jump/fall/swim | Planned | M02/M10 roadmap | Requires terrain/liquid and server contracts |
| Prediction/reconciliation | Planned | M08/M09 roadmap | Requires movement snapshot/network contract | | Prediction/reconciliation | Planned | M09/M10 roadmap | Requires movement snapshot/network contract |
## Known gaps and risks ## Known gaps and risks
@@ -7,7 +7,7 @@
| Status | Implemented extraction | | Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-M2-ANIMATION-PLAYBACK-001` | | Target/work package | M03 / `M03-RND-M2-ANIMATION-PLAYBACK-001` |
| Owners | Per-instance AnimationPlayer/native animator playback mutation | | Owners | Per-instance AnimationPlayer/native animator playback mutation |
| Last verified | Worktree `work/sindo-main-codex/m03-m2-animation-playback`, 2026-07-18 | | Last verified | Worktree `work/sindo-main-codex-m03-integrator/m03-closeout`, 2026-08-02 |
| Profiles/capabilities | Imported GLB and native experimental animated M2 instances | | Profiles/capabilities | Imported GLB and native experimental animated M2 instances |
## Purpose ## Purpose
@@ -59,7 +59,7 @@ MultiMesh, SceneTree attachment and application layers are forbidden.
| Output | Mutated native/imported playback | Controller | Rendered instance | Nodes retain state/resources | Instance lifetime | | Output | Mutated native/imported playback | Controller | Rendered instance | Nodes retain state/resources | Instance lifetime |
| Output | Detached native diagnostic records | Controller | Loader log adapter | Caller-owned Dictionaries | Debug call | | Output | Detached native diagnostic records | Controller | Loader log adapter | Caller-owned Dictionaries | Debug call |
Side effects are native field assignment, prepare/phase calls, animation loop Side effects are native field assignment, phased preparation calls, animation loop
mutation, play and seek. The service retains no inputs. mutation, play and seek. The service retains no inputs.
## Data flow ## Data flow
@@ -67,13 +67,13 @@ mutation, play and seek. The service retains no inputs.
```mermaid ```mermaid
flowchart TD flowchart TD
Identity[Path and index] --> Phase[Stable hash phase] Identity[Path and index] --> Phase[Stable hash phase]
NativeInventory[Exact-script native inventory] --> Prepare[prepare runtime if available] NativeInventory[Exact-script native inventory] --> Prepare[prepare local runtime mesh]
Phase --> NativePhase[set native phase] Phase --> Prepare
Players[AnimationPlayers] --> Select[Choose path-specific default] Players[AnimationPlayers] --> Select[Choose path-specific default]
Select --> Loop[Set every animation LOOP_LINEAR] Select --> Loop[Set every animation LOOP_LINEAR]
Loop --> Play[Play selected name] Loop --> Play[Play selected name]
Phase --> Seek[Seek positive-length selection] Phase --> Seek[Seek positive-length selection]
NativePhase --> Diagnostics{Debug requested?} Prepare --> Diagnostics{Debug requested?}
Diagnostics -->|yes| Snapshot[Detached runtime state] Diagnostics -->|yes| Snapshot[Detached runtime state]
``` ```
@@ -103,7 +103,7 @@ sequenceDiagram
M->>F: animation_players_in_subtree(duplicate) M->>F: animation_players_in_subtree(duplicate)
F-->>M: ordered players F-->>M: ordered players
M->>P: start_instance_playback(path, index, players, debug) M->>P: start_instance_playback(path, index, players, debug)
P->>N: prepare_runtime and set_phase P->>N: prepare_runtime_at_phase
P->>A: choose, loop, play and seek P->>A: choose, loop, play and seek
P-->>M: optional detached native diagnostics P-->>M: optional detached native diagnostics
M-->>M: tag states with instance index M-->>M: tag states with instance index
@@ -132,6 +132,11 @@ flowchart TB
- Native arrays are assigned by reference exactly as before extraction. - Native arrays are assigned by reference exactly as before extraction.
- Diagnostic Dictionaries are deep-duplicated before return. - Diagnostic Dictionaries are deep-duplicated before return.
- Main thread performs all engine-object mutation; pure phase math is thread-safe. - Main thread performs all engine-object mutation; pure phase math is thread-safe.
- A duplicated native animator resolves and duplicates its local Mesh, applies
phase and deforms once before attachment. Its later `_ready()` is idempotent.
- Preparation allocates an empty instance-local ArrayMesh because deformation
immediately rebuilds every surface from retained native arrays. Captured
immutable Material resources remain shared and are reapplied.
## Errors, cancellation and recovery ## Errors, cancellation and recovery
@@ -168,7 +173,8 @@ and material versions are unchanged; no rebake is required.
- `verify_m2_animation_playback_controller.gd` covers exact phase, ordinary/ - `verify_m2_animation_playback_controller.gd` covers exact phase, ordinary/
fish/bird priorities, substring/first fallback, loop/play/seek, native exact- fish/bird priorities, substring/first fallback, loop/play/seek, native exact-
script order, five-field copy, phase, detached diagnostics and boundaries. script order, five-field copy, single-rebuild phased preparation, idempotent
ready, detached diagnostics and boundaries.
- Finalizer/build/prototype/material/shutdown regressions protect adjacent behavior. - Finalizer/build/prototype/material/shutdown regressions protect adjacent behavior.
- Fidelity evidence is exact policy/mutation extraction; no private asset or - Fidelity evidence is exact policy/mutation extraction; no private asset or
original-client animation comparison is claimed. original-client animation comparison is claimed.
@@ -192,7 +198,8 @@ for world doodads and compatibility fixtures.
- Hash phase intentionally depends on existing Godot String hashing behavior. - Hash phase intentionally depends on existing Godot String hashing behavior.
- Default-name heuristics are not a complete WoW animation-state mapping. - Default-name heuristics are not a complete WoW animation-state mapping.
- No proprietary traversal, animation timing comparison, p95/p99 or paired-client run exists. - Native CPU deformation remains proportional to vertex count and is unsuitable
for large numbers of independently animated instances without a future GPU path.
## Source map ## Source map
@@ -0,0 +1,183 @@
# Renderer Closeout Verification
## Metadata
| Field | Value |
|---|---|
| Status | Implemented |
| Target/work package | M03 / `M03-QAR-INTEGRATOR-CLOSEOUT-001` |
| Owner | Renderer structural, cache and performance acceptance gates |
| Last verified | Worktree `work/sindo-main-codex-m03-integrator/m03-closeout`, 2026-08-02 |
| Profile | `Blizzlike335`; quality preset `High` for checkpoint evidence |
## Purpose
Provide reproducible closeout checks for the M03 renderer decomposition. The
checks prove that worker boundaries remain CPU-only, main-thread frame steps are
budgeted, cache versions remain explicit, and paired M00/M03 reports stay within
the agreed 10% performance budgets.
## Non-goals
- Claim pixel-level parity with the original build-12340 client.
- Replace subsystem unit verifiers or long-traversal release tests.
- Generate, mutate or migrate production cache payloads.
- Hide incompatible environments or cache inventories by normalizing reports.
## Context and boundaries
```mermaid
flowchart LR
M00[M00 commit capture] --> Reports[Render checkpoint reports]
M03[M03 commit capture] --> Reports
Reports --> Comparator[compare_render_performance.ps1]
Sources[Renderer source and cache versions] --> Contracts[verify_renderer_closeout_contracts.gd]
Comparator --> Stability[repeatability gate across short and long windows]
Stability --> Evidence[JSON comparison and exit code]
Contracts --> Evidence
Evidence --> Target[M03 Evidence / DONE decision]
```
The capture command owns SceneTree execution and PNG/report writes. The
PowerShell comparator is read-only except for its requested JSON output. The
GDScript contract verifier reads source files and creates no renderer resources.
## Public API
| Symbol | Kind | Purpose | Preconditions | Failure |
|---|---|---|---|---|
| `compare_render_performance.ps1 -BaselineReport <paths> -CandidateReport <paths> [-OutputReport <path>]` | CLI | Compare one report or median of repeated reports | Same schema, profile, environment, cache contract/inventory and result keys | Exit 1 and enumerate incompatible fields or budget regressions |
| `verify_render_performance_stability.ps1 -RepeatedSampleComparison <path> -LongWindowComparison <path>` | CLI | Reject only a metric regression reproduced by both independent protocols | Both comparator reports contain the same 84 metrics | Exit 1 with every repeatable result/metric key |
| `verify_renderer_closeout_contracts.gd` | Godot CLI | Check worker/main-thread/cache/converter source contracts | Project parses and referenced sources exist | Exit 1 with named contract failure |
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Lifetime |
|---|---|---|---|---|---|
| Input | M00/M03 `report.json` paths | `capture_render_checkpoints.gd` | Comparator | Filesystem-owned immutable evidence | One comparison |
| Input | Renderer/tool GDScript sources | Repository | Contract verifier | Read-only | One verifier run |
| Output | 84 metric comparisons and failures | Comparator | Integrator/CI | Optional JSON plus process output | Evidence retention |
| Output | Repeatable and protocol-local regression inventories | Stability gate | Integrator/CI | Optional JSON plus process output | Evidence retention |
| Output | Structural pass/fail summary | Contract verifier | Integrator/CI | Process output | One run |
## Data flow
```mermaid
flowchart TD
Read[Read every supplied report] --> Compatible{Metadata and inventory match?}
Compatible -->|no| Fail[Exit 1 with exact mismatch]
Compatible -->|yes| Median[Median each metric per checkpoint/pass]
Median --> Pair[Pair 14 result keys]
Pair --> Budget[Compare load, p95, p99, hitch and memory]
Budget --> Json[Optional comparison JSON]
Budget --> Cohorts[Repeated-sample and long-window comparisons]
Cohorts --> Repeated{Same metric fails both?}
Repeated -->|no| Pass[Exit 0 with protocol-local diagnostics]
Repeated -->|yes| Fail
```
## Lifecycle and sequence
```mermaid
sequenceDiagram
participant I as Integrator
participant B as M00 worktree
participant C as M03 worktree
participant R as Checkpoint capture
participant G as Performance comparator
I->>B: capture repeated baseline samples
B->>R: same viewport, driver and cache inventory
I->>C: capture repeated candidate samples
C->>R: same viewport, driver and cache inventory
I->>G: baseline paths plus candidate paths
G->>G: compatibility checks and per-metric medians
G-->>I: short-window and long-window JSON evidence
I->>G: verify stability across both protocols
G-->>I: repeatable-regression pass/fail
```
There is no persistent state machine. Each invocation is read, validate,
aggregate, compare and terminate.
## Ownership, threading and resources
- Reports and source files are borrowed read-only for one process.
- Median aggregation deep-copies the first report and never rewrites inputs.
- Rendering remains owned by the GUI capture process on Godot's main thread.
- The contract verifier is headless and does not instantiate the streaming world.
- The optional comparison report is wholly owned by the caller-selected path.
## Errors, cancellation and recovery
| Failure | Behavior | Recovery |
|---|---|---|
| Missing/malformed report | Terminate with path/shape error | Regenerate that capture |
| Environment/cache mismatch | Fail before accepting metrics | Recapture both commits on the same machine and cache |
| Missing/duplicate checkpoint | Fail result-key validation | Repair manifest/capture completeness |
| Metric over budget | Record baseline, candidate, limit and percentage | Diagnose named checkpoint/lane; rerun only after a code or evidence correction |
| Interrupted GUI capture | No complete report is accepted | Remove/ignore partial output and rerun |
## Configuration and capabilities
The comparator reads thresholds from the baseline report. M03 uses 10% maximum
regression for load time, frame p95, frame p99, maximum hitch, static memory and
video memory. Repeated input paths are optional; when supplied, each side is
reduced independently to the median for every metric.
Closeout uses two independent protocols: repeated three-second captures and a
ten-second measurement window. A regression is accepted as real only when the
same checkpoint/pass/metric exceeds its unchanged 10% budget in both protocols.
Protocol-local failures remain in the JSON as noise diagnostics rather than
being discarded.
## Persistence, cache and migrations
The comparison JSON uses schema version 1 and contains source paths, sample
counts, revisions, all metric pairs and failures. It is evidence, not a runtime
cache. Renderer cache versions are read from the manifest/source contracts; this
module performs no migration or invalidation.
## Diagnostics and observability
- Success reports result pairs, comparison count and budget percentage.
- Failure output names every checkpoint/pass, metric, values and limit.
- The structural verifier reports worker count, frame-step count and cache-version count.
- Capture reports retain PNG hashes, queue snapshots, environment and cache inventory.
## Verification, fidelity and performance
- `verify_renderer_closeout_contracts.gd` covers four worker boundaries, fifteen
frame steps, seven cache versions and the nested M2 GLB output contract.
- `compare_render_performance.ps1` compares 14 cold/warm result pairs and 84 metrics.
- `verify_render_performance_stability.ps1` requires metric-key agreement and
rejects any budget regression reproduced by both sampling protocols.
- M00 and M03 must be captured from their exact commits against the same cache
inventory; old reports with a different inventory are rejected.
- PNG hashes and asset-backed coverage prove that terrain, ADT boundaries, dense
M2, large WMO, liquid, animated M2 and sky were rendered. They do not prove
original-client pixel parity without human/reference-image approval.
## Extension points
CI may retain reports and comparison JSON as artifacts. A future release gate may
add driver-version metadata, long-traversal samples or approved visual-diff
thresholds without changing runtime renderer contracts.
## Known gaps and risks
- Godot reports the rendering API but not the installed NVIDIA driver version.
- A 0.5-second historical M00 measurement window requires repeated median samples.
- Original-client screenshots are not part of the repository evidence set.
- Long-traversal descriptor pressure remains a later quality/release gate.
## Source map
| Path | Responsibility |
|---|---|
| `tools/compare_render_performance.ps1` | Compatibility, median aggregation and metric budgets |
| `tools/verify_render_performance_stability.ps1` | Cross-protocol repeatability acceptance |
| `src/tools/verify_renderer_closeout_contracts.gd` | Structural source/cache/converter contracts |
| `src/tools/capture_render_checkpoints.gd` | Asset-backed GUI capture and report generation |
| `src/tools/render_baseline_manifest.json` | Coverage, viewport, cache contract and budgets |
| `targets/00-render-baseline.md` | Accepted M00 measurement protocol |
| `targets/03-renderer-facade.md` | M03 acceptance and Evidence |
+1 -1
View File
@@ -227,7 +227,7 @@ Existing ADT/native/cache format versions remain unchanged.
| Renderer loaded-mesh diagnostic backend | Implemented | M03 facade typed sample and detached snapshot contract | Not composed into gameplay; triangle Mesh ray is diagnostic only | | Renderer loaded-mesh diagnostic backend | Implemented | M03 facade typed sample and detached snapshot contract | Not composed into gameplay; triangle Mesh ray is diagnostic only |
| Authoritative renderer/physics backend | Planned | Boundary permits replacement | Define holes/slopes/collision/readiness semantics before gameplay composition | | Authoritative renderer/physics backend | Planned | Boundary permits replacement | Define holes/slopes/collision/readiness semantics before gameplay composition |
| Holes/slopes/collision | Planned | Outside height-only contract | Later movement/physics package | | Holes/slopes/collision | Planned | Outside height-only contract | Later movement/physics package |
| Liquid/swim query | Planned | Outside contract | M09/M12 world gameplay | | Liquid/swim query | Planned | Outside contract | M10/M13 world gameplay |
## Known gaps and risks ## Known gaps and risks
+20 -8
View File
@@ -7,7 +7,7 @@
| Status | Implemented | | Status | Implemented |
| Target/work package | M03 / `M03-RND-WMO-PLACEMENT-RESOLVER-001` | | Target/work package | M03 / `M03-RND-WMO-PLACEMENT-RESOLVER-001` |
| Owners | Pure WMO cache-key, placement-identity and world-transform rules | | Owners | Pure WMO cache-key, placement-identity and world-transform rules |
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-placement-resolver`, 2026-07-17 | | Last verified | Worktree `work/sindo-main-codex/m03-wmo-scene-instance-factory`, 2026-08-01 |
| Profiles/capabilities | Existing ADT/WDT WMO placement paths | | Profiles/capabilities | Existing ADT/WDT WMO placement paths |
## Purpose ## Purpose
@@ -31,14 +31,17 @@ live-prototype instance paths.
flowchart LR flowchart LR
Parsed[ADT/WDT WMO placement] --> Loader[StreamingWorldLoader adapter] Parsed[ADT/WDT WMO placement] --> Loader[StreamingWorldLoader adapter]
Loader --> Resolver[WmoPlacementResolver] Loader --> Resolver[WmoPlacementResolver]
Loader --> Factory[WmoSceneInstanceFactory]
Factory --> Resolver
Resolver --> CacheKey[Normalized cache key] Resolver --> CacheKey[Normalized cache key]
Resolver --> Identity[Registry unique key] Resolver --> Identity[Registry unique key]
Resolver --> Transform[World Transform3D] Resolver --> Transform[World Transform3D]
CacheKey --> Cache[Loader WMO caches/requests] CacheKey --> Cache[Loader WMO caches/requests]
Identity --> Registry[WmoPlacementRegistry] Identity --> Registry[WmoPlacementRegistry]
Transform --> RenderRoot[Lightweight render root] Transform --> RenderRoot[Lightweight render root]
Transform --> Scene[Cached scene instance] Transform --> Factory
Transform --> Live[Live prototype instance] Factory --> Scene[Cached scene instance]
Factory --> Live[Live prototype instance]
``` ```
Allowed dependencies are Dictionary/String values and Godot `Vector3`, `Basis` Allowed dependencies are Dictionary/String values and Godot `Vector3`, `Basis`
@@ -62,7 +65,7 @@ WorkerThreadPool, mutexes, files, gameplay, network and editor UI are forbidden.
| Input | Tile key and placement index | Loader build job | Synthetic identity fallback | Copied scalar/String | Registry entry lifetime | | Input | Tile key and placement index | Loader build job | Synthetic identity fallback | Copied scalar/String | Registry entry lifetime |
| Output | Normalized relative path | Resolver | Render/scene cache and load-request maps | New String value | Request/cache lookup | | Output | Normalized relative path | Resolver | Render/scene cache and load-request maps | New String value | Request/cache lookup |
| Output | `uid:*` or `tile:*:*` key | Resolver | `WmoPlacementRegistry` and loader ref arrays | New String value | Until unregister/reset | | Output | `uid:*` or `tile:*:*` key | Resolver | `WmoPlacementRegistry` and loader ref arrays | New String value | Until unregister/reset |
| Output | World `Transform3D` | Resolver | Three WMO instance adapters | Value copy | Instance lifetime after assignment | | Output | World `Transform3D` | Resolver | Lightweight render-root adapter and cached/live instance factory | Value copy | Instance lifetime after assignment |
The resolver retains no source Dictionary, output or engine resource. The resolver retains no source Dictionary, output or engine resource.
@@ -91,6 +94,7 @@ and shutdown require no resolver operation.
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant Loader as StreamingWorldLoader participant Loader as StreamingWorldLoader
participant Factory as WmoSceneInstanceFactory
participant Resolver as WmoPlacementResolver participant Resolver as WmoPlacementResolver
participant Registry as WmoPlacementRegistry participant Registry as WmoPlacementRegistry
participant Instance as Render/cached/live instance participant Instance as Render/cached/live instance
@@ -98,9 +102,15 @@ sequenceDiagram
Resolver-->>Loader: cache key Resolver-->>Loader: cache key
Loader->>Resolver: resolve_unique_key(placement, tile, index) Loader->>Resolver: resolve_unique_key(placement, tile, index)
Resolver-->>Registry: identity adopted by loader Resolver-->>Registry: identity adopted by loader
Loader->>Resolver: resolve_world_transform(placement) alt lightweight render root
Resolver-->>Loader: value Transform3D Loader->>Resolver: resolve_world_transform(placement)
Loader->>Instance: assign transform and attach/build Resolver-->>Loader: value Transform3D
else cached/live instance
Loader->>Factory: create with placement
Factory->>Resolver: resolve_world_transform(placement)
Resolver-->>Factory: value Transform3D
end
Loader->>Instance: attach/build prepared instance
``` ```
## Ownership, threading and resources ## Ownership, threading and resources
@@ -109,7 +119,9 @@ sequenceDiagram
- `WmoPlacementRegistry` owns placement-key reference sets. The loader owns its - `WmoPlacementRegistry` owns placement-key reference sets. The loader owns its
key-to-Node map, cache/load-request state, jobs/queues, resource fallback and key-to-Node map, cache/load-request state, jobs/queues, resource fallback and
cancellation. cancellation.
- The loader and builders own every Node/Mesh/MultiMesh/material/RID lifecycle. - `WmoSceneInstanceFactory` owns detached cached/live candidate roots until
rejection or transfer; the loader/builders own attachment and remaining
Node/Mesh/MultiMesh/material/RID lifecycle.
- Pure calls are thread-safe; current consumers execute on the main thread. - Pure calls are thread-safe; current consumers execute on the main thread.
## Errors, cancellation and recovery ## Errors, cancellation and recovery
+219
View File
@@ -0,0 +1,219 @@
# WMO Scene Instance Factory
## Metadata
| Field | Value |
|---|---|
| Status | Implemented |
| Target | M03 Renderer Facade and Safe Extraction |
| Work package | `M03-RND-WMO-SCENE-INSTANCE-FACTORY-001` |
| Owner | Render |
| Last verified | 2026-08-01 |
## Purpose
`WmoSceneInstanceFactory` creates detached WMO `Node3D` roots from validated
cached `PackedScene` resources or live-built prototypes. It owns cache-currentness
validation, basename assignment and canonical placement-resolver delegation.
## Non-goals
- look up/load/cache PackedScenes or build live WMO prototypes;
- apply Mesh/material/occluder/shadow runtime preparation;
- attach nodes, assign Editor ownership or manage placement references;
- own queues, permits, cache versions or world teardown;
- define WMO placement formulas or scene-cache currentness rules.
## Context and boundaries
The loader selects cached versus live sources. The factory creates a detached
instance and applies identity/placement. `WmoRuntimeScenePreparer` then applies
path-specific presentation policy before the loader attaches/registers the root.
```mermaid
flowchart LR
Cache[WMO PackedScene cache] --> Loader[StreamingWorldLoader]
Prototype[Live WMO prototype cache/build] --> Loader
Loader --> Factory[WmoSceneInstanceFactory]
Validator[WMOBuilder scene-cache validator] --> Factory
Resolver[WmoPlacementResolver] --> Factory
Factory --> Detached[Detached WMO Node3D]
Detached --> Preparer[WmoRuntimeScenePreparer]
Preparer --> Loader
Loader --> Scene[Attachment and placement registry]
```
## Public API
| Symbol | Role | Thread/lifetime | Failure behavior |
|---|---|---|---|
| `is_cached_node_current(node)` | Delegate one Node to the injected cache validator | Renderer main thread; no retention | Null/missing validator returns false |
| `instantiate_cached_scene(relative_path, scene, placement)` | Instantiate, type-check, validate, name and place a cached scene | Renderer main thread; detached result caller-owned | Invalid input/root/stale/dependency returns null; created rejected roots freed |
| `duplicate_live_prototype(relative_path, prototype, placement)` | Duplicate, name and place a live prototype | Renderer main thread; detached result caller-owned | Null/missing resolver/unexpected duplicate returns null |
The cached path validates before placement. The live path deliberately skips the
scene-cache validator. Both paths use `get_file().get_basename()` and the exact
`WmoPlacementResolver.resolve_world_transform` result.
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Cached `PackedScene` or live prototype `Node3D` | Loader cache/build adapters | Factory | Borrowed Resource/Node | One main-thread call |
| Input | Relative WMO path and placement Dictionary | Loader placement job | Factory | Borrowed values | One call |
| Internal | Candidate root | PackedScene instantiate/prototype duplicate | Validator/factory | Factory-owned until accepted | One call |
| Output | Detached named/placed `Node3D` | Factory | Runtime scene preparer/loader | Ownership transfers to caller | Until attachment/release |
| Output | Currentness bool | Validator via factory | Loader cache admission | Value | Immediate |
Side effects are limited to scene instantiation/duplication, candidate name and
transform mutation, and synchronous free of rejected candidates. No attachment,
filesystem, ResourceLoader, worker, RID, queue, cache or Editor-owner mutation.
## Data flow
```mermaid
flowchart LR
Source[PackedScene or live prototype] --> Create{Cached or live?}
Create -->|cached| Instantiate[PackedScene.instantiate]
Create -->|live| Duplicate[prototype.duplicate]
Instantiate --> Type{Node3D?}
Duplicate --> Type
Type -->|no| Free[Free created candidate and return null]
Type -->|yes cached| Current{Cache current?}
Type -->|yes live| Identity[Apply basename]
Current -->|no| Free
Current -->|yes| Identity
Identity --> Resolve[WmoPlacementResolver]
Resolve --> Return[Return detached Node3D]
```
## Main sequence
```mermaid
sequenceDiagram
participant Loader as StreamingWorldLoader
participant Factory as WmoSceneInstanceFactory
participant Validator as WMOBuilder
participant Resolver as WmoPlacementResolver
alt cached source
Loader->>Factory: instantiate_cached_scene(path, scene, placement)
Factory->>Factory: instantiate and require Node3D
Factory->>Validator: is_scene_cache_current(root)
Validator-->>Factory: current/stale
else live source
Loader->>Factory: duplicate_live_prototype(path, prototype, placement)
Factory->>Factory: duplicate and require Node3D
end
Factory->>Factory: assign basename
Factory->>Resolver: resolve_world_transform(placement)
Resolver-->>Factory: exact Transform3D
Factory-->>Loader: detached Node3D or null
```
## Dependency diagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Factory[WmoSceneInstanceFactory]
Factory --> Validator[Injected WMO scene-cache validator]
Factory --> Resolver[Injected WmoPlacementResolver]
Factory --> Engine[PackedScene / Node3D / Transform3D]
Factory -. no dependency .-> Preparation[WmoRuntimeScenePreparer]
Factory -. no dependency .-> IO[ResourceLoader / FileAccess]
Factory -. no dependency .-> Queue[WMO queues / scheduler]
```
## Ownership, threading and resources
- Calls are renderer-main-thread only because PackedScene/Node APIs mutate.
- The source scene/prototype remains caller/cache-owned.
- The factory owns a newly created root until rejection or successful return.
- Successful return transfers detached-root ownership to the caller.
- Descendant Mesh/Material Resources retain engine duplicate/instantiate identity.
- The factory retains only injected stateless dependencies, never Nodes/Resources.
## Errors, cancellation and recovery
| Failure/state | Detection | Behavior | Recovery |
|---|---|---|---|
| Null scene/prototype | Guard | Return null without allocation | Correct caller source |
| Missing validator | Currentness guard | Cached candidate rejected/freed | Fix composition |
| Missing resolver | Guard before creation | Return null without allocation | Fix composition |
| Non-Node3D root | Runtime type check | Free candidate and return null | Rebuild invalid cache/source |
| Stale cached root | Injected validator | Free candidate; skip placement | Rebuild cache/current metadata |
| Placement cancellation | Loader lifecycle | Detached/attached result released by caller | Existing retry path |
| Shutdown | No retained candidates | Nothing to drain | Existing loader teardown |
The non-Node3D cached rejection now frees the created invalid root synchronously.
Normal admitted caches already enforce Node3D through the scene finalizer, so this
closes an error-path lifetime leak without changing valid rendered output.
## Configuration and capabilities
No new settings. Cache-currentness rules belong to the injected WMOBuilder
boundary; placement formulas belong to `WmoPlacementResolver`.
## Persistence, cache and migration
No format/version change and no rebake. The factory reads no files and writes no
metadata. Existing cache validator version policy remains authoritative.
## Diagnostics and observability
The factory emits no logs or metrics. Loader cache/placement metrics and
synthetic rejection contracts remain the diagnostic surfaces.
## Verification
- `verify_wmo_scene_instance_factory.gd` covers cached validation-before-placement,
exact accepted root/descendant Resource identity, stale-root free, non-Node3D
rejection, live validator suppression, detached ownership, dependencies,
basename/Transform3D application, source boundaries and 1,000 duplicates.
- Adjacent scene finalizer, placement resolver, runtime preparer, shutdown and
checkpoint regressions protect lifecycle and visible output.
- Fidelity evidence is behavior-preserving extraction for valid inputs. The
invalid non-Node3D free is a lifetime fix, not a visual 3.3.5a change.
The synthetic budget requires 1,000 simple live duplicates in under one second.
Asset-backed CPU/GPU p95/p99 and long-traversal evidence remain pending.
## Extension points
- Asset-backed cached/live instances can compare placement and lifetime without
changing the factory API.
- New source kinds should be separate explicit methods only when their validation
and identity semantics differ materially.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Cached WMO instantiation | Implemented extraction | Type/currentness/name/placement/lifetime contract | Serialized asset-backed cache corpus pending |
| Live prototype duplication | Implemented extraction | Identity/name/placement/validator-suppression contract | Asset-backed traversal/leak evidence pending |
| Runtime preparation | Separate implemented service | Runtime scene preparer regression | Visual/GPU p95/p99 pending |
| Attachment/registry lifetime | Loader-owned | Existing WMO placement/shutdown regressions | Further orchestration extraction pending |
## Known gaps and risks
- Scene instantiation/duplication remains synchronous main-thread work.
- No private WMO corpus, portal/room behavior, long traversal, leak/GPU timing or
paired original-client capture is included.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/wmo/wmo_scene_instance_factory.gd` | Cached/live creation, validation, identity and placement |
| `src/render/wmo/wmo_placement_resolver.gd` | Canonical WMO placement Transform3D |
| `src/render/wmo/wmo_runtime_scene_preparer.gd` | Post-factory cached/live render preparation |
| `src/scenes/streaming/streaming_world_loader.gd` | Source selection, cache/prototype lookup, attachment and lifetime |
| `src/tools/verify_wmo_scene_instance_factory.gd` | Synthetic type/identity/lifetime/boundary/timing regression |
## Related decisions and references
- [`wmo-placement-resolver.md`](wmo-placement-resolver.md)
- [`wmo-scene-resource-finalizer.md`](wmo-scene-resource-finalizer.md)
- [`wmo-runtime-scene-preparer.md`](wmo-runtime-scene-preparer.md)
- [`world-renderer.md`](world-renderer.md)
- [`../../RENDER.md`](../../RENDER.md)
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
+17 -1
View File
@@ -7,7 +7,7 @@
| Status | Partial | | Status | Partial |
| Target/work package | M00 baseline; `M01-RND-STREAMING-FOCUS-001`; `M01-QAR-SERVER-SPAWN-RENDERER-001`; M03 facade/planner/scheduler/internal-access/ground/environment/entity packages; M03 terrain packages; M03 M2 packages; M03 WMO placement package | | Target/work package | M00 baseline; `M01-RND-STREAMING-FOCUS-001`; `M01-QAR-SERVER-SPAWN-RENDERER-001`; M03 facade/planner/scheduler/internal-access/ground/environment/entity packages; M03 terrain packages; M03 M2 packages; M03 WMO placement package |
| Owners | Renderer workstream / milestone integrator | | Owners | Renderer workstream / milestone integrator |
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-runtime-scene-preparer`, 2026-08-01 | | Last verified | Worktree `work/sindo-main-codex/m03-wmo-scene-instance-factory`, 2026-08-01 |
| Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete | | Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete |
## Purpose ## Purpose
@@ -74,6 +74,8 @@ flowchart LR
WmoGroupMaterializer --> Scene WmoGroupMaterializer --> Scene
Loader --> WmoScenePreparer[WmoRuntimeScenePreparer] Loader --> WmoScenePreparer[WmoRuntimeScenePreparer]
WmoScenePreparer --> Scene WmoScenePreparer --> Scene
Loader --> WmoInstanceFactory[WmoSceneInstanceFactory]
WmoInstanceFactory --> WmoScenePreparer
Native --> Parsed[Parsed tile/model data] Native --> Parsed[Parsed tile/model data]
Parsed --> Loader Parsed --> Loader
Loader --> Scene[SceneTree nodes] Loader --> Scene[SceneTree nodes]
@@ -170,6 +172,7 @@ from externally reading/writing loader-private queue, task, cache and tile-state
| `WmoRenderBuildQueue` / `WmoRenderBuildJob` | Internal WMO pending-state service | Owns typed root/resource/cursor jobs and FIFO placement keys | Renderer main thread; map session | Invalid enqueue rejected; duplicate/stale behavior preserved | | `WmoRenderBuildQueue` / `WmoRenderBuildJob` | Internal WMO pending-state service | Owns typed root/resource/cursor jobs and FIFO placement keys | Renderer main thread; map session | Invalid enqueue rejected; duplicate/stale behavior preserved |
| `WmoRenderGroupMaterializer.materialize_mesh_group/materialize_multimesh_group` | Internal WMO scene-materialization service | Creates, configures and attaches one indexed lightweight render group | Renderer main thread; stateless after each call | Invalid parent/resource/index returns null without attachment | | `WmoRenderGroupMaterializer.materialize_mesh_group/materialize_multimesh_group` | Internal WMO scene-materialization service | Creates, configures and attaches one indexed lightweight render group | Renderer main thread; stateless after each call | Invalid parent/resource/index returns null without attachment |
| `WmoRuntimeScenePreparer.prepare_cached_instance/prepare_live_instance` | Internal WMO subtree-preparation service | Preserves cached/live Mesh-finalization distinction, direct occluder policy and recursive shadow enabling | Renderer main thread; stateless after each call | Null/freed root returns false | | `WmoRuntimeScenePreparer.prepare_cached_instance/prepare_live_instance` | Internal WMO subtree-preparation service | Preserves cached/live Mesh-finalization distinction, direct occluder policy and recursive shadow enabling | Renderer main thread; stateless after each call | Null/freed root returns false |
| `WmoSceneInstanceFactory.instantiate_cached_scene/duplicate_live_prototype` | Internal WMO instance-creation service | Validates/creates detached cached/live roots and applies shared name/placement | Renderer main thread; stateless after each call | Invalid/stale/dependency failure returns null and frees created rejects |
| `WmoRenderResourceCacheState` | Internal WMO cache-state service | Owns validated Resources, negative entries and pending cache paths | Renderer main thread; map/cache session | Invalid/occupied request and unknown completion are rejected | | `WmoRenderResourceCacheState` | Internal WMO cache-state service | Owns validated Resources, negative entries and pending cache paths | Renderer main thread; map/cache session | Invalid/occupied request and unknown completion are rejected |
| `WmoRenderResourceFinalizer` | Internal WMO terminal-I/O service | Polls lightweight render requests, validates script/format and publishes Resource/missing outcomes | Renderer main thread; stateless across calls | Non-terminal retained; failed/null/wrong/stale complete missing | | `WmoRenderResourceFinalizer` | Internal WMO terminal-I/O service | Polls lightweight render requests, validates script/format and publishes Resource/missing outcomes | Renderer main thread; stateless across calls | Non-terminal retained; failed/null/wrong/stale complete missing |
| `WmoSceneResourceCacheState` | Internal WMO cache-state service | Owns validated PackedScenes, negative entries and pending `.tscn` paths | Renderer main thread; map/cache session | Direct missing and terminal request transitions remain distinct | | `WmoSceneResourceCacheState` | Internal WMO cache-state service | Owns validated PackedScenes, negative entries and pending `.tscn` paths | Renderer main thread; map/cache session | Direct missing and terminal request transitions remain distinct |
@@ -214,6 +217,7 @@ loader configuration remains transitional composition data, not a caller API.
| Internal WMO pending build | Placement key, Node3D root, WMO Resource and cursors | Loader / `WmoRenderBuildQueue` | Loader drain and step planner adapter | Queue-owned job and strong references | Until cancel/clear/replacement | | Internal WMO pending build | Placement key, Node3D root, WMO Resource and cursors | Loader / `WmoRenderBuildQueue` | Loader drain and step planner adapter | Queue-owned job and strong references | Until cancel/clear/replacement |
| Internal WMO group materialization | Parent root, exact Mesh/MultiMesh, indexed metadata and render settings | Loader / `WmoRenderGroupMaterializer` | Attached geometry node | Parent owns node and exact Resource reference | One main-thread group operation | | Internal WMO group materialization | Parent root, exact Mesh/MultiMesh, indexed metadata and render settings | Loader / `WmoRenderGroupMaterializer` | Attached geometry node | Parent owns node and exact Resource reference | One main-thread group operation |
| Internal WMO subtree preparation | Cached/live root, extracted directory and render policies | Loader / `WmoRuntimeScenePreparer` | Borrowed subtree and runtime Mesh finalizer | Loader/placement owns subtree; preparer retains nothing | One main-thread instance preparation | | Internal WMO subtree preparation | Cached/live root, extracted directory and render policies | Loader / `WmoRuntimeScenePreparer` | Borrowed subtree and runtime Mesh finalizer | Loader/placement owns subtree; preparer retains nothing | One main-thread instance preparation |
| Internal WMO instance creation | Cached PackedScene or live prototype, path and placement | Loader / `WmoSceneInstanceFactory` | Runtime scene preparer and attachment adapter | Factory owns candidate until detached-root transfer | One main-thread creation |
| Internal WMO render cache | Normalized path, cache path and validated Resource | Loader / `WmoRenderResourceCacheState` | Loader lookup, ResourceLoader poll and build queue | State-owned Resource/path references; detached request snapshots | Until transient/full clear | | Internal WMO render cache | Normalized path, cache path and validated Resource | Loader / `WmoRenderResourceCacheState` | Loader lookup, ResourceLoader poll and build queue | State-owned Resource/path references; detached request snapshots | Until transient/full clear |
| Internal WMO scene cache | Normalized path, `.tscn` path and validated PackedScene | Loader / `WmoSceneResourceCacheState` | Loader lookup, request poll and scene instantiation | State-owned PackedScene/path references; detached request snapshots | Until transient/full clear | | Internal WMO scene cache | Normalized path, `.tscn` path and validated PackedScene | Loader / `WmoSceneResourceCacheState` | Loader lookup, request poll and scene instantiation | State-owned PackedScene/path references; detached request snapshots | Until transient/full clear |
| Internal ADT water load | Tile key, ADT path, task ID and parsed Dictionary | Loader/worker / `AdtWaterLoadPipelineState` | Loader task start, budgeted drain and finalization | State-owned records; mutex result mailbox | Request through result completion/reset | | Internal ADT water load | Tile key, ADT path, task ID and parsed Dictionary | Loader/worker / `AdtWaterLoadPipelineState` | Loader task start, budgeted drain and finalization | State-owned records; mutex result mailbox | Request through result completion/reset |
@@ -425,6 +429,9 @@ sequenceDiagram
creation, render settings and attachment without retaining engine objects. creation, render settings and attachment without retaining engine objects.
`WmoRuntimeScenePreparer` owns cached-only Mesh traversal/finalization plus the `WmoRuntimeScenePreparer` owns cached-only Mesh traversal/finalization plus the
shared direct-Occluders and recursive shadow policies for cached/live roots. shared direct-Occluders and recursive shadow policies for cached/live roots.
`WmoSceneInstanceFactory` owns cached/live detached-root creation, cache
validation, basename and placement application; loader retains source lookup,
runtime preparation, attachment and lifetime.
`WmoRenderResourceCacheState` owns validated render Resources, negative entries `WmoRenderResourceCacheState` owns validated render Resources, negative entries
and pending cache paths; `WmoRenderResourceFinalizer` owns its terminal and pending cache paths; `WmoRenderResourceFinalizer` owns its terminal
ResourceLoader polling and script/format validation. `WmoSceneResourceCacheState` ResourceLoader polling and script/format validation. `WmoSceneResourceCacheState`
@@ -576,6 +583,9 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
- WMO runtime scene preparer contract: cached/live finalizer distinction, - WMO runtime scene preparer contract: cached/live finalizer distinction,
exact Mesh traversal order, direct occluder removal, recursive shadow policy, exact Mesh traversal order, direct occluder removal, recursive shadow policy,
ownership boundaries and bounded main-thread timing. ownership boundaries and bounded main-thread timing.
- WMO scene instance factory contract: cached validation-before-placement,
stale/type rejection lifetime, live validator suppression, exact descendant
Resource identity, naming/placement and bounded main-thread timing.
- WMO render Resource cache contract: invalid/duplicate request rejection, - WMO render Resource cache contract: invalid/duplicate request rejection,
validated/missing terminal transitions, transient/full reset, detached sorted validated/missing terminal transitions, transient/full reset, detached sorted
diagnostics, loader-owned version validation and bounded timing. diagnostics, loader-owned version validation and bounded timing.
@@ -638,6 +648,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| WMO render build queue | Implemented extraction | Typed lifecycle/order/ownership/source/timing contract | Asset-backed traversal/leak evidence pending | | WMO render build queue | Implemented extraction | Typed lifecycle/order/ownership/source/timing contract | Asset-backed traversal/leak evidence pending |
| WMO render group materializer | Implemented extraction | Synthetic Resource/name/transform/render/attachment/source/timing contract | Asset-backed visual/leak/GPU p95/p99 pending | | WMO render group materializer | Implemented extraction | Synthetic Resource/name/transform/render/attachment/source/timing contract | Asset-backed visual/leak/GPU p95/p99 pending |
| WMO runtime scene preparer | Implemented extraction | Synthetic cached/live traversal/occluder/shadow/source/timing contract | Asset-backed visual/leak/GPU p95/p99 pending | | WMO runtime scene preparer | Implemented extraction | Synthetic cached/live traversal/occluder/shadow/source/timing contract | Asset-backed visual/leak/GPU p95/p99 pending |
| WMO scene instance factory | Implemented extraction | Synthetic cached/live type/identity/lifetime/name/placement/source/timing contract | Serialized/asset-backed traversal/leak evidence pending |
| WMO render Resource cache state | Implemented extraction | Scene-free lifecycle/exclusivity/source/timing plus shutdown contract | Asset-backed traversal/leak evidence pending | | WMO render Resource cache state | Implemented extraction | Scene-free lifecycle/exclusivity/source/timing plus shutdown contract | Asset-backed traversal/leak evidence pending |
| WMO render Resource finalizer | Implemented extraction | Status/order/script/format/adoption/source/timing contract | Serialized/asset-backed corrupt-cache and leak evidence pending | | WMO render Resource finalizer | Implemented extraction | Status/order/script/format/adoption/source/timing contract | Serialized/asset-backed corrupt-cache and leak evidence pending |
| WMO scene Resource cache state | Implemented extraction | Scene-free lifecycle/direct-missing/source/timing plus shutdown contract | Asset-backed traversal/leak evidence pending | | WMO scene Resource cache state | Implemented extraction | Scene-free lifecycle/direct-missing/source/timing plus shutdown contract | Asset-backed traversal/leak evidence pending |
@@ -722,6 +733,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `src/render/wmo/wmo_runtime_mesh_finalizer.gd` | Cached WMO runtime refresh admission, surface iteration and material reconstruction | | `src/render/wmo/wmo_runtime_mesh_finalizer.gd` | Cached WMO runtime refresh admission, surface iteration and material reconstruction |
| `src/render/wmo/wmo_render_group_materializer.gd` | Indexed lightweight WMO geometry-node creation, render setup and attachment | | `src/render/wmo/wmo_render_group_materializer.gd` | Indexed lightweight WMO geometry-node creation, render setup and attachment |
| `src/render/wmo/wmo_runtime_scene_preparer.gd` | Cached/live WMO subtree Mesh traversal and render policy | | `src/render/wmo/wmo_runtime_scene_preparer.gd` | Cached/live WMO subtree Mesh traversal and render policy |
| `src/render/wmo/wmo_scene_instance_factory.gd` | Cached/live detached-root creation, validation, identity and placement |
| `src/render/streaming/streaming_target_planner.gd` | Scene-free wanted/retained ADT target calculation | | `src/render/streaming/streaming_target_planner.gd` | Scene-free wanted/retained ADT target calculation |
| `src/render/streaming/streaming_target_policy.gd` | Immutable renderer radius/prefetch policy | | `src/render/streaming/streaming_target_policy.gd` | Immutable renderer radius/prefetch policy |
| `src/render/streaming/streaming_target_plan.gd` | Immutable planner result with read-only tile-key sets | | `src/render/streaming/streaming_target_plan.gd` | Immutable planner result with read-only tile-key sets |
@@ -761,6 +773,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `src/tools/verify_wmo_runtime_mesh_finalizer.gd` | WMO Mesh identity/version/material-definition/boundary/timing regression | | `src/tools/verify_wmo_runtime_mesh_finalizer.gd` | WMO Mesh identity/version/material-definition/boundary/timing regression |
| `src/tools/verify_wmo_render_group_materializer.gd` | WMO render-group Resource/name/transform/render/attachment/boundary/timing regression | | `src/tools/verify_wmo_render_group_materializer.gd` | WMO render-group Resource/name/transform/render/attachment/boundary/timing regression |
| `src/tools/verify_wmo_runtime_scene_preparer.gd` | WMO cached/live traversal/occluder/shadow/boundary/timing regression | | `src/tools/verify_wmo_runtime_scene_preparer.gd` | WMO cached/live traversal/occluder/shadow/boundary/timing regression |
| `src/tools/verify_wmo_scene_instance_factory.gd` | WMO cached/live type/identity/lifetime/name/placement/boundary/timing regression |
| `src/tools/verify_streaming_target_planner.gd` | Planner behavior, dependency and bounded timing regression | | `src/tools/verify_streaming_target_planner.gd` | Planner behavior, dependency and bounded timing regression |
| `src/tools/verify_render_budget_scheduler.gd` | Scheduler bounds, shared-lane priority, cancellation and timing regression | | `src/tools/verify_render_budget_scheduler.gd` | Scheduler bounds, shared-lane priority, cancellation and timing regression |
| `src/tools/verify_renderer_internal_access.gd` | Gameplay/EditorPlugin/registered renderer-tool boundary gate derived from private streamer fields | | `src/tools/verify_renderer_internal_access.gd` | Gameplay/EditorPlugin/registered renderer-tool boundary gate derived from private streamer fields |
@@ -772,6 +785,9 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `src/native/src/*_loader.cpp` | Native binary parsing | | `src/native/src/*_loader.cpp` | Native binary parsing |
| `src/tools/build_*cache.gd`, `src/tools/bake_*cache.gd` | Offline cache generation | | `src/tools/build_*cache.gd`, `src/tools/bake_*cache.gd` | Offline cache generation |
| `tools/run_render_baseline.ps1` | Unified M00 baseline runner | | `tools/run_render_baseline.ps1` | Unified M00 baseline runner |
| `tools/compare_render_performance.ps1` | Exact-environment single/repeated report comparator |
| `tools/verify_render_performance_stability.ps1` | Repeated-sample/long-window repeatability gate |
| `src/tools/verify_renderer_closeout_contracts.gd` | Worker, main-thread, cache-version and nested-GLB closeout contracts |
| `src/tools/compare_render_checkpoints.gd` | Offline JPG/PNG paired-image perceptual metrics and JSON pass/fail report | | `src/tools/compare_render_checkpoints.gd` | Offline JPG/PNG paired-image perceptual metrics and JSON pass/fail report |
| `src/tools/verify_render_runtime_cache_shutdown.gd` | Headless ownership regression for detached runtime prototypes, resource caches and empty liquid roots | | `src/tools/verify_render_runtime_cache_shutdown.gd` | Headless ownership regression for detached runtime prototypes, resource caches and empty liquid roots |
| `src/tools/capture_render_checkpoints.gd` | Deterministic no-roll checkpoint camera, performance and visual capture | | `src/tools/capture_render_checkpoints.gd` | Deterministic no-roll checkpoint camera, performance and visual capture |
+23
View File
@@ -6,6 +6,29 @@ Godot project scanning is disabled for this directory by `.gdignore`. Reference
assets remain available to Git, text search and external tooling, but Godot must assets remain available to Git, text search and external tooling, but Godot must
not generate `.import` sidecars inside nested reference repositories. not generate `.import` sidecars inside nested reference repositories.
## Git reference revisions
Remote branches were fetched with pruning and the checked-out default branches
were fast-forwarded on 2026-09-05. The parent repository pins the exact commits
through gitlinks; `.gitmodules` records the branch used for an intentional future
`git submodule update --remote`. Inspect local state before advancing the parent
gitlink pins; checked-out branches in this refresh used `git pull --ff-only`.
| Reference | Canonical branch | Pinned commit |
| --- | --- | --- |
| `open-realm` | `main` | `950a1e8c1343a6e2139cc448f779cb2957a8d3f8` |
| `whoa` | `master` | `ea1345636045635ac601b7d525eb0c38c6c0f6dd` |
| `WoWee` | `master` | `607ea3b8369851014721416293f8e95dfbe64eec` |
| `WowUnreal` | `main` | `c2a4b9827b2e7672e799085c5bf631d37c4ca5d3` |
| `wow.export` | `main` | `c2fd7bde36a712be78a5da896c995b84fbfa2545` |
| `benilla` | `main` | `bc1a2428dd7e00ca8abbfb8f0bf53750dae7b123` |
| `blender-wow-studio/.../pywowlib` | `master` | `55276dc5c2195da7fe136638a2a59716622f8c65` |
`blender-wow-studio-3.4-1.1.0_Experimental` and `noggit-red` are tracked
snapshots, not top-level gitlinks. They do not have a local branch and require a
separate, provenance-checked snapshot refresh instead of a blind pull. The
embedded `pywowlib` gitlink is maintained independently as listed above.
- `blender-wow-studio-3.4-1.1.0_Experimental/` - Blender add-on sources for M2/WMO import/export reference. - `blender-wow-studio-3.4-1.1.0_Experimental/` - Blender add-on sources for M2/WMO import/export reference.
- `wow.export/` - wow.export source tree and Blender OBJ importer reference. - `wow.export/` - wow.export source tree and Blender OBJ importer reference.
- `noggit-red/` - Noggit RED source tree from `https://gitlab.com/dirtbikercj/noggit-red`, used as a reference for WoW 3.3.5 map editing, ADT/WMO/M2 placement behavior, UID handling and editor workflows. - `noggit-red/` - Noggit RED source tree from `https://gitlab.com/dirtbikercj/noggit-red`, used as a reference for WoW 3.3.5 map editing, ADT/WMO/M2 placement behavior, UID handling and editor workflows.
+1
Submodule reference/benilla added at bc1a2428dd
@@ -39,9 +39,12 @@ func start_instance_playback(
var phase := phase_for_instance(relative_path, instance_index) var phase := phase_for_instance(relative_path, instance_index)
var native_diagnostics: Array[Dictionary] = [] var native_diagnostics: Array[Dictionary] = []
for animator in native_animators_in_subtree(root, native_animator_script): for animator in native_animators_in_subtree(root, native_animator_script):
if animator.has_method("prepare_runtime"): if animator.has_method("prepare_runtime_at_phase"):
animator.prepare_runtime() animator.prepare_runtime_at_phase(phase)
animator.set_phase(phase) else:
if animator.has_method("prepare_runtime"):
animator.prepare_runtime()
animator.set_phase(phase)
if collect_native_diagnostics and animator.has_method("runtime_debug_state"): if collect_native_diagnostics and animator.has_method("runtime_debug_state"):
var diagnostic_variant = animator.runtime_debug_state() var diagnostic_variant = animator.runtime_debug_state()
if diagnostic_variant is Dictionary: if diagnostic_variant is Dictionary:
@@ -0,0 +1,73 @@
class_name WmoSceneInstanceFactory
extends RefCounted
## Creates detached cached/live WMO Node3D instances with the existing cache
## validation, naming and world-placement rules. Runtime preparation is separate.
var _scene_cache_validator: Object
var _placement_resolver: Object
func _init(scene_cache_validator: Object, placement_resolver: Object) -> void:
_scene_cache_validator = scene_cache_validator
_placement_resolver = placement_resolver
## Returns whether [param node] satisfies the injected WMO scene-cache contract.
## Null nodes or missing validators return false without mutation.
func is_cached_node_current(node: Node) -> bool:
if node == null or _scene_cache_validator == null:
return false
return bool(_scene_cache_validator.call("is_scene_cache_current", node))
## Instantiates, validates, names and places a cached WMO PackedScene. Rejected
## instantiated roots are freed synchronously. The accepted detached Node3D is
## caller-owned and retains its exact descendant Resource identities.
func instantiate_cached_scene(
relative_path: String,
scene: PackedScene,
placement: Dictionary) -> Node3D:
if scene == null or _placement_resolver == null:
return null
var instantiated_root := scene.instantiate()
if not (instantiated_root is Node3D):
if instantiated_root != null:
instantiated_root.free()
return null
var instance := instantiated_root as Node3D
if not is_cached_node_current(instance):
instance.free()
return null
_apply_identity_and_placement(instance, relative_path, placement)
return instance
## Duplicates, names and places a live-built WMO prototype. The detached result
## is caller-owned. Null inputs, missing placement composition or an unexpected
## non-Node3D duplicate return null; rejected duplicates are freed synchronously.
func duplicate_live_prototype(
relative_path: String,
prototype: Node3D,
placement: Dictionary) -> Node3D:
if prototype == null or _placement_resolver == null:
return null
var duplicated_root := prototype.duplicate()
if not (duplicated_root is Node3D):
if duplicated_root != null:
duplicated_root.free()
return null
var instance := duplicated_root as Node3D
_apply_identity_and_placement(instance, relative_path, placement)
return instance
func _apply_identity_and_placement(
instance: Node3D,
relative_path: String,
placement: Dictionary) -> void:
instance.name = relative_path.get_file().get_basename()
instance.transform = _placement_resolver.call(
"resolve_world_transform",
placement
) as Transform3D
@@ -0,0 +1 @@
uid://c13w66d7uaf2n
+44 -16
View File
@@ -26,11 +26,13 @@ func setup(target_mesh_instance: MeshInstance3D, bone_data: Array, surface_data:
_capture_materials() _capture_materials()
_make_mesh_unique() _make_mesh_unique()
_rebuild_mesh(0.0) _rebuild_mesh(0.0)
set_process(mesh != null and not bones.is_empty() and not surfaces.is_empty() and animation_length > 0.0) _prepared = _has_runtime_animation_data()
set_process(_prepared)
func _ready() -> void: func _ready() -> void:
prepare_runtime() if not _prepared:
prepare_runtime()
func _process(delta: float) -> void: func _process(delta: float) -> void:
@@ -41,24 +43,49 @@ func _process(delta: float) -> void:
func set_phase(phase: float) -> void: func set_phase(phase: float) -> void:
if animation_length <= 0.0: _set_phase_time(phase)
_time = 0.0
else:
_time = fposmod(animation_length * phase, animation_length)
_rebuild_mesh(_time) _rebuild_mesh(_time)
func prepare_runtime() -> bool: func prepare_runtime() -> bool:
return _prepare_runtime(false)
## Rebinds a duplicated animator to its local mesh and applies its deterministic
## phase with one deformation rebuild. This must happen before attachment so
## _ready() can remain idempotent for already prepared runtime instances.
func prepare_runtime_at_phase(phase: float) -> bool:
_set_phase_time(phase)
return _prepare_runtime(true)
func _prepare_runtime(force_rebuild: bool) -> bool:
if _prepared and not force_rebuild:
set_process(true)
return true
_resolve_mesh_instance() _resolve_mesh_instance()
if force_rebuild:
_materials.clear()
_capture_materials() _capture_materials()
_unique_mesh_ready = false _unique_mesh_ready = false
_make_mesh_unique() _make_mesh_unique()
_rebuild_mesh(_time) _rebuild_mesh(_time)
_prepared = mesh != null and not bones.is_empty() and not surfaces.is_empty() and animation_length > 0.0 _prepared = _has_runtime_animation_data()
set_process(_prepared) set_process(_prepared)
return _prepared return _prepared
func _has_runtime_animation_data() -> bool:
return mesh != null and not bones.is_empty() and not surfaces.is_empty() and animation_length > 0.0
func _set_phase_time(phase: float) -> void:
if animation_length <= 0.0:
_time = 0.0
else:
_time = fposmod(animation_length * phase, animation_length)
func runtime_debug_state() -> Dictionary: func runtime_debug_state() -> Dictionary:
return { return {
"prepared": _prepared, "prepared": _prepared,
@@ -81,11 +108,12 @@ func _resolve_mesh_instance() -> void:
func _make_mesh_unique() -> void: func _make_mesh_unique() -> void:
if _unique_mesh_ready or mesh_instance == null or mesh_instance.mesh == null: if _unique_mesh_ready or mesh_instance == null or mesh_instance.mesh == null:
return return
var duplicated := mesh_instance.mesh.duplicate(true) as ArrayMesh # _rebuild_mesh() replaces every surface from the retained native arrays, so
if duplicated == null: # copying the source ArrayMesh would only duplicate data that is discarded.
return # Materials were captured before this call and are intentionally shared.
mesh_instance.mesh = duplicated var instance_mesh := ArrayMesh.new()
mesh = duplicated mesh_instance.mesh = instance_mesh
mesh = instance_mesh
_unique_mesh_ready = true _unique_mesh_ready = true
@@ -143,20 +171,20 @@ func _rebuild_mesh(time: float) -> void:
continue continue
var transform: Transform3D = bone_matrices[bone_index] var transform: Transform3D = bone_matrices[bone_index]
skinned_pos += transform * base_vertices[vertex_index] * weight skinned_pos += transform * base_vertices[vertex_index] * weight
if normals.size() == base_normals.size(): if not normals.is_empty():
skinned_nrm += (transform.basis * base_normals[vertex_index]) * weight skinned_nrm += (transform.basis * base_normals[vertex_index]) * weight
total_weight += weight total_weight += weight
if total_weight > 0.0: if total_weight > 0.0:
vertices[vertex_index] = skinned_pos / total_weight vertices[vertex_index] = skinned_pos / total_weight
if normals.size() == base_normals.size(): if not normals.is_empty():
normals[vertex_index] = (skinned_nrm / total_weight).normalized() normals[vertex_index] = (skinned_nrm / total_weight).normalized()
else: else:
vertices[vertex_index] = base_vertices[vertex_index] vertices[vertex_index] = base_vertices[vertex_index]
if normals.size() == base_normals.size(): if not normals.is_empty():
normals[vertex_index] = base_normals[vertex_index] normals[vertex_index] = base_normals[vertex_index]
else: else:
vertices[vertex_index] = base_vertices[vertex_index] vertices[vertex_index] = base_vertices[vertex_index]
if normals.size() == base_normals.size(): if not normals.is_empty():
normals[vertex_index] = base_normals[vertex_index] normals[vertex_index] = base_normals[vertex_index]
var arrays := [] var arrays := []
+23 -26
View File
@@ -42,6 +42,9 @@ const WMO_RENDER_GROUP_MATERIALIZER_SCRIPT := preload(
const WMO_RUNTIME_SCENE_PREPARER_SCRIPT := preload( const WMO_RUNTIME_SCENE_PREPARER_SCRIPT := preload(
"res://src/render/wmo/wmo_runtime_scene_preparer.gd" "res://src/render/wmo/wmo_runtime_scene_preparer.gd"
) )
const WMO_SCENE_INSTANCE_FACTORY_SCRIPT := preload(
"res://src/render/wmo/wmo_scene_instance_factory.gd"
)
const M2_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/m2_builder.gd") const M2_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/m2_builder.gd")
const M2_NATIVE_ANIMATOR_SCRIPT := preload("res://src/scenes/streaming/m2_native_animator.gd") const M2_NATIVE_ANIMATOR_SCRIPT := preload("res://src/scenes/streaming/m2_native_animator.gd")
const STREAMING_FOCUS_SCRIPT := preload("res://src/domain/streaming/streaming_focus.gd") const STREAMING_FOCUS_SCRIPT := preload("res://src/domain/streaming/streaming_focus.gd")
@@ -393,6 +396,10 @@ var _wmo_runtime_scene_preparer := WMO_RUNTIME_SCENE_PREPARER_SCRIPT.new(
) )
var _wmo_missing_cache: Dictionary = {} var _wmo_missing_cache: Dictionary = {}
var _wmo_placement_resolver := WMO_PLACEMENT_RESOLVER_SCRIPT.new() var _wmo_placement_resolver := WMO_PLACEMENT_RESOLVER_SCRIPT.new()
var _wmo_scene_instance_factory := WMO_SCENE_INSTANCE_FACTORY_SCRIPT.new(
WMO_BUILDER_SCRIPT,
_wmo_placement_resolver
)
var _world_wmo_root: Node3D var _world_wmo_root: Node3D
var _wmo_placement_registry := WMO_PLACEMENT_REGISTRY_SCRIPT.new() var _wmo_placement_registry := WMO_PLACEMENT_REGISTRY_SCRIPT.new()
var _wmo_render_build_step_planner := WMO_RENDER_BUILD_STEP_PLANNER_SCRIPT.new() var _wmo_render_build_step_planner := WMO_RENDER_BUILD_STEP_PLANNER_SCRIPT.new()
@@ -3692,7 +3699,13 @@ func _process_wmo_build_jobs() -> void:
) )
refs.append(unique_key) refs.append(unique_key)
else: else:
var scene_instance := _instantiate_wmo_scene(rel_path, scene, placement) var scene_instance := (
_wmo_scene_instance_factory.instantiate_cached_scene(
rel_path,
scene,
placement
)
)
if scene_instance != null: if scene_instance != null:
_prepare_runtime_wmo_instance(scene_instance) _prepare_runtime_wmo_instance(scene_instance)
_world_wmo_root.add_child(scene_instance) _world_wmo_root.add_child(scene_instance)
@@ -3920,26 +3933,6 @@ func _get_resource_file_size(path: String) -> int:
return int(length) return int(length)
func _is_wmo_node_cache_current(node: Node) -> bool:
if node == null:
return false
return bool(WMO_BUILDER_SCRIPT.is_scene_cache_current(node))
func _instantiate_wmo_scene(rel_path: String, scene: PackedScene, placement: Dictionary) -> Node3D:
if scene == null:
return null
var instance := scene.instantiate() as Node3D
if instance == null:
return null
if not _is_wmo_node_cache_current(instance):
instance.free()
return null
instance.name = rel_path.get_file().get_basename()
instance.transform = _wmo_placement_resolver.resolve_world_transform(placement)
return instance
func _prepare_runtime_wmo_instance(instance: Node3D) -> void: func _prepare_runtime_wmo_instance(instance: Node3D) -> void:
_wmo_runtime_scene_preparer.prepare_cached_instance( _wmo_runtime_scene_preparer.prepare_cached_instance(
instance, instance,
@@ -4632,11 +4625,15 @@ func _instantiate_wmo_world(rel_path: String, placement: Dictionary) -> Node3D:
if prototype == null: if prototype == null:
return null return null
var instance := prototype.duplicate()
instance.name = rel_path.get_file().get_basename()
# WMOs are parented to _terrain_root, whose position absorbs the editor # WMOs are parented to _terrain_root, whose position absorbs the editor
# offset — use world-space placement.pos directly (no tile_origin subtraction). # offset — the factory applies world-space placement.pos directly.
instance.transform = _wmo_placement_resolver.resolve_world_transform(placement) var instance := _wmo_scene_instance_factory.duplicate_live_prototype(
rel_path,
prototype,
placement
)
if instance == null:
return null
_wmo_runtime_scene_preparer.prepare_live_instance( _wmo_runtime_scene_preparer.prepare_live_instance(
instance, instance,
enable_occlusion_culling, enable_occlusion_culling,
@@ -4736,7 +4733,7 @@ func _get_or_load_wmo_prototype(rel_path: String) -> Node3D:
var resource: Resource = load(cache_path) var resource: Resource = load(cache_path)
if resource is PackedScene: if resource is PackedScene:
var node = (resource as PackedScene).instantiate() var node = (resource as PackedScene).instantiate()
if node is Node3D and _is_wmo_node_cache_current(node): if node is Node3D and _wmo_scene_instance_factory.is_cached_node_current(node):
_wmo_prototype_cache[normalized_rel] = node as Node3D _wmo_prototype_cache[normalized_rel] = node as Node3D
return node as Node3D return node as Node3D
if node is Node: if node is Node:
+2 -2
View File
@@ -176,14 +176,14 @@ func _bake_glb_animation_cache(
if not force and FileAccess.file_exists(abs_out_glb): if not force and FileAccess.file_exists(abs_out_glb):
return true return true
var abs_converter := ProjectSettings.globalize_path(converter) var abs_converter := ProjectSettings.globalize_path(converter)
var abs_output := ProjectSettings.globalize_path(output_dir) var converter_output_directory := abs_out_glb.get_base_dir()
if not FileAccess.file_exists(abs_converter): if not FileAccess.file_exists(abs_converter):
push_warning("M2 GLB converter not found: %s" % converter) push_warning("M2 GLB converter not found: %s" % converter)
return false return false
var stdout := [] var stdout := []
var exit_code := OS.execute( var exit_code := OS.execute(
python_exe, python_exe,
[abs_converter, abs_m2, abs_output], [abs_converter, abs_m2, converter_output_directory],
stdout, stdout,
true, true,
false) false)
@@ -16,6 +16,7 @@ func _initialize() -> void:
_verify_animation_selection_priority(failures) _verify_animation_selection_priority(failures)
_verify_player_loop_play_and_seek(failures) _verify_player_loop_play_and_seek(failures)
_verify_native_copy_start_and_diagnostics(failures) _verify_native_copy_start_and_diagnostics(failures)
_verify_native_single_rebuild_preparation(failures)
_verify_ownership_boundaries(failures) _verify_ownership_boundaries(failures)
var elapsed_milliseconds := _verify_bounded_timing(failures) var elapsed_milliseconds := _verify_bounded_timing(failures)
if not failures.is_empty(): if not failures.is_empty():
@@ -24,7 +25,7 @@ func _initialize() -> void:
quit(1) quit(1)
return return
print( print(
"M2_ANIMATION_PLAYBACK_CONTROLLER PASS cases=15 iterations=20000 elapsed_ms=%.3f" "M2_ANIMATION_PLAYBACK_CONTROLLER PASS cases=20 iterations=20000 elapsed_ms=%.3f"
% elapsed_milliseconds % elapsed_milliseconds
) )
quit(0) quit(0)
@@ -142,6 +143,47 @@ func _verify_native_copy_start_and_diagnostics(failures: Array[String]) -> void:
target_root.free() target_root.free()
func _verify_native_single_rebuild_preparation(failures: Array[String]) -> void:
var fixture_root := Node3D.new()
var mesh_instance := MeshInstance3D.new()
mesh_instance.name = "Mesh"
var source_mesh := ArrayMesh.new()
var source_arrays := []
source_arrays.resize(Mesh.ARRAY_MAX)
source_arrays[Mesh.ARRAY_VERTEX] = PackedVector3Array([
Vector3.ZERO,
Vector3.RIGHT,
Vector3.UP,
])
source_arrays[Mesh.ARRAY_INDEX] = PackedInt32Array([0, 1, 2])
source_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, source_arrays)
var shared_material := StandardMaterial3D.new()
source_mesh.surface_set_material(0, shared_material)
mesh_instance.mesh = source_mesh
fixture_root.add_child(mesh_instance)
var animator: Node = NATIVE_ANIMATOR_SCRIPT.new()
fixture_root.add_child(animator)
animator.setup(
mesh_instance,
[{"parent": -1, "pivot": Vector3.ZERO}],
[{
"vertices": PackedVector3Array([Vector3.ZERO, Vector3.RIGHT, Vector3.UP]),
"indices": PackedInt32Array([0, 1, 2]),
}],
4.0
)
var prepared := bool(animator.prepare_runtime_at_phase(0.25))
var phased_mesh: Mesh = mesh_instance.mesh
animator.call("_ready")
_expect_true(prepared, "native phased preparation succeeds", failures)
_expect_float_equal(float(animator.get("_time")), 1.0, "native phased preparation time", failures)
_expect_true(phased_mesh != source_mesh, "native phased Mesh is instance-local", failures)
_expect_same(phased_mesh.surface_get_material(0), shared_material, "native Material remains shared", failures)
_expect_same(mesh_instance.mesh, phased_mesh, "native ready does not duplicate prepared mesh", failures)
fixture_root.free()
func _verify_ownership_boundaries(failures: Array[String]) -> void: func _verify_ownership_boundaries(failures: Array[String]) -> void:
var controller_source := FileAccess.get_file_as_string(CONTROLLER_PATH) var controller_source := FileAccess.get_file_as_string(CONTROLLER_PATH)
var materializer_source := FileAccess.get_file_as_string(MATERIALIZER_PATH) var materializer_source := FileAccess.get_file_as_string(MATERIALIZER_PATH)
@@ -156,6 +198,17 @@ func _verify_ownership_boundaries(failures: Array[String]) -> void:
_expect_false(loader_source.contains(removed_loader_function), "legacy helper removed: %s" % removed_loader_function, failures) _expect_false(loader_source.contains(removed_loader_function), "legacy helper removed: %s" % removed_loader_function, failures)
_expect_equal(materializer_source.count("_animation_playback_controller.copy_native_animator_data("), 1, "native copy delegates once", failures) _expect_equal(materializer_source.count("_animation_playback_controller.copy_native_animator_data("), 1, "native copy delegates once", failures)
_expect_equal(materializer_source.count("_animation_playback_controller.start_instance_playback("), 1, "playback delegates once", failures) _expect_equal(materializer_source.count("_animation_playback_controller.start_instance_playback("), 1, "playback delegates once", failures)
_expect_true(
controller_source.contains("animator.prepare_runtime_at_phase(phase)"),
"native phase preparation uses one rebuild",
failures
)
var native_animator_source := FileAccess.get_file_as_string("res://src/scenes/streaming/m2_native_animator.gd")
_expect_true(
native_animator_source.contains("if not _prepared:\n\t\tprepare_runtime()"),
"ready is idempotent after pre-attachment preparation",
failures
)
for retained_materializer_rule in [ for retained_materializer_rule in [
"Node.DUPLICATE_SIGNALS | Node.DUPLICATE_GROUPS | Node.DUPLICATE_SCRIPTS", "Node.DUPLICATE_SIGNALS | Node.DUPLICATE_GROUPS | Node.DUPLICATE_SCRIPTS",
"batch_root.add_child(instance)", "batch_root.add_child(instance)",
@@ -0,0 +1,173 @@
extends SceneTree
## M03 closeout gate for cache versions and worker/main-thread render boundaries.
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
const MANIFEST_PATH := "res://src/tools/render_baseline_manifest.json"
const M2_CACHE_BAKER_PATH := "res://src/tools/bake_m2_cache.gd"
const WORKER_FUNCTIONS: Array[String] = [
"_load_tile_task",
"_load_terrain_splat_task",
"_load_tile_water_task",
"_group_tile_m2_task",
]
const WORKER_FORBIDDEN_TOKENS: Array[String] = [
"RenderingServer",
".add_child(",
".queue_free(",
".free(",
"Node3D.new(",
"MeshInstance3D.new(",
"MultiMeshInstance3D.new(",
"ArrayMesh.new(",
"build_tile_water_scene(",
"build_tile_coarse_render_payload(",
]
const MAIN_THREAD_FRAME_STEPS: Array[String] = [
"_drain_tile_load_results()",
"_drain_terrain_upgrade_results()",
"_drain_terrain_control_splat_cache_results()",
"_drain_terrain_splat_cache_results()",
"_drain_terrain_splat_results()",
"_process_water_load_queue()",
"_drain_water_load_results()",
"_process_queues()",
"_drain_m2_group_results()",
"_drain_m2_animation_loads()",
"_drain_m2_mesh_loads()",
"_process_m2_build_jobs()",
"_process_wmo_build_jobs()",
"_process_wmo_render_build_jobs()",
"_process_detail_asset_queue()",
]
func _initialize() -> void:
var failures: Array[String] = []
var loader_source := _read_text(LOADER_PATH, failures)
var process_source := _function_source(loader_source, "_process", failures)
_verify_frame_entrypoint(process_source, failures)
_verify_worker_boundaries(loader_source, failures)
_verify_rendering_server_boundary(loader_source, failures)
_verify_cache_contract(loader_source, failures)
_verify_m2_glb_cache_output_contract(failures)
if not failures.is_empty():
for failure in failures:
push_error("RENDERER_CLOSEOUT_CONTRACTS: %s" % failure)
quit(1)
return
print("RENDERER_CLOSEOUT_CONTRACTS PASS workers=%d frame_steps=%d cache_versions=7 nested_glb=1" % [
WORKER_FUNCTIONS.size(),
MAIN_THREAD_FRAME_STEPS.size(),
])
quit(0)
func _verify_frame_entrypoint(process_source: String, failures: Array[String]) -> void:
var scheduler_index := process_source.find("_render_budget_scheduler.begin_frame(")
if scheduler_index < 0:
failures.append("_process does not begin a render-budget frame")
return
for step in MAIN_THREAD_FRAME_STEPS:
var step_index := process_source.find(step)
if step_index < 0:
failures.append("_process is missing main-thread step %s" % step)
elif step_index < scheduler_index:
failures.append("%s runs before render-budget frame admission" % step)
func _verify_worker_boundaries(loader_source: String, failures: Array[String]) -> void:
for function_name in WORKER_FUNCTIONS:
var function_source := _function_source(loader_source, function_name, failures)
for forbidden_token in WORKER_FORBIDDEN_TOKENS:
if function_source.contains(forbidden_token):
failures.append("worker %s contains main-thread token %s" % [
function_name,
forbidden_token,
])
func _verify_rendering_server_boundary(loader_source: String, failures: Array[String]) -> void:
var remaining_source := loader_source
for function_name in ["_create_render_instance", "_free_render_instance"]:
var function_source := _function_source(loader_source, function_name, failures)
remaining_source = remaining_source.replace(function_source, "")
if remaining_source.contains("RenderingServer.instance_create("):
failures.append("RenderingServer instance creation escaped the owned adapter")
if remaining_source.contains("RenderingServer.free_rid("):
failures.append("RenderingServer RID release escaped the owned adapter")
func _verify_cache_contract(loader_source: String, failures: Array[String]) -> void:
var manifest_source := _read_text(MANIFEST_PATH, failures)
var parsed_manifest = JSON.parse_string(manifest_source)
if not (parsed_manifest is Dictionary):
failures.append("baseline manifest is not a Dictionary")
return
var cache_contract: Dictionary = parsed_manifest.get("cache_contract", {})
var expected_versions := {
"baked_terrain": 5,
"streaming_terrain": 2,
"terrain_splat": 1,
"terrain_control_splat": 3,
"wmo_streaming": 2,
"wmo_builder": 2,
"m2_material": 2,
}
if cache_contract.size() != expected_versions.size():
failures.append("cache contract key count changed: expected=%d actual=%d" % [
expected_versions.size(),
cache_contract.size(),
])
for cache_name in expected_versions:
if not cache_contract.has(cache_name):
failures.append("cache contract is missing %s" % cache_name)
continue
var cache_record: Dictionary = cache_contract[cache_name]
if int(cache_record.get("version", -1)) != int(expected_versions[cache_name]):
failures.append("cache version changed for %s" % cache_name)
var required_loader_declarations := [
"const REQUIRED_BAKED_TILE_FORMAT_VERSION := 5",
"const REQUIRED_SPLAT_TILE_FORMAT_VERSION := 1",
"const REQUIRED_CONTROL_SPLAT_TILE_FORMAT_VERSION := 3",
"STREAMING_TILE_SCRIPT.FORMAT_VERSION",
"WMO_STREAMING_SCRIPT.FORMAT_VERSION",
]
for declaration in required_loader_declarations:
if not loader_source.contains(declaration):
failures.append("loader cache-version boundary is missing %s" % declaration)
func _verify_m2_glb_cache_output_contract(failures: Array[String]) -> void:
var baker_source := _read_text(M2_CACHE_BAKER_PATH, failures)
if not baker_source.contains(
"var converter_output_directory := abs_out_glb.get_base_dir()"):
failures.append("M2 GLB converter output is not derived from the nested cache path")
if not baker_source.contains(
"[abs_converter, abs_m2, converter_output_directory]"):
failures.append("M2 GLB converter does not receive the nested output directory")
func _function_source(source: String, function_name: String, failures: Array[String]) -> String:
var signature := "func %s(" % function_name
var start_index := source.find(signature)
if start_index < 0:
failures.append("missing function %s" % function_name)
return ""
var next_function_index := source.find("\nfunc ", start_index + signature.length())
if next_function_index < 0:
return source.substr(start_index)
return source.substr(start_index, next_function_index - start_index)
func _read_text(path: String, failures: Array[String]) -> String:
var file := FileAccess.open(path, FileAccess.READ)
if file == null:
failures.append("cannot read %s" % path)
return ""
var content := file.get_as_text()
file.close()
return content
@@ -0,0 +1 @@
uid://nqj0mu6r8omf
+10 -3
View File
@@ -4,6 +4,7 @@ extends SceneTree
const RESOLVER_SCRIPT := preload("res://src/render/wmo/wmo_placement_resolver.gd") const RESOLVER_SCRIPT := preload("res://src/render/wmo/wmo_placement_resolver.gd")
const RESOLVER_PATH := "res://src/render/wmo/wmo_placement_resolver.gd" const RESOLVER_PATH := "res://src/render/wmo/wmo_placement_resolver.gd"
const INSTANCE_FACTORY_PATH := "res://src/render/wmo/wmo_scene_instance_factory.gd"
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd" const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
@@ -25,7 +26,7 @@ func _initialize() -> void:
quit(1) quit(1)
return return
print( print(
"WMO_PLACEMENT_RESOLVER PASS cases=9 iterations=20000 elapsed_ms=%.3f" "WMO_PLACEMENT_RESOLVER PASS cases=10 iterations=20000 elapsed_ms=%.3f"
% elapsed_milliseconds % elapsed_milliseconds
) )
quit(0) quit(0)
@@ -138,6 +139,7 @@ func _verify_node_property_equivalence(failures: Array[String]) -> void:
func _verify_source_boundaries(failures: Array[String]) -> void: func _verify_source_boundaries(failures: Array[String]) -> void:
var loader_source := _read_text(LOADER_PATH, failures) var loader_source := _read_text(LOADER_PATH, failures)
var resolver_source := _read_text(RESOLVER_PATH, failures) var resolver_source := _read_text(RESOLVER_PATH, failures)
var instance_factory_source := _read_text(INSTANCE_FACTORY_PATH, failures)
_expect_true( _expect_true(
loader_source.contains("WMO_PLACEMENT_RESOLVER_SCRIPT.new()"), loader_source.contains("WMO_PLACEMENT_RESOLVER_SCRIPT.new()"),
"loader composes resolver", "loader composes resolver",
@@ -157,8 +159,13 @@ func _verify_source_boundaries(failures: Array[String]) -> void:
) )
_expect_equal_int( _expect_equal_int(
loader_source.count("_wmo_placement_resolver.resolve_world_transform("), loader_source.count("_wmo_placement_resolver.resolve_world_transform("),
3, 1,
"three transform adapters", "one direct lightweight render-root transform adapter",
failures
)
_expect_true(
instance_factory_source.contains('"resolve_world_transform"'),
"cached/live instance factory delegates transform resolution",
failures failures
) )
_expect_true( _expect_true(
@@ -0,0 +1 @@
uid://bhj5k6lmi7na8
@@ -0,0 +1,294 @@
extends SceneTree
## Asset-free WMO cached/live instance validation, identity, placement,
## lifetime, source-boundary and bounded-timing regression.
const FACTORY_SCRIPT := preload("res://src/render/wmo/wmo_scene_instance_factory.gd")
const FACTORY_PATH := "res://src/render/wmo/wmo_scene_instance_factory.gd"
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
class FakeSceneCacheValidator extends RefCounted:
var is_current := true
var inspected_nodes: Array[Node] = []
var events: Array[String]
func _init(shared_events: Array[String] = []) -> void:
events = shared_events
func is_scene_cache_current(node: Node) -> bool:
inspected_nodes.append(node)
events.append("validate")
return is_current
class FakePlacementResolver extends RefCounted:
var resolved_transform := Transform3D(Basis.IDENTITY, Vector3(4.0, 5.0, 6.0))
var placements: Array[Dictionary] = []
var events: Array[String]
func _init(shared_events: Array[String] = []) -> void:
events = shared_events
func resolve_world_transform(placement: Dictionary) -> Transform3D:
placements.append(placement.duplicate(true))
events.append("resolve")
return resolved_transform
func _initialize() -> void:
var failures: Array[String] = []
_verify_cached_scene_contract(failures)
_verify_stale_and_wrong_root_rejection(failures)
_verify_live_prototype_contract(failures)
_verify_invalid_composition(failures)
_verify_source_boundaries(failures)
var elapsed_milliseconds := _verify_bounded_timing(failures)
if not failures.is_empty():
for failure in failures:
push_error("WMO_SCENE_INSTANCE_FACTORY: %s" % failure)
quit(1)
return
print(
"WMO_SCENE_INSTANCE_FACTORY PASS cases=41 iterations=1000 elapsed_ms=%.3f"
% elapsed_milliseconds
)
quit(0)
func _verify_cached_scene_contract(failures: Array[String]) -> void:
var events: Array[String] = []
var validator := FakeSceneCacheValidator.new(events)
var resolver := FakePlacementResolver.new(events)
var factory := FACTORY_SCRIPT.new(validator, resolver)
var fixture := _create_packed_wmo_scene()
var scene: PackedScene = fixture["scene"]
var source_mesh: Mesh = fixture["mesh"]
var placement := {"pos": Vector3(1.0, 2.0, 3.0), "rotation": Vector3.ZERO}
var instance: Node3D = factory.instantiate_cached_scene(
"World/Wmo/Stormwind/Keep.wmo",
scene,
placement
)
_expect_true(instance != null, "current cached scene accepted", failures)
_expect_true(instance.get_parent() == null, "cached result remains detached", failures)
_expect_string_equal(instance.name, "Keep", "cached basename applied", failures)
_expect_true(instance.transform == resolver.resolved_transform, "cached transform applied", failures)
_expect_equal(validator.inspected_nodes.size(), 1, "cached root validated once", failures)
_expect_same(validator.inspected_nodes[0], instance, "accepted exact root validated", failures)
_expect_true(resolver.placements == [placement], "cached placement forwarded", failures)
_expect_true(events == ["validate", "resolve"], "validate precedes placement", failures)
var mesh_child := instance.get_node_or_null("Group") as MeshInstance3D
_expect_true(mesh_child != null, "cached Mesh child retained", failures)
_expect_same(mesh_child.mesh, source_mesh, "cached descendant Mesh identity retained", failures)
_expect_true(factory.is_cached_node_current(instance), "public currentness delegates", failures)
instance.free()
func _verify_stale_and_wrong_root_rejection(failures: Array[String]) -> void:
var validator := FakeSceneCacheValidator.new()
validator.is_current = false
var resolver := FakePlacementResolver.new()
var factory := FACTORY_SCRIPT.new(validator, resolver)
var fixture := _create_packed_wmo_scene()
_expect_true(
factory.instantiate_cached_scene("world/stale.wmo", fixture["scene"], {}) == null,
"stale cached scene rejected",
failures
)
_expect_equal(validator.inspected_nodes.size(), 1, "stale root validated once", failures)
_expect_false(
is_instance_valid(validator.inspected_nodes[0]),
"stale instantiated root freed",
failures
)
_expect_equal(resolver.placements.size(), 0, "stale root skips placement", failures)
var control_root := Control.new()
var wrong_root_scene := PackedScene.new()
_expect_equal(wrong_root_scene.pack(control_root), OK, "wrong-root fixture packed", failures)
control_root.free()
validator.is_current = true
_expect_true(
factory.instantiate_cached_scene("world/control.wmo", wrong_root_scene, {}) == null,
"non-Node3D cached root rejected",
failures
)
_expect_equal(validator.inspected_nodes.size(), 1, "wrong root skips validator", failures)
func _verify_live_prototype_contract(failures: Array[String]) -> void:
var validator := FakeSceneCacheValidator.new()
var resolver := FakePlacementResolver.new()
var factory := FACTORY_SCRIPT.new(validator, resolver)
var prototype := Node3D.new()
prototype.name = "Prototype"
var source_mesh := ArrayMesh.new()
var mesh_child := MeshInstance3D.new()
mesh_child.name = "Group"
mesh_child.mesh = source_mesh
prototype.add_child(mesh_child)
var placement := {"pos": Vector3(7.0, 8.0, 9.0)}
var instance: Node3D = factory.duplicate_live_prototype(
"World/Live/Abbey.wmo",
prototype,
placement
)
_expect_true(instance != null, "live prototype duplicated", failures)
_expect_false(is_same(instance, prototype), "live root identity isolated", failures)
_expect_true(instance.get_parent() == null, "live result remains detached", failures)
_expect_string_equal(instance.name, "Abbey", "live basename applied", failures)
_expect_true(instance.transform == resolver.resolved_transform, "live transform applied", failures)
_expect_same(
(instance.get_node("Group") as MeshInstance3D).mesh,
source_mesh,
"live descendant Mesh identity retained",
failures
)
_expect_equal(validator.inspected_nodes.size(), 0, "live path skips cache validator", failures)
_expect_true(resolver.placements == [placement], "live placement forwarded", failures)
instance.free()
prototype.free()
func _verify_invalid_composition(failures: Array[String]) -> void:
var validator := FakeSceneCacheValidator.new()
var resolver := FakePlacementResolver.new()
var factory := FACTORY_SCRIPT.new(validator, resolver)
_expect_false(factory.is_cached_node_current(null), "null currentness rejected", failures)
_expect_true(
factory.instantiate_cached_scene("world/a.wmo", null, {}) == null,
"null PackedScene rejected",
failures
)
_expect_true(
factory.duplicate_live_prototype("world/a.wmo", null, {}) == null,
"null prototype rejected",
failures
)
var fixture := _create_packed_wmo_scene()
var missing_validator_factory := FACTORY_SCRIPT.new(null, resolver)
_expect_true(
missing_validator_factory.instantiate_cached_scene(
"world/a.wmo", fixture["scene"], {}
) == null,
"missing validator rejects cached instance",
failures
)
var missing_resolver_factory := FACTORY_SCRIPT.new(validator, null)
_expect_true(
missing_resolver_factory.instantiate_cached_scene(
"world/a.wmo", fixture["scene"], {}
) == null,
"missing resolver rejects cached instance before allocation",
failures
)
var prototype := Node3D.new()
_expect_true(
missing_resolver_factory.duplicate_live_prototype("world/a.wmo", prototype, {}) == null,
"missing resolver rejects live duplicate",
failures
)
prototype.free()
func _verify_source_boundaries(failures: Array[String]) -> void:
var factory_source := FileAccess.get_file_as_string(FACTORY_PATH)
var loader_source := FileAccess.get_file_as_string(LOADER_PATH)
_expect_true(
loader_source.contains("WMO_SCENE_INSTANCE_FACTORY_SCRIPT.new("),
"loader composes scene instance factory",
failures
)
_expect_true(
loader_source.contains("_wmo_scene_instance_factory.instantiate_cached_scene("),
"loader delegates cached instantiation",
failures
)
_expect_true(
loader_source.contains("_wmo_scene_instance_factory.duplicate_live_prototype("),
"loader delegates live duplication",
failures
)
for released_token in [
"func _is_wmo_node_cache_current(",
"func _instantiate_wmo_scene(",
]:
_expect_false(loader_source.contains(released_token), "loader releases %s" % released_token, failures)
for owned_token in [
"scene.instantiate()",
"prototype.duplicate()",
"is_scene_cache_current",
"relative_path.get_file().get_basename()",
"resolve_world_transform",
]:
_expect_true(factory_source.contains(owned_token), "factory owns %s" % owned_token, failures)
for forbidden_dependency in [
"ResourceLoader.",
"FileAccess.",
"WorkerThreadPool.",
"RenderingServer.",
"add_child(",
".owner =",
"WmoRuntimeScenePreparer",
"_wmo_render_build_queue",
]:
_expect_false(factory_source.contains(forbidden_dependency), "factory omits %s" % forbidden_dependency, failures)
func _verify_bounded_timing(failures: Array[String]) -> float:
var validator := FakeSceneCacheValidator.new()
var resolver := FakePlacementResolver.new()
var factory := FACTORY_SCRIPT.new(validator, resolver)
var prototype := Node3D.new()
var started_microseconds := Time.get_ticks_usec()
for iteration in 1000:
var instance: Node3D = factory.duplicate_live_prototype(
"world/timing.wmo", prototype, {}
)
instance.free()
var elapsed_milliseconds := float(Time.get_ticks_usec() - started_microseconds) / 1000.0
_expect_equal(resolver.placements.size(), 1000, "timing duplicates complete", failures)
_expect_true(elapsed_milliseconds < 1000.0, "1000 duplicates under one second", failures)
prototype.free()
return elapsed_milliseconds
func _create_packed_wmo_scene() -> Dictionary:
var source_root := Node3D.new()
var mesh := ArrayMesh.new()
var mesh_child := MeshInstance3D.new()
mesh_child.name = "Group"
mesh_child.mesh = mesh
source_root.add_child(mesh_child)
mesh_child.owner = source_root
var scene := PackedScene.new()
var pack_error := scene.pack(source_root)
if pack_error != OK:
push_error("WMO_SCENE_INSTANCE_FACTORY: cannot pack fixture")
source_root.free()
return {"scene": scene, "mesh": mesh}
func _expect_true(condition: bool, label: String, failures: Array[String]) -> void:
if not condition:
failures.append(label)
func _expect_false(condition: bool, label: String, failures: Array[String]) -> void:
_expect_true(not condition, label, failures)
func _expect_equal(actual: int, expected: int, label: String, failures: Array[String]) -> void:
if actual != expected:
failures.append("%s expected=%d actual=%d" % [label, expected, actual])
func _expect_string_equal(actual: String, expected: String, label: String, failures: Array[String]) -> void:
if actual != expected:
failures.append("%s expected=%s actual=%s" % [label, expected, actual])
func _expect_same(actual: Variant, expected: Variant, label: String, failures: Array[String]) -> void:
if not is_same(actual, expected):
failures.append(label)
@@ -0,0 +1 @@
uid://cae6gor0iqjv4
+1 -1
View File
@@ -69,6 +69,6 @@
- Remaining risks: direct build-12340 timing evidence for movement/camera, - Remaining risks: direct build-12340 timing evidence for movement/camera,
jump/fall/swim/fly, autorun, left-button camera/select behavior, active camera jump/fall/swim/fly, autorun, left-button camera/select behavior, active camera
collision, terrain slopes/holes/liquids, persisted keybindings, server authority, collision, terrain slopes/holes/liquids, persisted keybindings, server authority,
prediction and reconciliation remain for M09/M10/M12. Runtime renderer scenes prediction and reconciliation remain for M10/M11/M13. Runtime renderer scenes
continue to default to explicit `RenderSandbox` until an application shell continue to default to explicit `RenderSandbox` until an application shell
selects `Blizzlike335`. M02 makes no complete `1:1` compatibility claim. selects `Blizzlike335`. M02 makes no complete `1:1` compatibility claim.
+58 -7
View File
@@ -1,6 +1,7 @@
# M03 — Renderer Facade and Safe Extraction # M03 — Renderer Facade and Safe Extraction
<!-- OPENWC_TARGET:ACTIVE --> <!-- OPENWC_TARGET:DONE -->
<!-- OPENWC_TARGET_DONE:M03:3bf1c2c:2026-08-02 -->
## Outcome ## Outcome
@@ -12,9 +13,9 @@
- [x] Запретить gameplay/editor доступ к внутренним очередям streamer. - [x] Запретить gameplay/editor доступ к внутренним очередям streamer.
- [x] Извлечь pure `StreamingTargetPlanner`. - [x] Извлечь pure `StreamingTargetPlanner`.
- [x] Извлечь `RenderBudgetScheduler` с bounded queues/cancellation. - [x] Извлечь `RenderBudgetScheduler` с bounded queues/cancellation.
- [ ] Затем извлекать terrain, M2, WMO и liquid services по одному. - [x] Затем извлекать terrain, M2, WMO и liquid services по одному.
- [ ] Сохранить cache versioning и main-thread GPU finalization rules. - [x] Сохранить cache versioning и main-thread GPU finalization rules.
- [ ] Добавить dependency и performance regression checks. - [x] Добавить dependency и performance regression checks.
## Fidelity evidence ## Fidelity evidence
@@ -27,7 +28,7 @@ Runtime и Editor используют facade; planner/scheduler тестиру
## Evidence ## Evidence
- Date: 2026-08-01 - Date: 2026-08-01
- Revision/worktree: master merges `7ece2ab`, `7e35de7`, `80cb084`, `d6e5b53`, `c69abd6`, `606770c`, `630a0c1`, `f36fabb`, `24aef13`, `2342430`, `396be5e`, `f88bf97`, `3f84717`, `d2cb52f`, `f9e5a4f`, `c06aed5`, `a878e7c`, `60603e1`, `b4955d6`, `ada0fd9`, `f470775`, `874fd0f`, `38f831e`, `83b3618`, `3094561`, `fa85985`, `1fafdde`, `7cd07dd`, `1de50f9`, `3dccd3e`, `e7a7c67`, `c0fc191`, `c251985`, `5e82daa`, `1dc013e`, `99a90dd`, `4354834`, `a043c79`, `f79e064`, `d37c799`, `06f6394`, `34b7000`, `1acddab`, `6a0f9bd`, `d65ebee`, `705354d`, `57d0a9f`; - Revision/worktree: master merges `7ece2ab`, `7e35de7`, `80cb084`, `d6e5b53`, `c69abd6`, `606770c`, `630a0c1`, `f36fabb`, `24aef13`, `2342430`, `396be5e`, `f88bf97`, `3f84717`, `d2cb52f`, `f9e5a4f`, `c06aed5`, `a878e7c`, `60603e1`, `b4955d6`, `ada0fd9`, `f470775`, `874fd0f`, `38f831e`, `83b3618`, `3094561`, `fa85985`, `1fafdde`, `7cd07dd`, `1de50f9`, `3dccd3e`, `e7a7c67`, `c0fc191`, `c251985`, `5e82daa`, `1dc013e`, `99a90dd`, `4354834`, `a043c79`, `f79e064`, `d37c799`, `06f6394`, `34b7000`, `1acddab`, `6a0f9bd`, `d65ebee`, `705354d`, `57d0a9f`, `541279e`;
packages `M03-RND-SCHEDULER-001`, `M03-RND-INTERNAL-ACCESS-GATE-001`, packages `M03-RND-SCHEDULER-001`, `M03-RND-INTERNAL-ACCESS-GATE-001`,
`M03-RND-FACADE-GROUND-QUERY-001`, `M03-RND-FACADE-ENVIRONMENT-001`, `M03-RND-FACADE-GROUND-QUERY-001`, `M03-RND-FACADE-ENVIRONMENT-001`,
`M03-RND-FACADE-ENTITY-001`, `M03-RND-TERRAIN-CACHE-SERVICE-001`, `M03-RND-FACADE-ENTITY-001`, `M03-RND-TERRAIN-CACHE-SERVICE-001`,
@@ -47,6 +48,7 @@ Runtime и Editor используют facade; planner/scheduler тестиру
`M03-RND-WMO-RUNTIME-MESH-FINALIZER-001`, `M03-RND-WMO-RUNTIME-MESH-FINALIZER-001`,
`M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001`, `M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001`,
`M03-RND-WMO-RUNTIME-SCENE-PREPARER-001`, `M03-RND-WMO-RUNTIME-SCENE-PREPARER-001`,
`M03-RND-WMO-SCENE-INSTANCE-FACTORY-001`,
`M03-RND-ADT-WATER-LOAD-PIPELINE-001`, `M03-RND-ADT-WATER-LOAD-PIPELINE-001`,
`M03-RND-ADT-WATER-SCENE-FINALIZER-001`, `M03-RND-ADT-WATER-SCENE-FINALIZER-001`,
`M03-RND-M2-RUNTIME-REBUILD-CLASSIFIER-001`, `M03-RND-M2-RUNTIME-REBUILD-CLASSIFIER-001`,
@@ -389,6 +391,19 @@ Runtime и Editor используют facade; planner/scheduler тестиру
passed post-merge preparer (`3.486ms`), Mesh finalizer (`0.199ms`), all nine passed post-merge preparer (`3.486ms`), Mesh finalizer (`0.199ms`), all nine
adjacent WMO services, shutdown, materials, facade, internal-access `30`, adjacent WMO services, shutdown, materials, facade, internal-access `30`,
manifest `7/7`, documentation `51` and coordination. manifest `7/7`, documentation `51` and coordination.
WMO scene instance factory passed `cases=41 iterations=1000
elapsed_ms=5.297`; the updated placement resolver dependency passed
`cases=10 iterations=20000 elapsed_ms=28.740`. Cached validation-before-
placement, live validator suppression, basename, exact Transform3D and
descendant Mesh identity remained unchanged. Invalid non-Node3D cached roots
are now freed synchronously, closing an error-path lifetime leak. The suite
passed `65/66` with no unexpected failures and only the proprietary ADT probe
unavailable; editor parse had zero script diagnostics, documentation covered
`52` module specifications and checkpoint dry-run retained `7/7`. Merge
`541279e` passed post-merge factory (`5.308ms`), placement resolver
(`31.207ms`), runtime preparation/finalization, all adjacent WMO services,
shutdown, materials, facade, internal-access `30`, manifest `7/7`,
documentation `52` and coordination.
M02 terrain-query regression M02 terrain-query regression
remained green (13 pre-existing expired M00 claim warnings). remained green (13 pre-existing expired M00 claim warnings).
- Fidelity comparison: all 16 historical operation limits and drain sites were - Fidelity comparison: all 16 historical operation limits and drain sites were
@@ -549,6 +564,7 @@ Runtime и Editor используют facade; planner/scheduler тестиру
WMO runtime Mesh finalizer and four loader Mesh-delegation adapters, WMO runtime Mesh finalizer and four loader Mesh-delegation adapters,
stateless WMO render group materializer and both loader node-attachment adapters, stateless WMO render group materializer and both loader node-attachment adapters,
stateless WMO runtime scene preparer and cached/live loader preparation adapters, stateless WMO runtime scene preparer and cached/live loader preparation adapters,
stateless WMO scene instance factory and cached/live loader creation adapters,
ADT water pending/task/result pipeline state and loader enqueue/start/publish/ ADT water pending/task/result pipeline state and loader enqueue/start/publish/
drain/release/reset/shutdown/metrics adapters, drain/release/reset/shutdown/metrics adapters,
stateless ADT water scene finalizer and both loader build/attach adapters, stateless ADT water scene finalizer and both loader build/attach adapters,
@@ -611,11 +627,46 @@ Runtime и Editor используют facade; planner/scheduler тестиру
cached-scene WMO FileAccess/size admission and live fallback remain in the cached-scene WMO FileAccess/size admission and live fallback remain in the
loader; both WMO terminal I/O paths, runtime Mesh material refresh and loader; both WMO terminal I/O paths, runtime Mesh material refresh and
lightweight group materialization plus cached/live subtree preparation are lightweight group materialization plus cached/live subtree preparation are
separated; recursive Mesh/shadow mutation and Node creation remain synchronous separated; cached/live root creation and placement are also separated, while
main-thread work behind existing orchestration/permits; ResourceLoader/prototype construction remain loader-owned. Recursive Mesh/
shadow mutation and Node creation remain synchronous main-thread work behind
existing orchestration/permits;
asset-backed WMO placement/portal/material/leak/p95/p99 evidence remains pending; asset-backed WMO placement/portal/material/leak/p95/p99 evidence remains pending;
ADT water parsing and active worker interruption remain loader-owned; ADT water parsing and active worker interruption remain loader-owned;
materialization is separated but remains synchronous main-thread work behind materialization is separated but remains synchronous main-thread work behind
the existing permit, and asset-backed MH2O/MCLQ traversal/leak/p95/p99/ the existing permit, and asset-backed MH2O/MCLQ traversal/leak/p95/p99/
original-client evidence is pending; M03 still needs further terrain/M2/WMO/ original-client evidence is pending; M03 still needs further terrain/M2/WMO/
liquid service extraction and dependency/performance acceptance. liquid service extraction and dependency/performance acceptance.
### Closeout evidence — 2026-08-02
- Exact accepted M00 commit `239cb24` and M03 were captured on the same RTX 5070,
Godot 4.6.1, `High` preset and exact cache inventory: terrain `687`, WMO
render/scene `368/368`, M2 baked/GLB/scene `2009/2009/2009`.
- The repeated-sample protocol (five M00 reports and three M03 reports) and the
independent ten-second measurement protocol compared all `14` cold/warm
result pairs and `84` metrics with the unchanged 10% budgets. The stability
gate passed with `repeatable_regressions=0`; five protocol-local exceedances
did not reproduce across both sampling windows.
- Native GryphonRoost preparation now allocates an empty instance-local
`ArrayMesh`, shares captured Materials and performs one phased deformation
rebuild. The diagnosed initial build hitch fell from `168.02ms` to no native
copy hitch; dense-M2 maximum hitches fell from M00 `171–195ms` to M03
`52–66ms` in the paired short-window series.
- All `67/67` autonomous Godot verifier scripts passed. Renderer closeout
contracts passed `workers=4 frame_steps=15 cache_versions=7 nested_glb=1`;
documentation passed `module_specs=53 required_files=7`; coordination and
`git diff --check` passed.
- Asset-backed PNG/checkpoint evidence covers terrain overview, ADT boundary,
dense M2, large WMO, liquid, native animation and dusk sky. This proves the
M03 extraction did not introduce a repeatable performance regression; it does
not claim pixel-level parity with the original WoW 3.3.5a client.
- Superseding the historical pending statements above: the extraction,
dependency, cache-version, main-thread finalization and performance criteria
required by M03 are accepted. Remaining items are release-level risks:
original-client visual approval, long-traversal descriptor pressure, GPU
skinning for many native animators and interruption of already in-flight work.
- Closeout files: `src/tools/verify_renderer_closeout_contracts.gd`,
`tools/compare_render_performance.ps1`,
`tools/verify_render_performance_stability.ps1`, renderer closeout/module
documentation, nested M2 GLB bake fix and native animator regression coverage.
+113
View File
@@ -0,0 +1,113 @@
# M04 — Renderer Fidelity and Graphics Foundation
<!-- OPENWC_TARGET:ACTIVE -->
## Outcome
Довести графическую основу OpenWC до evidence-backed визуальной совместимости с
оригинальным клиентом WoW 3.3.5a build 12340 и создать общий graphics contract
для дальнейшего клиента, Editor preview и отдельного racing-профиля.
Noggit используется как дополнительный ориентир композиции и authoring preview,
но не заменяет оригинальный клиент как fidelity oracle.
Benilla используется как pinned secondary reference для M2 animation/material
architecture, particles/ribbons, WMO portal visibility и temporal test ideas. Его
Vanilla 1.12.1/Bevy behavior не заменяет build-12340 captures и не подтверждает
WotLK parity без отдельного fixture comparison.
## Reference corpus
- [ ] Принять versioned CSV manifest и валидатор оригинальных captures.
- [ ] Импортировать минимум две позиции каждой выбранной локации: `wide` для
неба/тумана/дальности/композиции и `ground` для terrain/material/M2/WMO.
- [ ] Сохранять map/zone/subzone, WoW/server coordinates, orientation, доступные
camera parameters, time, weather, graphics profile, viewport и имя артефакта.
- [ ] Не угадывать отсутствующие camera значения: хранить `Unknown`, landmark и
ограничение сравнения.
- [ ] Хранить proprietary screenshots/video вне Git; в репозитории сохранять
manifest, SHA-256, provenance, metrics, classifications и human approval.
- [ ] Добавить выбранную матрицу dawn/day/dusk/night, weather, shadow и liquid
quality без полного комбинаторного умножения всех локаций.
- [ ] Добавить temporal clips/кадры для water, animation, billboards, particles,
ribbons, weather и spell effect lifecycle.
## Graphics foundation
- [ ] Ввести явный graphics profile contract: `Blizzlike335` и opt-in
`Enhanced/Racing`; улучшения не меняют Blizzlike-путь и не маскируют gaps.
- [ ] Зафиксировать color pipeline: source color space, sampling, gamma,
tonemapping, fog, alpha coverage и deterministic capture output.
- [ ] Заменять approximate constants только после paired original-client
evidence; каждое осознанное расхождение регистрировать по подсистеме.
- [ ] Сохранить `WorldRenderFacade`, streaming budgets, cache versioning и
main-thread GPU finalization boundaries M03.
- [ ] Не начинать полный custom `RenderingDevice` renderer без bounded spike,
profiler evidence и ADR; сначала использовать точные Godot shader passes.
## Required implementation order
- [ ] Environment: точная DBC interpolation, sky/skybox, ambient/directional
light, zone transitions, fog, day/night и weather presentation.
- [ ] Terrain: MCNK topology/normals/holes, MCAL variants/layer animation,
filtering/mips, MCSH/baked shadow data, seams и WDL distant terrain.
- [ ] M2/WMO materials: build-12340 blend/depth/cull flags, alpha test,
multi-texture combiners, vertex colors, emissive/unlit, UV animation,
billboards, WMO indoor/outdoor, portals, local lights и fog semantics.
- [ ] Shadows: воспроизвести оригинальную policy для выбранных graphics settings;
terrain/baked, character/object и dynamic shadows проверять раздельно.
- [ ] Liquids: MH2O/MCLQ/MLIQ geometry, masks/heights/depth, original animated
textures и water/ocean/magma/slime/underwater behavior. Enhanced reflections,
normals и shore blending остаются opt-in.
- [ ] Animation/characters: GPU skinning path, sequences/blending/global
sequences, attachments, geosets, customization, equipment, mounts и vehicles;
CPU deformation оставить только измеренным fallback/diagnostic path.
- [ ] Effects: M2 particles/ribbons, texture animation, weather emitters,
SpellVisual/SpellVisualKit cast/projectile/impact/aura presentation, pooling,
deterministic lifetime и visibility budgets.
- [ ] Racing readiness: renderer/world/character/effects APIs не зависят от WoW
gameplay/network; racing profile меняет только явные graphics capabilities.
## Verification
- Original-client/OpenWC paired captures используют одинаковые viewport, camera
contract, position, time, weather и graphics profile либо явно классифицируют
неизвестное поле.
- Static comparison включает full-frame и semantic regions; geometry/framing,
material, lighting, shadow, liquid, animation/effect gaps не смешиваются.
- Temporal comparison проверяет phase, duration, trajectory, UV motion и emitter
lifetime, а не только один случайный кадр.
- GPU test matrix покрывает Godot Forward+/D3D12/Vulkan, cold/warm load, rapid
traversal, teleport, weather/time transitions и profile switching.
- Обязательны p50/p95/p99, max hitch, draw calls, visible objects, CPU/GPU memory,
shader/cache stability и Node/Resource/RID leak checks.
## Exit criteria
- Reference corpus покрывает обязательные static и temporal классы и проходит
schema/provenance/hash validation.
- Все пункты renderer coverage map имеют evidence-backed статус; `Verified`
допускается только с original-client fixture и human approval.
- Для утверждённой build-12340 capture matrix не остаётся неклассифицированных
визуально различимых gaps; принятые engine/GPU отклонения имеют измеренный
tolerance, rationale и artifact evidence.
- `Blizzlike335` сохраняет оригинальную policy, `Enhanced/Racing` выключаем и не
изменяет default behavior.
- Long traversal не создаёт recurring hitches, cache churn или renderer leaks;
M03 performance budgets остаются зелёными либо изменены только отдельным
evidence-backed решением.
- Character, animation, liquid, shadow и effects contracts пригодны для runtime,
Editor preview и будущего racing fork без зависимости от network/gameplay.
## Evidence
- Date:
- Revision/worktree:
- Original-client build/settings/corpus:
- Commands:
- Static/temporal comparison results:
- Performance and lifetime results:
- Human approval:
- API/module docs and diagrams:
- Changed files:
- Remaining risks:
@@ -1,4 +1,4 @@
# M04 — Godot Editor Shell # M05 — Godot Editor Shell
<!-- OPENWC_TARGET:OPEN --> <!-- OPENWC_TARGET:OPEN -->
@@ -1,4 +1,4 @@
# M05 — Content Project # M06 — Content Project
<!-- OPENWC_TARGET:OPEN --> <!-- OPENWC_TARGET:OPEN -->
@@ -1,4 +1,4 @@
# M06 — TrinityCore/AzerothCore Server Adapters # M07 — TrinityCore/AzerothCore Server Adapters
<!-- OPENWC_TARGET:OPEN --> <!-- OPENWC_TARGET:OPEN -->
@@ -1,4 +1,4 @@
# M07 — World Editor Vertical Slice # M08 — World Editor Vertical Slice
<!-- OPENWC_TARGET:OPEN --> <!-- OPENWC_TARGET:OPEN -->
@@ -1,4 +1,4 @@
# M08 — WoW 3.3.5a Network Foundation # M09 — WoW 3.3.5a Network Foundation
<!-- OPENWC_TARGET:OPEN --> <!-- OPENWC_TARGET:OPEN -->
@@ -1,4 +1,4 @@
# M09 — Gameplay Domain # M10 — Gameplay Domain
<!-- OPENWC_TARGET:OPEN --> <!-- OPENWC_TARGET:OPEN -->
@@ -1,4 +1,4 @@
# M10 — First Playable Client Slice # M11 — First Playable Client Slice
<!-- OPENWC_TARGET:OPEN --> <!-- OPENWC_TARGET:OPEN -->
@@ -1,4 +1,4 @@
# M11 — Quest End-to-End # M12 — Quest End-to-End
<!-- OPENWC_TARGET:OPEN --> <!-- OPENWC_TARGET:OPEN -->
@@ -1,4 +1,4 @@
# M12 — WoW 3.3.5a Client Completeness # M13 — WoW 3.3.5a Client Completeness
<!-- OPENWC_TARGET:OPEN --> <!-- OPENWC_TARGET:OPEN -->
@@ -1,4 +1,4 @@
# M13 — Dungeon and Encounter Authoring # M14 — Dungeon and Encounter Authoring
<!-- OPENWC_TARGET:OPEN --> <!-- OPENWC_TARGET:OPEN -->
+25 -18
View File
@@ -8,12 +8,15 @@ Godot-specific implementation rules обязательны из [`../docs/GODOT_
## Итоговый продукт ## Итоговый продукт
OpenWC должен предоставить четыре согласованных режима: OpenWC должен предоставить пять согласованных режимов/потребителей:
1. `Blizzlike335` — клиент WoW 3.3.5a build 12340 с максимально точным наблюдаемым поведением. 1. `Blizzlike335` — клиент WoW 3.3.5a build 12340 с максимально точным наблюдаемым поведением.
2. `Enhanced` — opt-in графические, UX и accessibility улучшения, не меняющие wire protocol и server authority без отдельного profile. 2. `Enhanced` — opt-in графические, UX и accessibility улучшения, не меняющие wire protocol и server authority без отдельного profile.
3. `AuthoringStudio` — инструменты мира, контента, БД и playtest внутри Godot Editor. 3. `AuthoringStudio` — инструменты мира, контента, БД и playtest внутри Godot Editor.
4. `HeadlessTools` — import, validation, bake, packet replay, content build и CI без GUI. 4. `HeadlessTools` — import, validation, bake, packet replay, content build и CI без GUI.
5. `Racing` — downstream fork/profile, использующий общие world, character,
animation, effects и graphics contracts без зависимости от WoW gameplay и
network lifecycle.
Один кодовый путь должен обслуживать runtime и editor preview через публичные contracts. Generated preview nodes, DB records и caches не являются источником истины. Один кодовый путь должен обслуживать runtime и editor preview через публичные contracts. Generated preview nodes, DB records и caches не являются источником истины.
@@ -21,7 +24,7 @@ OpenWC должен предоставить четыре согласованн
- MPQ/BLP/ADT/WDT/M2/WMO native parsing и renderer vertical slice уже существуют. - MPQ/BLP/ADT/WDT/M2/WMO native parsing и renderer vertical slice уже существуют.
- Runtime streaming, terrain quality, MultiMesh M2, WMO caches, liquids, sky и character experiments находятся в рабочем состоянии, но сосредоточены в крупных orchestration scripts. - Runtime streaming, terrain quality, MultiMesh M2, WMO caches, liquids, sky и character experiments находятся в рабочем состоянии, но сосредоточены в крупных orchestration scripts.
- Renderer baseline M00 завершён: paired comparison с оригинальным клиентом записывает измеренные gaps без заявления parity, а обязательные diagnostic gaps закрыты или явно классифицированы. M01 завершил coordinate, identity и streaming-focus seams; M02 декомпозировал player input, movement, terrain query, camera и presentation без observable regression. Текущая работа M03 закрывает renderer стабильным facade и извлекает planner/scheduler без rewrite. - Renderer baseline M00 завершён: paired comparison с оригинальным клиентом записывает измеренные gaps без заявления parity, а обязательные diagnostic gaps закрыты или явно классифицированы. M01 завершил coordinate, identity и streaming-focus seams; M02 декомпозировал player input, movement, terrain query, camera и presentation без observable regression. M03 закрыл renderer стабильным facade, planner/scheduler и извлечёнными services. Текущий M04 доводит графическую основу до evidence-backed build-12340 fidelity до начала Editor/network milestones.
- Gameplay domain, network protocol, production UI/Lua, audio orchestration и server adapters в основном предстоит реализовать. - Gameplay domain, network protocol, production UI/Lua, audio orchestration и server adapters в основном предстоит реализовать.
- Editor plugin пока решает extraction/preview задачи, но не является полноценной authoring platform. - Editor plugin пока решает extraction/preview задачи, но не является полноценной authoring platform.
@@ -45,14 +48,15 @@ M00 renderer baseline
└─ M01 coordinates/contracts └─ M01 coordinates/contracts
└─ M02 input/movement/camera/presentation split └─ M02 input/movement/camera/presentation split
└─ M03 renderer facade └─ M03 renderer facade
├─ M04 editor shell → M05 content → M06 server adapters → M07 world editor └─ M04 renderer fidelity/graphics foundation
└─ M08 network → M09 gameplay domain → M10 playable client ├─ M05 editor shell → M06 content → M07 server adapters → M08 world editor
M07 + M10 → M11 quest end-to-end └─ M09 network → M10 gameplay domain → M11 playable client
└─ M12 completeness waves M08 + M11 → M12 quest end-to-end
└─ M13 dungeon authoring └─ M13 completeness waves
└─ M14 dungeon authoring
``` ```
M12 не является одним большим этапом. Он выполняется последовательными completeness waves: M13 не является одним большим этапом. Он выполняется последовательными completeness waves:
1. `C1 Core loop` — combat, spells, auras, death, loot, inventory, quests. 1. `C1 Core loop` — combat, spells, auras, death, loot, inventory, quests.
2. `C2 Character` — creation/customization, equipment, animation, pets, mounts. 2. `C2 Character` — creation/customization, equipment, animation, pets, mounts.
@@ -73,16 +77,17 @@ M12 не является одним большим этапом. Он выпо
| M01 | FND typed coordinates/IDs/contracts | Server/ADT/Godot round-trip и StreamingFocus | | M01 | FND typed coordinates/IDs/contracts | Server/ADT/Godot round-trip и StreamingFocus |
| M02 | FND composition, GMP movement, RND presenters | Player split без visual/control regression | | M02 | FND composition, GMP movement, RND presenters | Player split без visual/control regression |
| M03 | RND facade/planner/scheduler, FND jobs | Renderer доступен через API и сохраняет budgets | | M03 | RND facade/planner/scheduler, FND jobs | Renderer доступен через API и сохраняет budgets |
| M04 | EDT plugin shell, QAR editor tests | Reload-safe workspace, commands, undo/recovery | | M04 | RND/QAR reference corpus, materials, environment, shadows, liquids, characters/effects | Original-client-paired graphics foundation и отделённый Enhanced/Racing profile |
| M05 | FND content schemas/cache, EDT project | Deterministic Content Project build/migration | | M05 | EDT plugin shell, QAR editor tests | Reload-safe workspace, commands, undo/recovery |
| M06 | SRV environments/snapshots/adapters | Two-core read/diff/dry-run round-trip | | M06 | FND content schemas/cache, EDT project | Deterministic Content Project build/migration |
| M07 | EDT world editing/navigation/deploy | NPC placement на dev-core без ручного SQL | | M07 | SRV environments/snapshots/adapters | Two-core read/diff/dry-run round-trip |
| M08 | NET transport/auth/protocol/entities | Headless auth → world и deterministic replay | | M08 | EDT world editing/navigation/deploy | NPC placement на dev-core без ручного SQL |
| M09 | GMP world state/movement/targeting | Packet replay → deterministic gameplay snapshot | | M09 | NET transport/auth/protocol/entities | Headless auth → world и deterministic replay |
| M10 | FND app lifecycle, RND/UIA presentation | Игрок входит, видит entities и перемещается | | M10 | GMP world state/movement/targeting | Packet replay → deterministic gameplay snapshot |
| M11 | GMP quests, EDT quest tools, SRV deploy, UIA quest UI | Author → deploy → complete quest своим клиентом | | M11 | FND app lifecycle, RND/UIA presentation | Игрок входит, видит entities и перемещается |
| M12 C1–C8 | NET/GMP/RND/UIA completeness + QAR | Feature matrix `Compatible/Verified` по waves | | M12 | GMP quests, EDT quest tools, SRV deploy, UIA quest UI | Author → deploy → complete quest своим клиентом |
| M13 | EDT dungeon/encounter, SRV modules, GMP instances | Custom dungeon group playtest end-to-end | | M13 C1–C8 | NET/GMP/RND/UIA completeness + QAR | Feature matrix `Compatible/Verified` по waves |
| M14 | EDT dungeon/encounter, SRV modules, GMP instances | Custom dungeon group playtest end-to-end |
## Recommended implementation rhythm ## Recommended implementation rhythm
@@ -101,6 +106,8 @@ M12 не является одним большим этапом. Он выпо
## Cross-cutting contracts ## Cross-cutting contracts
- `BuildProfile` — `Blizzlike335`, `Enhanced`, authoring и test capabilities. - `BuildProfile` — `Blizzlike335`, `Enhanced`, authoring и test capabilities.
- `GraphicsProfile` — explicit Blizzlike/Enhanced/Racing material, lighting,
shadow, liquid, effects, distance и post-processing capabilities.
- `CoordinateMapper` — единственное преобразование WoW/Godot/ADT/server coordinates. - `CoordinateMapper` — единственное преобразование WoW/Godot/ADT/server coordinates.
- `AssetRepository` — original assets, overlays, caches и provenance. - `AssetRepository` — original assets, overlays, caches и provenance.
- `ProtocolProfile` — build, opcodes, update fields, codecs и server capabilities. - `ProtocolProfile` — build, opcodes, update fields, codecs и server capabilities.
+23 -53
View File
@@ -4,23 +4,23 @@
Этот каталог задаёт обязательную последовательность реализации. Архитектура описана в `docs/`, фактическое состояние renderer — в `RENDER.md`, а здесь находятся исполняемые milestones. Этот каталог задаёт обязательную последовательность реализации. Архитектура описана в `docs/`, фактическое состояние renderer — в `RENDER.md`, а здесь находятся исполняемые milestones.
Полная инженерная декомпозиция всех подсистем находится в [`DEVELOPMENT_ROADMAP.md`](DEVELOPMENT_ROADMAP.md). Перед работой над milestone агент читает соответствующий subsystem plan из `targets/roadmap/`, но статус меняет только в исполняемом target-файле M00–M13. Полная инженерная декомпозиция всех подсистем находится в [`DEVELOPMENT_ROADMAP.md`](DEVELOPMENT_ROADMAP.md). Перед работой над milestone агент читает соответствующий subsystem plan из `targets/roadmap/`, но статус меняет только в исполняемом target-файле M00–M14.
Параллельная командная работа выполняется по [`../docs/TEAM_WORKFLOW.md`](../docs/TEAM_WORKFLOW.md): отдельные worktrees, claims, exclusive paths, contract-first и интегратор milestone. Параллельная командная работа выполняется по [`../docs/TEAM_WORKFLOW.md`](../docs/TEAM_WORKFLOW.md): отдельные worktrees, claims, exclusive paths, contract-first и интегратор milestone.
## Current target ## Текущая цель
`M03` — [03-renderer-facade.md](03-renderer-facade.md) `M04` — [04-renderer-fidelity.md](04-renderer-fidelity.md)
*Начало фазы выполнения:* `YYYY-MM-DD`
Одновременно `ACTIVE` может быть только одна цель. Следующая цель становится `ACTIVE` после появления валидной `OPENWC_TARGET_DONE` метки у предыдущей. Одновременно `АКТИВНАЯ` может быть только одна цель. Следующая цель становится `АКТИВНОЙ` после появления валидной метки `OPENWC_TARGET_DONE` у предыдущей.
Статусные метки применяются только к исполняемым файлам `targets/[0-9][0-9]-*.md`. `DEVELOPMENT_ROADMAP.md` и `targets/roadmap/` являются нормативной декомпозицией, но не создают параллельную систему статусов. Статусные метки применяются только к исполняемым файлам `targets/[0-9][0-9]-*.md`. `DEVELOPMENT_ROADMAP.md` и `targets/roadmap/` являются нормативной декомпозицией, но не создают параллельную систему статусов.
## Definition of fidelity 1:1 ## Определение фидели 1:1
`1:1` означает совпадение наблюдаемого поведения с оригинальным клиентом WoW 3.3.5a build 12340 для выбранного сценария: `1:1` означает совпадение наблюдаемого поведения с оригинальным клиентом WoW 3.3.5a build 12340 для выбранного сценария:
- wire protocol и update fields; - жилл протокол и обновляемые поля;
- игровые состояния и переходы; - игровые состояния и переходы;
- координаты, timing и server authority; - координаты, timing и server authority;
- UI/Lua/FrameXML semantics; - UI/Lua/FrameXML semantics;
@@ -36,53 +36,23 @@
| M00 | [Renderer baseline](00-render-baseline.md) | — | DONE | | M00 | [Renderer baseline](00-render-baseline.md) | — | DONE |
| M01 | [Coordinates and architecture seams](01-coordinates-and-seams.md) | M00 | DONE | | M01 | [Coordinates and architecture seams](01-coordinates-and-seams.md) | M00 | DONE |
| M02 | [Player decomposition](02-player-decomposition.md) | M01 | DONE | | M02 | [Player decomposition](02-player-decomposition.md) | M01 | DONE |
| M03 | [Renderer facade and extraction](03-renderer-facade.md) | M02 | ACTIVE | | M03 | [Renderer facade and extraction](03-renderer-facade.md) | M02 | DONE |
| M04 | [Godot Editor shell](04-editor-shell.md) | M03 | OPEN | | M04 | [Renderer fidelity and graphics foundation](04-renderer-fidelity.md) | M03 | ACTIVE |
| M05 | [Content Project](05-content-project.md) | M04 | OPEN | | M05 | [Godot Editor shell](05-editor-shell.md) | M04 | OPEN |
| M06 | [Server database adapters](06-server-adapters.md) | M05 | OPEN | | M06 | [Content Project](06-content-project.md) | M05 | OPEN |
| M07 | [World Editor vertical slice](07-world-editor-slice.md) | M06 | OPEN | | M07 | [Server database adapters](07-server-adapters.md) | M06 | OPEN |
| M08 | [Network foundation](08-network-foundation.md) | M03 | OPEN | | M08 | [World Editor vertical slice](08-world-editor-slice.md) | M07 | OPEN |
| M09 | [Gameplay domain](09-gameplay-domain.md) | M08 | OPEN | | M09 | [Network foundation](09-network-foundation.md) | M04 | OPEN |
| M10 | [Playable client slice](10-playable-client.md) | M09, M03 | OPEN | | M10 | [Gameplay domain](10-gameplay-domain.md) | M09 | OPEN |
| M11 | [Quest end-to-end](11-quest-end-to-end.md) | M07, M10 | OPEN | | M11 | [Playable client slice](11-playable-client.md) | M10, M04 | OPEN |
| M12 | [WoW 3.3.5a completeness](12-client-completeness.md) | M11 | OPEN | | M12 | [Quest end-to-end](12-quest-end-to-end.md) | M08, M11 | OPEN |
| M13 | [Dungeon authoring](13-dungeon-authoring.md) | M12 foundation | OPEN | | M13 | [WoW 3.3.5a completeness](13-client-completeness.md) | M12 | OPEN |
| M14 | [Dungeon authoring](14-dungeon-authoring.md) | M13 foundation | OPEN |
M12 выполняется восемью completeness waves, определёнными в [`DEVELOPMENT_ROADMAP.md`](DEVELOPMENT_ROADMAP.md), а не одной массовой реализацией. M13 выполняется восемью completeness waves, определёнными в [`DEVELOPMENT_ROADMAP.md`](DEVELOPMENT_ROADMAP.md), а не одной массовой реализацией.
M07 и M08 могут выполняться параллельными командами только после M06/M03 соответственно и только если пользователь явно разрешил параллельную работу. Их результаты сходятся в M11. M08 и M09 могут выполняться параллельными командами только после M07/M04 соответственно и только если пользователь явно разрешил параллельную работу. Их результаты сходятся в M12.
`recast-rs` запланирован как optional navigation backend в M05, исследовательский CLI spike и world validation в M07, затем полноценная dungeon navigation validation в M13. Он не блокирует M00–M04 и не считается автоматически совместимым с server `mmaps`. `recast-rs` запланирован как optional navigation backend в M06, исследовательский CLI spike и world validation в M08, затем полноценная dungeon navigation validation в M14. Он не блокирует M00–M05 и не считается автоматически совместимым с server `mmaps`.
`rilua` зарегистрирован как Lua 5.1.1 runtime/test-oracle candidate для M12. До compatibility spike он не добавляется как dependency; UI строится через заменяемый `LuaRuntime`, поскольку WoW API, FrameXML, secure actions и taint не входят в готовый VM. `rilua` зарегистрирован как Lua 5.1.1 runtime/test-oracle
Wowser зарегистрирован как независимый MIT reference для M08 и M12: auth → world protocol lifecycle, binary/data handling, asset pipeline и общий client coverage. Его старый browser stack не является кандидатом на интеграцию; полезные решения принимаются только после проверки против build 12340 и server cores.
## Общий quality gate
Каждая цель должна сохранить:
- headless загрузку Godot project;
- затронутые unit/contract/integration tests;
- 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
В конце target-файла агент заполняет:
```text
## Evidence
- Date:
- Revision/worktree:
- Commands:
- Results:
- Fidelity comparison:
- Changed files:
- Remaining risks:
```
+1 -1
View File
@@ -10,7 +10,7 @@
- Отделить user settings, developer settings, server connection profiles и content project settings. - Отделить user settings, developer settings, server connection profiles и content project settings.
- Secrets хранить вне Git/Resource; поддержать environment/OS credential provider. - Secrets хранить вне Git/Resource; поддержать environment/OS credential provider.
- Настройки иметь schema version, defaults, validation, migration и reset-by-section. - Настройки иметь schema version, defaults, validation, migration и reset-by-section.
- `Blizzlike335` сделать неизменяемым базовым профилем; enhanced settings наследуют его явно. - `Blizzlike335` сделать неизменяемым базовым профилем; Enhanced/Racing settings наследуют его явно и не меняют default behavior.
## FND-2. Application lifecycle ## FND-2. Application lifecycle
+59 -1
View File
@@ -4,6 +4,59 @@
Воспроизвести визуальные правила WoW 3.3.5a и дать opt-in улучшения, сохранив frame pacing на больших мирах Godot. Воспроизвести визуальные правила WoW 3.3.5a и дать opt-in улучшения, сохранив frame pacing на больших мирах Godot.
## M04 execution policy
M04 выполняет renderer fidelity до Editor, network и gameplay milestones. M03
facade, ownership, streaming budgets и cache rules сохраняются: fidelity работа
расширяет services и shaders, но не возвращает прямой cross-layer доступ к
streamer internals.
Authoritative oracle — оригинальный клиент build 12340. Noggit полезен для
обзорной композиции, placements и будущего Editor viewport, но не подтверждает
точные lighting, shadow, liquid, material или effect semantics.
Pinned Benilla source полезен как secondary implementation reference для
pose/palette animation, global sequences, material/pass ordering, shared dynamic
effect buffers, M2 particles/ribbons и WMO portal traversal. Поскольку Benilla
ориентирован на Vanilla build 5875 и Bevy, каждое заимствованное правило сначала
проверяется на build-12340 fixture и адаптируется к существующим Godot/M03
ownership, scheduling и finalization boundaries.
WoWee revision `607ea3b8369851014721416293f8e95dfbe64eec` добавляет полезные WotLK-oriented
test patterns для M2 global sequences/color-alpha tracks, `$FSD` event timing,
liquid masks, rotated WMO water projection и CPU/shader vertex contracts. Они
становятся M04 fixture candidates, но не oracle: Hermite/Bezier sampling и effects
частично аппроксимированы, tilted placement order не решён, а часть asset tests
silent-skips missing data. OpenWC fixtures обязаны фиксировать build 12340,
provenance/hash и фактическое исполнение. Из-за дополнительного license restriction
WoWee используется только как research reference без копирования/вендоринга кода.
Reference corpus принимает versioned CSV и приватные captures. Для каждой
выбранной локации базовый набор содержит две позиции: `wide` для sky/fog/WDL и
общей композиции, `ground` для terrain/material/M2/WMO. Специализированные
water, interior, shadow, character и effect viewpoints добавляются только когда
две базовые позиции не покрывают уникальную визуальную policy.
Repository хранит schema, coordinates/camera/settings metadata, SHA-256,
comparison metrics, gap classification и human approval. Proprietary image/video
bytes остаются вне Git. Неизвестные camera/time/weather/settings поля отмечаются
как `Unknown`; их запрещено угадывать или автоматически считать parity evidence.
Обязательный порядок реализации:
1. reference manifest, validator, paired capture и semantic region diff;
2. deterministic color/gamma/sampling/alpha/fog pipeline;
3. environment/sky/weather и terrain/WDL/MCSH;
4. M2/WMO material passes и indoor/outdoor lighting;
5. graphics-setting-aware shadows и liquids;
6. GPU animation, characters, attachments, equipment и mounts;
7. particles, ribbons, weather и spell visual lifecycle;
8. Enhanced/Racing profile separation, traversal/performance/lifetime closeout.
Полный custom `RenderingDevice` backend допускается только после bounded spike,
доказавшего, что точный critical pass нельзя реализовать Godot spatial/sky/
particle/compositor механизмами в установленном performance budget.
## RND-1. Renderer architecture ## RND-1. Renderer architecture
- `WorldRenderFacade` принимает focus, environment snapshot и entity presentation commands. - `WorldRenderFacade` принимает focus, environment snapshot и entity presentation commands.
@@ -99,4 +152,9 @@
## Gate ## Gate
Видимый мир не деградирует при декомпозиции; under-foot/near-field quality готова до появления в кадре; long traversal не создаёт recurring hitches/leaks; Enhanced всегда отключаем. Для утверждённой original-client capture matrix отсутствуют неклассифицированные
визуально различимые gaps; каждый `Verified` class имеет fixture, metrics и human
approval. Under-foot/near-field quality готова до появления в кадре; temporal
effects совпадают по phase/duration/trajectory; long traversal не создаёт
recurring hitches/leaks; Enhanced/Racing всегда отключаем и не меняет
`Blizzlike335` default behavior.
+13 -1
View File
@@ -90,7 +90,19 @@
- Original 3.3.5a FrameXML/Lua extracted from legally owned data is primary behavior corpus. - 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. - `rilua`/PUC-Rio are VM candidates/oracles, not complete WoW UI.
- WowBench/Wowser/WowUnreal/WoWee provide API/event/loader test ideas. - WoWee revision `607ea3b8369851014721416293f8e95dfbe64eec` provides a large
headless FrameXML/Lua corpus, retained widget-tree/layout tests, a unified
XML-to-Lua `CreateFrame` path, progressive default-UI takeover and static API/
event/handler audits. OpenWC reuses the test taxonomy and staged-handover model,
not its monolithic Lua/game binding, permissive unknown-API fallback or absent
secure/taint semantics; its restricted license also keeps it reference-only.
- WowBench/Wowser/WowUnreal provide additional API/event/loader test ideas.
- Pinned [Benilla](https://github.com/samwhosung/benilla) provides a strong
engine-free TOC/FrameXML/Lua decomposition and compatibility-test reference:
document/load order, templates, anchors, widget identity, handler globals,
deterministic event/`OnUpdate` order, sandbox and SavedVariables. Its Vanilla
Lua 5.0 compatibility layer and missing secure/taint coverage are not WotLK
semantics and require a build-12340 corpus.
## Gate ## Gate
@@ -66,7 +66,7 @@
## EDT-8. Graphics extension tools ## EDT-8. Graphics extension tools
- Material/shader profile editor с Blizzlike/Enhanced comparison. - Material/shader profile editor с Blizzlike/Enhanced/Racing comparison.
- Texture replacement/upscale overlays и mip/alpha/color-space validation. - Texture replacement/upscale overlays и mip/alpha/color-space validation.
- Water/sky/weather/light preview with fixed time and reference capture. - Water/sky/weather/light preview with fixed time and reference capture.
- Character/equipment/animation/attachment preview. - Character/equipment/animation/attachment preview.
+335
View File
@@ -0,0 +1,335 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string[]]$BaselineReport,
[Parameter(Mandatory = $true)]
[string[]]$CandidateReport,
[string]$OutputReport
)
$ErrorActionPreference = 'Stop'
$failures = [System.Collections.Generic.List[string]]::new()
$comparisons = [System.Collections.Generic.List[object]]::new()
function Read-RenderReport {
param(
[Parameter(Mandatory = $true)]
[string]$Path,
[Parameter(Mandatory = $true)]
[string]$Label
)
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
throw "$Label report does not exist: $Path"
}
$report = Get-Content -Raw -Encoding UTF8 -LiteralPath $Path | ConvertFrom-Json
if ($null -eq $report -or $null -eq $report.results) {
throw "$Label report has no results array: $Path"
}
return $report
}
function Convert-ToStableJson {
param([object]$Value)
return $Value | ConvertTo-Json -Depth 20 -Compress
}
function Get-MedianValue {
param([double[]]$Values)
$ordered = @($Values | Sort-Object)
if ($ordered.Count -eq 0) {
throw 'Cannot calculate a median from an empty value set'
}
$middle = [math]::Floor($ordered.Count / 2)
if ($ordered.Count % 2 -eq 1) {
return [double]$ordered[$middle]
}
return ([double]$ordered[$middle - 1] + [double]$ordered[$middle]) / 2.0
}
function Assert-EqualValue {
param(
[string]$Name,
[object]$BaselineValue,
[object]$CandidateValue
)
$baselineJson = Convert-ToStableJson $BaselineValue
$candidateJson = Convert-ToStableJson $CandidateValue
if ($baselineJson -cne $candidateJson) {
$failures.Add("$Name differs: baseline=$baselineJson candidate=$candidateJson")
}
}
function Get-ResultKey {
param([object]$Result)
return "$($Result.name)|$($Result.pass)"
}
function Assert-CompatibleCacheInventory {
param(
[object]$BaselineInventory,
[object]$CandidateInventory
)
$baselineNames = @($BaselineInventory.PSObject.Properties.Name | Sort-Object)
$candidateNames = @($CandidateInventory.PSObject.Properties.Name | Sort-Object)
Assert-EqualValue 'cache inventory key set' $baselineNames $candidateNames
foreach ($cacheName in $baselineNames) {
$baselineRecord = $BaselineInventory.$cacheName
$candidateRecord = $CandidateInventory.$cacheName
if ($null -eq $candidateRecord) {
continue
}
Assert-EqualValue "cache inventory presence for $cacheName" `
$baselineRecord.present `
$candidateRecord.present
Assert-EqualValue "cache inventory file count for $cacheName" `
$baselineRecord.file_count `
$candidateRecord.file_count
if ([bool]$baselineRecord.present -and [int64]$candidateRecord.file_count -le 0) {
$failures.Add("cache inventory is empty for $cacheName")
}
}
}
function Build-ResultIndex {
param(
[object[]]$Results,
[string]$Label
)
$index = @{}
foreach ($result in $Results) {
$key = Get-ResultKey $result
if ($index.ContainsKey($key)) {
$failures.Add("$Label report contains duplicate result: $key")
continue
}
$index[$key] = $result
}
return $index
}
function Merge-RenderReports {
param(
[object[]]$Reports,
[string]$Label
)
$merged = Convert-ToStableJson $Reports[0] | ConvertFrom-Json
if ($Reports.Count -eq 1) {
return $merged
}
$resultIndexes = @(
foreach ($sample in $Reports) {
Build-ResultIndex -Results @($sample.results) -Label "$Label sample"
}
)
$referenceResultKeys = @($resultIndexes[0].Keys | Sort-Object)
for ($sampleNumber = 1; $sampleNumber -lt $Reports.Count; $sampleNumber++) {
$sample = $Reports[$sampleNumber]
Assert-EqualValue "$Label[$sampleNumber] schema_version" $merged.schema_version $sample.schema_version
Assert-EqualValue "$Label[$sampleNumber] profile" $merged.profile $sample.profile
Assert-EqualValue "$Label[$sampleNumber] cache_state" $merged.cache_state $sample.cache_state
Assert-EqualValue "$Label[$sampleNumber] viewport" $merged.environment.viewport $sample.environment.viewport
Assert-EqualValue "$Label[$sampleNumber] Godot version" $merged.environment.godot_version.string $sample.environment.godot_version.string
Assert-EqualValue "$Label[$sampleNumber] rendering driver" $merged.environment.rendering_driver $sample.environment.rendering_driver
Assert-EqualValue "$Label[$sampleNumber] rendering method" $merged.environment.rendering_method $sample.environment.rendering_method
Assert-EqualValue "$Label[$sampleNumber] video adapter" $merged.environment.video_adapter $sample.environment.video_adapter
Assert-EqualValue "$Label[$sampleNumber] CPU" $merged.environment.cpu $sample.environment.cpu
Assert-EqualValue "$Label[$sampleNumber] cache contract" $merged.cache_contract $sample.cache_contract
Assert-EqualValue "$Label[$sampleNumber] cache inventory" $merged.cache_inventory $sample.cache_inventory
Assert-EqualValue `
"$Label[$sampleNumber] result key set" `
$referenceResultKeys `
@($resultIndexes[$sampleNumber].Keys | Sort-Object)
}
$metricDefinitions = @(
@('load_time_ms', 'root'),
@('frame_ms_p95', 'metrics'),
@('frame_ms_p99', 'metrics'),
@('max_hitch_ms', 'metrics'),
@('memory_static_bytes', 'metrics'),
@('video_memory_bytes', 'metrics')
)
$mergedResultIndex = Build-ResultIndex -Results @($merged.results) -Label "Median $Label"
foreach ($resultKey in $referenceResultKeys) {
$mergedResult = $mergedResultIndex[$resultKey]
foreach ($definition in $metricDefinitions) {
$metricName = $definition[0]
$location = $definition[1]
$values = @(
foreach ($sampleIndex in $resultIndexes) {
if ($location -eq 'root') {
[double]$sampleIndex[$resultKey].$metricName
} else {
[double]$sampleIndex[$resultKey].metrics.$metricName
}
}
)
$medianValue = Get-MedianValue -Values $values
if ($location -eq 'root') {
$mergedResult.$metricName = $medianValue
} else {
$mergedResult.metrics.$metricName = $medianValue
}
}
}
return $merged
}
function Compare-Metric {
param(
[string]$ResultKey,
[string]$MetricName,
[double]$BaselineValue,
[double]$CandidateValue,
[double]$AllowedRegressionPercent
)
$limit = if ($BaselineValue -eq 0.0) {
0.0
} else {
$BaselineValue * (1.0 + $AllowedRegressionPercent / 100.0)
}
$regressionPercent = if ($BaselineValue -eq 0.0) {
if ($CandidateValue -eq 0.0) { 0.0 } else { [double]::PositiveInfinity }
} else {
(($CandidateValue - $BaselineValue) / $BaselineValue) * 100.0
}
$passed = $CandidateValue -le $limit
$comparisons.Add([pscustomobject]@{
result = $ResultKey
metric = $MetricName
baseline = $BaselineValue
candidate = $CandidateValue
limit = $limit
regression_percent = $regressionPercent
passed = $passed
})
if (-not $passed) {
$failures.Add(
("{0} {1} regressed by {2:N3}%: baseline={3:N3} candidate={4:N3} limit={5:N3}" -f
$ResultKey,
$MetricName,
$regressionPercent,
$BaselineValue,
$CandidateValue,
$limit)
)
}
}
$baselineReports = @(
for ($baselineIndex = 0; $baselineIndex -lt $BaselineReport.Count; $baselineIndex++) {
Read-RenderReport `
-Path $BaselineReport[$baselineIndex] `
-Label "Baseline[$baselineIndex]"
}
)
$candidateReports = @(
for ($candidateIndex = 0; $candidateIndex -lt $CandidateReport.Count; $candidateIndex++) {
Read-RenderReport `
-Path $CandidateReport[$candidateIndex] `
-Label "Candidate[$candidateIndex]"
}
)
$baseline = Merge-RenderReports -Reports $baselineReports -Label 'baseline'
$candidate = Merge-RenderReports -Reports $candidateReports -Label 'candidate'
Assert-EqualValue 'schema_version' $baseline.schema_version $candidate.schema_version
Assert-EqualValue 'profile' $baseline.profile $candidate.profile
Assert-EqualValue 'cache_state' $baseline.cache_state $candidate.cache_state
Assert-EqualValue 'viewport' $baseline.environment.viewport $candidate.environment.viewport
Assert-EqualValue 'Godot version' $baseline.environment.godot_version.string $candidate.environment.godot_version.string
Assert-EqualValue 'rendering driver' $baseline.environment.rendering_driver $candidate.environment.rendering_driver
Assert-EqualValue 'rendering method' $baseline.environment.rendering_method $candidate.environment.rendering_method
Assert-EqualValue 'video adapter' $baseline.environment.video_adapter $candidate.environment.video_adapter
Assert-EqualValue 'CPU' $baseline.environment.cpu $candidate.environment.cpu
Assert-EqualValue 'cache contract' $baseline.cache_contract $candidate.cache_contract
Assert-CompatibleCacheInventory $baseline.cache_inventory $candidate.cache_inventory
$baselineIndex = Build-ResultIndex -Results @($baseline.results) -Label 'Baseline'
$candidateIndex = Build-ResultIndex -Results @($candidate.results) -Label 'Candidate'
Assert-EqualValue 'result key set' @($baselineIndex.Keys | Sort-Object) @($candidateIndex.Keys | Sort-Object)
$budgets = $baseline.comparison_budgets
$metricDefinitions = @(
@('load_time_ms', 'load_time_ms_max_regression_percent', 'root'),
@('frame_ms_p95', 'frame_ms_p95_max_regression_percent', 'metrics'),
@('frame_ms_p99', 'frame_ms_p99_max_regression_percent', 'metrics'),
@('max_hitch_ms', 'max_hitch_ms_max_regression_percent', 'metrics'),
@('memory_static_bytes', 'memory_bytes_max_regression_percent', 'metrics'),
@('video_memory_bytes', 'memory_bytes_max_regression_percent', 'metrics')
)
foreach ($key in @($baselineIndex.Keys | Sort-Object)) {
if (-not $candidateIndex.ContainsKey($key)) {
continue
}
$baselineResult = $baselineIndex[$key]
$candidateResult = $candidateIndex[$key]
foreach ($definition in $metricDefinitions) {
$metricName = $definition[0]
$budgetName = $definition[1]
$location = $definition[2]
$baselineValue = if ($location -eq 'root') {
[double]$baselineResult.$metricName
} else {
[double]$baselineResult.metrics.$metricName
}
$candidateValue = if ($location -eq 'root') {
[double]$candidateResult.$metricName
} else {
[double]$candidateResult.metrics.$metricName
}
Compare-Metric `
-ResultKey $key `
-MetricName $metricName `
-BaselineValue $baselineValue `
-CandidateValue $candidateValue `
-AllowedRegressionPercent ([double]$budgets.$budgetName)
}
}
$summary = [pscustomobject]@{
schema_version = 1
baseline_revision = $baseline.revision
candidate_revision = $candidate.revision
baseline_created_utc = $baseline.created_utc
candidate_created_utc = $candidate.created_utc
baseline_samples = $baselineReports.Count
baseline_reports = @($BaselineReport)
candidate_samples = $candidateReports.Count
candidate_reports = @($CandidateReport)
result_pairs = $baselineIndex.Count
metric_comparisons = $comparisons.Count
passed = $failures.Count -eq 0
failures = @($failures)
comparisons = @($comparisons)
}
if ($OutputReport) {
$parent = Split-Path -Parent $OutputReport
if ($parent -and -not (Test-Path -LiteralPath $parent)) {
New-Item -ItemType Directory -Path $parent | Out-Null
}
$summary | ConvertTo-Json -Depth 20 | Set-Content -Encoding UTF8 -LiteralPath $OutputReport
}
if ($failures.Count -gt 0) {
foreach ($failure in $failures) {
Write-Error "RENDER_PERFORMANCE: $failure" -ErrorAction Continue
}
Write-Host "RENDER_PERFORMANCE FAIL result_pairs=$($baselineIndex.Count) comparisons=$($comparisons.Count) failures=$($failures.Count)"
exit 1
}
Write-Host "RENDER_PERFORMANCE PASS result_pairs=$($baselineIndex.Count) comparisons=$($comparisons.Count) max_regression_percent=10"
exit 0
@@ -0,0 +1,106 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$RepeatedSampleComparison,
[Parameter(Mandatory = $true)]
[string]$LongWindowComparison,
[string]$OutputReport
)
$ErrorActionPreference = 'Stop'
function Read-ComparisonReport {
param([string]$Path, [string]$Label)
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
throw "$Label comparison does not exist: $Path"
}
$report = Get-Content -Raw -Encoding UTF8 -LiteralPath $Path | ConvertFrom-Json
if ($null -eq $report -or $null -eq $report.comparisons) {
throw "$Label comparison has no comparisons array: $Path"
}
return $report
}
function Build-ComparisonIndex {
param([object[]]$Comparisons, [string]$Label)
$index = @{}
foreach ($comparison in $Comparisons) {
$key = "$($comparison.result)|$($comparison.metric)"
if ($index.ContainsKey($key)) {
throw "$Label comparison contains duplicate metric: $key"
}
$index[$key] = $comparison
}
return $index
}
$repeatedReport = Read-ComparisonReport `
-Path $RepeatedSampleComparison `
-Label 'Repeated-sample'
$longWindowReport = Read-ComparisonReport `
-Path $LongWindowComparison `
-Label 'Long-window'
$repeatedIndex = Build-ComparisonIndex `
-Comparisons @($repeatedReport.comparisons) `
-Label 'Repeated-sample'
$longWindowIndex = Build-ComparisonIndex `
-Comparisons @($longWindowReport.comparisons) `
-Label 'Long-window'
$repeatedKeys = @($repeatedIndex.Keys | Sort-Object)
$longWindowKeys = @($longWindowIndex.Keys | Sort-Object)
if (($repeatedKeys | ConvertTo-Json -Compress) -cne ($longWindowKeys | ConvertTo-Json -Compress)) {
throw 'Comparison metric inventories differ between repeated and long-window protocols'
}
$repeatableRegressions = [System.Collections.Generic.List[object]]::new()
$nonRepeatableRegressions = [System.Collections.Generic.List[object]]::new()
foreach ($key in $repeatedKeys) {
$repeatedMetricComparison = $repeatedIndex[$key]
$longWindowMetricComparison = $longWindowIndex[$key]
$repeatedFailed = ([bool]$repeatedMetricComparison.passed) -eq $false
$longWindowFailed = ([bool]$longWindowMetricComparison.passed) -eq $false
if ($repeatedFailed -and $longWindowFailed) {
$repeatableRegressions.Add([pscustomobject]@{
key = $key
repeated_sample = $repeatedMetricComparison
long_window = $longWindowMetricComparison
})
} elseif ($repeatedFailed -or $longWindowFailed) {
$nonRepeatableRegressions.Add([pscustomobject]@{
key = $key
failed_protocol = if ($repeatedFailed) { 'repeated_sample' } else { 'long_window' }
})
}
}
$summary = [pscustomobject]@{
schema_version = 1
repeated_sample_comparison = $RepeatedSampleComparison
long_window_comparison = $LongWindowComparison
metric_comparisons = $repeatedKeys.Count
passed = $repeatableRegressions.Count -eq 0
repeatable_regressions = @($repeatableRegressions)
non_repeatable_regressions = @($nonRepeatableRegressions)
}
if ($OutputReport) {
$parent = Split-Path -Parent $OutputReport
if ($parent -and -not (Test-Path -LiteralPath $parent)) {
New-Item -ItemType Directory -Path $parent | Out-Null
}
$summary | ConvertTo-Json -Depth 20 | Set-Content -Encoding UTF8 -LiteralPath $OutputReport
}
if ($repeatableRegressions.Count -gt 0) {
foreach ($regression in $repeatableRegressions) {
Write-Error "RENDER_PERFORMANCE_STABILITY: repeatable regression $($regression.key)" -ErrorAction Continue
}
Write-Host "RENDER_PERFORMANCE_STABILITY FAIL repeated=$($repeatableRegressions.Count) metrics=$($repeatedKeys.Count)"
exit 1
}
Write-Host "RENDER_PERFORMANCE_STABILITY PASS metrics=$($repeatedKeys.Count) non_repeatable=$($nonRepeatableRegressions.Count)"
exit 0