Compare commits
18 Commits
f8348cf0cb
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 1fd87b3132 | |||
| bc4440562d | |||
| 5b73ddadc9 | |||
| a11eb50219 | |||
| adc441ffca | |||
| afbc4faab3 | |||
| f77f50c0f9 | |||
| 4369dd3c91 | |||
| fe6d0deab9 | |||
| 203d40bd0e | |||
| 3bf1c2c657 | |||
| d9d20cb53f | |||
| ef6d324b4f | |||
| f978a16806 | |||
| 541279ed45 | |||
| 8da41dc17c | |||
| 7e97b19095 | |||
| 07521ee6a4 |
+28
@@ -5,3 +5,31 @@
|
||||
path = third_party/godot-cpp
|
||||
url = https://github.com/godotengine/godot-cpp
|
||||
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
|
||||
|
||||
@@ -686,6 +686,42 @@ SKYBOX_MODEL ...
|
||||
- полноценный liquid rendering там не реализован;
|
||||
- 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:
|
||||
|
||||
- старый клиент не рендерил все как 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
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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.
|
||||
@@ -9,6 +9,12 @@ OpenWC состоит из двух продуктов на общей плат
|
||||
|
||||
Оба продукта используют общие канонические модели, импорт данных и 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. Сервер авторитетен для боя, ресурсов, инвентаря, квестового прогресса и общего мира.
|
||||
@@ -57,6 +63,11 @@ TrinityCore/AzerothCore ◄── Network Adapter ◄── Runtime Client
|
||||
|
||||
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
|
||||
|
||||
Владеет 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.
|
||||
- `ContentTypeDescriptor` — schema, inspector, validator, compiler.
|
||||
- `WorldRenderer` — streaming focus и entity presentation.
|
||||
- `GraphicsProfile` — explicit Blizzlike/Enhanced/Racing visual capabilities.
|
||||
- `GameplaySystem` — commands/events без scene dependency.
|
||||
- `EditorTool` — selection, command creation и gizmo, без прямой записи.
|
||||
- `TestFixtureProvider` — обезличенные packets, DB snapshots и content fixtures.
|
||||
|
||||
@@ -197,7 +197,7 @@ func request_streaming_tile_load(request: StreamingTileLoadRequest) -> void:
|
||||
- Предпочитать именованные predicates длинным boolean expressions.
|
||||
- Branch по capability/profile должен быть локальным и типизированным.
|
||||
- Не распространять `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`, если таблица остаётся читаемой и валидируемой.
|
||||
|
||||
## Comments
|
||||
|
||||
@@ -181,7 +181,7 @@ stateDiagram-v2
|
||||
- Uniform/global parameter: coordinate/color space, range, units и producer.
|
||||
- Material profile: supported WoW shader/blend modes и approximations.
|
||||
- Expensive branch/texture dependency имеет cost/fallback note.
|
||||
- Blizzlike и Enhanced behavior документируются отдельно.
|
||||
- Blizzlike, Enhanced и Racing behavior документируются отдельно.
|
||||
|
||||
### Network codecs
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ Gameplay state, network session, renderer world, editor session, caches конк
|
||||
- Visibility ranges/HLOD, occlusion и automatic mesh LOD использовать совместно по профилю.
|
||||
- Unique materials/textures минимизировать; descriptor/resource counts являются budget metric.
|
||||
- RenderingServer RIDs имеют явного владельца и освобождаются в deterministic shutdown test.
|
||||
- Shader/material profiles разделяют Blizzlike и Enhanced; runtime не компилирует тяжёлые варианты при пересечении ADT boundary.
|
||||
- Shader/material profiles разделяют Blizzlike, Enhanced и Racing; runtime не компилирует тяжёлые варианты при пересечении ADT boundary.
|
||||
|
||||
## EditorPlugin lifecycle
|
||||
|
||||
|
||||
+15
-1
@@ -2,6 +2,11 @@
|
||||
|
||||
Reference-код используется для исследования форматов, поведения и архитектурных вариантов. Он не определяет API OpenWC и не копируется без проверки лицензии, корректности и соответствия Godot.
|
||||
|
||||
Актуальные branches и pinned commits локальных Git-референсов зафиксированы в
|
||||
[`reference/README.md`](../reference/README.md#git-reference-revisions). Gitlinks
|
||||
точно фиксируют ревизию, а `.gitmodules` задаёт canonical remote и ветку
|
||||
для контролируемого обновления.
|
||||
|
||||
## Основные источники
|
||||
|
||||
### OpenWC renderer
|
||||
@@ -17,12 +22,13 @@ Reference-код используется для исследования фор
|
||||
|
||||
### WoWee
|
||||
|
||||
- Исследование обновлено до `master` commit `607ea3b8369851014721416293f8e95dfbe64eec` (2026-09-05), относительно прежнего reviewed pin `8456c236b57140e98667d6d8188f5cd1cc226daf`.
|
||||
- `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/TESTING.md` — единая точка запуска тестов, fixtures, sanitizers и CI discipline.
|
||||
- `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
|
||||
|
||||
@@ -51,6 +57,14 @@ Reference-код используется для исследования фор
|
||||
|
||||
Не переносим напрямую старый 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
|
||||
|
||||
- [`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
@@ -18,7 +18,20 @@
|
||||
|
||||
Готово, когда 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.
|
||||
- Content Project schema, stable IDs, save/load/migration.
|
||||
@@ -28,7 +41,7 @@
|
||||
|
||||
Готово, когда небольшой synthetic project можно создать, изменить, undo, перезапустить Editor и получить идентичное состояние.
|
||||
|
||||
## M2 — Server inspector и adapters
|
||||
## M3 — Server inspector и adapters
|
||||
|
||||
- TrinityCore/AzerothCore connection profiles.
|
||||
- Schema detection и capabilities.
|
||||
@@ -38,7 +51,7 @@
|
||||
|
||||
Готово, когда одна сущность round-trip проходит оба поддерживаемых adapter profile без молчаливой потери данных.
|
||||
|
||||
## M3 — World Editor MVP
|
||||
## M4 — World Editor MVP
|
||||
|
||||
- Map viewport и coordinate overlays.
|
||||
- Server spawn visualization.
|
||||
@@ -48,7 +61,7 @@
|
||||
|
||||
Готово, когда NPC размещается в Editor и появляется на тестовом core в ожидаемой позиции.
|
||||
|
||||
## M4 — Quest vertical slice
|
||||
## M5 — Quest vertical slice
|
||||
|
||||
- Quest form и chain graph.
|
||||
- Kill/collect/explore objectives, giver/ender, rewards и localization.
|
||||
@@ -57,7 +70,7 @@
|
||||
|
||||
Готово, когда созданный в Editor квест полностью проходится клиентом.
|
||||
|
||||
## M5 — Playable network client
|
||||
## M6 — Playable network client
|
||||
|
||||
- Auth, realm, character selection и world session.
|
||||
- Entity/update fields и world spawn.
|
||||
@@ -66,7 +79,7 @@
|
||||
|
||||
Готово, когда клиент стабильно входит в мир, перемещается и видит синхронизированные entities.
|
||||
|
||||
## M6 — Core gameplay
|
||||
## M7 — Core gameplay
|
||||
|
||||
- Combat/spells/auras/death.
|
||||
- Inventory/equipment/loot/vendors.
|
||||
@@ -76,7 +89,7 @@
|
||||
|
||||
Готово, когда базовый leveling loop проходит без внешнего клиента.
|
||||
|
||||
## M7 — Dungeon authoring
|
||||
## M8 — Dungeon authoring
|
||||
|
||||
- DungeonPackage, encounters, triggers, doors и spawn groups.
|
||||
- SmartAI/script skeleton generation.
|
||||
@@ -85,7 +98,7 @@
|
||||
|
||||
Готово, когда custom dungeon собирается, разворачивается и проходится группой на test core.
|
||||
|
||||
## M8 — Compatibility и completeness
|
||||
## M9 — Compatibility и completeness
|
||||
|
||||
- Feature matrix WoW 3.3.5a.
|
||||
- Addon compatibility tiers.
|
||||
@@ -98,17 +111,17 @@
|
||||
При равной ценности порядок такой:
|
||||
|
||||
1. безопасность данных и воспроизводимость;
|
||||
2. корректность протокола и authoritative state;
|
||||
3. пользовательский vertical slice;
|
||||
4. diagnostics и testability;
|
||||
5. frame pacing;
|
||||
6. визуальная точность и polish.
|
||||
2. original-client renderer fidelity и frame pacing текущего M04;
|
||||
3. корректность протокола и authoritative state;
|
||||
4. пользовательский vertical slice;
|
||||
5. diagnostics и testability;
|
||||
6. opt-in визуальные улучшения после Blizzlike evidence.
|
||||
|
||||
## Не делать раньше времени
|
||||
|
||||
- прямую запись в production DB;
|
||||
- универсальный visual scripting для любой C++ механики;
|
||||
- массовую реализацию Lua API без работающего UI slice;
|
||||
- большой rewrite существующего renderer;
|
||||
- полный custom renderer до bounded shader/backend spike и profiler evidence;
|
||||
- multi-expansion abstraction до устойчивого профиля 3.3.5a;
|
||||
- proprietary asset packaging в репозитории.
|
||||
|
||||
@@ -44,8 +44,8 @@ ID записывается в claim, ветке, PR/MR и handoff. Нельзя
|
||||
```text
|
||||
M01-FND-COORDS-001
|
||||
M03-RND-SCHEDULER-001
|
||||
M08-NET-SRP-001
|
||||
M12-UIA-LUA-SPIKE-001
|
||||
M09-NET-SRP-001
|
||||
M13-UIA-LUA-SPIKE-001
|
||||
```
|
||||
|
||||
Program codes определены в [`../targets/DEVELOPMENT_ROADMAP.md`](../targets/DEVELOPMENT_ROADMAP.md).
|
||||
@@ -214,7 +214,7 @@ Merge order:
|
||||
|
||||
```text
|
||||
fnd(M01): add canonical coordinate mapper
|
||||
net(M08): decode auth challenge safely
|
||||
net(M09): decode auth challenge safely
|
||||
rnd(M03): extract streaming target planner
|
||||
test(M00): add paired checkpoint manifest
|
||||
```
|
||||
|
||||
@@ -54,6 +54,42 @@
|
||||
- dense WMO/M2, water, character equipment и UI scale matrices.
|
||||
- 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
|
||||
|
||||
- Golden synthetic meshes проверяют slope, climb, radius erosion, holes, tiled seams и off-mesh connections.
|
||||
|
||||
+40
-2
@@ -40,13 +40,15 @@ Decision/ADR:
|
||||
| OpenWC native loaders | ADOPTED | MPQ/BLP/ADT/WDT/M2/WMO | Текущий import/render pipeline | Неполная fidelity форматов |
|
||||
| StormLib | ADOPTED | MPQ | Чтение архивов через native extension | Version/license/update audit |
|
||||
| WowUnreal | REFERENCE | Полный клиент | Coverage, acceptance criteria, networking/UI research | Unreal-specific design |
|
||||
| WoWee | REFERENCE | Клиент/editor/formats | Architecture, editor workflows, tests, open formats | Заявления требуют независимой проверки |
|
||||
| Noggit Red | REFERENCE | World editor | Terrain/placement UX, UID workflows | Не Godot architecture |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| whoa | REFERENCE | Client behavior | 3.3.5a runtime semantics и fixtures | Лицензия и переносимость отдельных решений |
|
||||
| wow.export | REFERENCE | Asset conversion | M2/WMO/material/export edge cases | Web-specific pipeline |
|
||||
| Blender WoW Studio | REFERENCE | Authoring/conversion | WMO/M2/ADT authoring knowledge | Blender-specific UI/data model |
|
||||
| [Wowser](https://github.com/wowserhq/wowser) | REFERENCE | 3.3.5a web client | Auth/realm/character/world protocol, binary parsing, asset pipeline и render research | Старый JS/WebGL proof-of-concept, неполный клиент |
|
||||
| [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 |
|
||||
| [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 |
|
||||
@@ -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 |
|
||||
| [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 — карточка кандидата
|
||||
|
||||
- **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.
|
||||
- **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 — карточка кандидата
|
||||
|
||||
- **Problem solved:** Recast navmesh generation, Detour queries, tiled navigation и dynamic obstacles.
|
||||
|
||||
@@ -52,9 +52,11 @@
|
||||
| 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 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) |
|
||||
| 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 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) |
|
||||
| Streaming target planner | Implemented | [`streaming-target-planner.md`](streaming-target-planner.md) |
|
||||
| Render budget scheduler | Implemented | [`render-budget-scheduler.md`](render-budget-scheduler.md) |
|
||||
|
||||
@@ -241,7 +241,7 @@ runtime data migration.
|
||||
| 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 |
|
||||
| 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
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@ separate versioned movement snapshot contract.
|
||||
|
||||
- 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 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
|
||||
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 |
|
||||
| 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 |
|
||||
| Jump/fall/swim | Planned | M02/M09 roadmap | Requires terrain/liquid and server contracts |
|
||||
| Prediction/reconciliation | Planned | M08/M09 roadmap | Requires movement snapshot/network contract |
|
||||
| Jump/fall/swim | Planned | M02/M10 roadmap | Requires terrain/liquid and server contracts |
|
||||
| Prediction/reconciliation | Planned | M09/M10 roadmap | Requires movement snapshot/network contract |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
| Status | Implemented extraction |
|
||||
| Target/work package | M03 / `M03-RND-M2-ANIMATION-PLAYBACK-001` |
|
||||
| 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 |
|
||||
|
||||
## 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 | 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.
|
||||
|
||||
## Data flow
|
||||
@@ -67,13 +67,13 @@ mutation, play and seek. The service retains no inputs.
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Identity[Path and index] --> Phase[Stable hash phase]
|
||||
NativeInventory[Exact-script native inventory] --> Prepare[prepare runtime if available]
|
||||
Phase --> NativePhase[set native phase]
|
||||
NativeInventory[Exact-script native inventory] --> Prepare[prepare local runtime mesh]
|
||||
Phase --> Prepare
|
||||
Players[AnimationPlayers] --> Select[Choose path-specific default]
|
||||
Select --> Loop[Set every animation LOOP_LINEAR]
|
||||
Loop --> Play[Play selected name]
|
||||
Phase --> Seek[Seek positive-length selection]
|
||||
NativePhase --> Diagnostics{Debug requested?}
|
||||
Prepare --> Diagnostics{Debug requested?}
|
||||
Diagnostics -->|yes| Snapshot[Detached runtime state]
|
||||
```
|
||||
|
||||
@@ -103,7 +103,7 @@ sequenceDiagram
|
||||
M->>F: animation_players_in_subtree(duplicate)
|
||||
F-->>M: ordered players
|
||||
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-->>M: optional detached native diagnostics
|
||||
M-->>M: tag states with instance index
|
||||
@@ -132,6 +132,11 @@ flowchart TB
|
||||
- Native arrays are assigned by reference exactly as before extraction.
|
||||
- Diagnostic Dictionaries are deep-duplicated before return.
|
||||
- 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
|
||||
|
||||
@@ -168,7 +173,8 @@ and material versions are unchanged; no rebake is required.
|
||||
|
||||
- `verify_m2_animation_playback_controller.gd` covers exact phase, ordinary/
|
||||
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.
|
||||
- Fidelity evidence is exact policy/mutation extraction; no private asset or
|
||||
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.
|
||||
- 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
|
||||
|
||||
|
||||
@@ -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 |
|
||||
@@ -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 |
|
||||
| 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 |
|
||||
| Liquid/swim query | Planned | Outside contract | M09/M12 world gameplay |
|
||||
| Liquid/swim query | Planned | Outside contract | M10/M13 world gameplay |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
| Status | Implemented |
|
||||
| Target/work package | M03 / `M03-RND-WMO-PLACEMENT-RESOLVER-001` |
|
||||
| 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 |
|
||||
|
||||
## Purpose
|
||||
@@ -31,14 +31,17 @@ live-prototype instance paths.
|
||||
flowchart LR
|
||||
Parsed[ADT/WDT WMO placement] --> Loader[StreamingWorldLoader adapter]
|
||||
Loader --> Resolver[WmoPlacementResolver]
|
||||
Loader --> Factory[WmoSceneInstanceFactory]
|
||||
Factory --> Resolver
|
||||
Resolver --> CacheKey[Normalized cache key]
|
||||
Resolver --> Identity[Registry unique key]
|
||||
Resolver --> Transform[World Transform3D]
|
||||
CacheKey --> Cache[Loader WMO caches/requests]
|
||||
Identity --> Registry[WmoPlacementRegistry]
|
||||
Transform --> RenderRoot[Lightweight render root]
|
||||
Transform --> Scene[Cached scene instance]
|
||||
Transform --> Live[Live prototype instance]
|
||||
Transform --> Factory
|
||||
Factory --> Scene[Cached scene instance]
|
||||
Factory --> Live[Live prototype instance]
|
||||
```
|
||||
|
||||
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 |
|
||||
| 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 | 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.
|
||||
|
||||
@@ -91,6 +94,7 @@ and shutdown require no resolver operation.
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Loader as StreamingWorldLoader
|
||||
participant Factory as WmoSceneInstanceFactory
|
||||
participant Resolver as WmoPlacementResolver
|
||||
participant Registry as WmoPlacementRegistry
|
||||
participant Instance as Render/cached/live instance
|
||||
@@ -98,9 +102,15 @@ sequenceDiagram
|
||||
Resolver-->>Loader: cache key
|
||||
Loader->>Resolver: resolve_unique_key(placement, tile, index)
|
||||
Resolver-->>Registry: identity adopted by loader
|
||||
Loader->>Resolver: resolve_world_transform(placement)
|
||||
Resolver-->>Loader: value Transform3D
|
||||
Loader->>Instance: assign transform and attach/build
|
||||
alt lightweight render root
|
||||
Loader->>Resolver: resolve_world_transform(placement)
|
||||
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
|
||||
@@ -109,7 +119,9 @@ sequenceDiagram
|
||||
- `WmoPlacementRegistry` owns placement-key reference sets. The loader owns its
|
||||
key-to-Node map, cache/load-request state, jobs/queues, resource fallback and
|
||||
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.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
@@ -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)
|
||||
@@ -7,7 +7,7 @@
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
||||
## Purpose
|
||||
@@ -74,6 +74,8 @@ flowchart LR
|
||||
WmoGroupMaterializer --> Scene
|
||||
Loader --> WmoScenePreparer[WmoRuntimeScenePreparer]
|
||||
WmoScenePreparer --> Scene
|
||||
Loader --> WmoInstanceFactory[WmoSceneInstanceFactory]
|
||||
WmoInstanceFactory --> WmoScenePreparer
|
||||
Native --> Parsed[Parsed tile/model data]
|
||||
Parsed --> Loader
|
||||
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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
@@ -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 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 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 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 |
|
||||
@@ -425,6 +429,9 @@ sequenceDiagram
|
||||
creation, render settings and attachment without retaining engine objects.
|
||||
`WmoRuntimeScenePreparer` owns cached-only Mesh traversal/finalization plus the
|
||||
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
|
||||
and pending cache paths; `WmoRenderResourceFinalizer` owns its terminal
|
||||
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,
|
||||
exact Mesh traversal order, direct occluder removal, recursive shadow policy,
|
||||
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,
|
||||
validated/missing terminal transitions, transient/full reset, detached sorted
|
||||
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 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 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 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 |
|
||||
@@ -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_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_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_policy.gd` | Immutable renderer radius/prefetch policy |
|
||||
| `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_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_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_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 |
|
||||
@@ -772,6 +785,9 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
||||
| `src/native/src/*_loader.cpp` | Native binary parsing |
|
||||
| `src/tools/build_*cache.gd`, `src/tools/bake_*cache.gd` | Offline cache generation |
|
||||
| `tools/run_render_baseline.ps1` | Unified M00 baseline runner |
|
||||
| `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/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 |
|
||||
|
||||
@@ -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
|
||||
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.
|
||||
- `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.
|
||||
|
||||
+1
-1
Submodule reference/WoWee updated: 626243e937...607ea3b836
Submodule
+1
Submodule reference/benilla added at bc1a2428dd
+1
-1
Submodule reference/open-realm updated: c7ed0545c8...950a1e8c13
+1
-1
Submodule reference/whoa updated: 74bc963a1c...ea13456360
+1
-1
Submodule reference/wow.export updated: 7f8d4f8784...c2fd7bde36
@@ -39,9 +39,12 @@ func start_instance_playback(
|
||||
var phase := phase_for_instance(relative_path, instance_index)
|
||||
var native_diagnostics: Array[Dictionary] = []
|
||||
for animator in native_animators_in_subtree(root, native_animator_script):
|
||||
if animator.has_method("prepare_runtime"):
|
||||
animator.prepare_runtime()
|
||||
animator.set_phase(phase)
|
||||
if animator.has_method("prepare_runtime_at_phase"):
|
||||
animator.prepare_runtime_at_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"):
|
||||
var diagnostic_variant = animator.runtime_debug_state()
|
||||
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
|
||||
@@ -26,11 +26,13 @@ func setup(target_mesh_instance: MeshInstance3D, bone_data: Array, surface_data:
|
||||
_capture_materials()
|
||||
_make_mesh_unique()
|
||||
_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:
|
||||
prepare_runtime()
|
||||
if not _prepared:
|
||||
prepare_runtime()
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
@@ -41,24 +43,49 @@ func _process(delta: float) -> void:
|
||||
|
||||
|
||||
func set_phase(phase: float) -> void:
|
||||
if animation_length <= 0.0:
|
||||
_time = 0.0
|
||||
else:
|
||||
_time = fposmod(animation_length * phase, animation_length)
|
||||
_set_phase_time(phase)
|
||||
_rebuild_mesh(_time)
|
||||
|
||||
|
||||
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()
|
||||
if force_rebuild:
|
||||
_materials.clear()
|
||||
_capture_materials()
|
||||
_unique_mesh_ready = false
|
||||
_make_mesh_unique()
|
||||
_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)
|
||||
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:
|
||||
return {
|
||||
"prepared": _prepared,
|
||||
@@ -81,11 +108,12 @@ func _resolve_mesh_instance() -> void:
|
||||
func _make_mesh_unique() -> void:
|
||||
if _unique_mesh_ready or mesh_instance == null or mesh_instance.mesh == null:
|
||||
return
|
||||
var duplicated := mesh_instance.mesh.duplicate(true) as ArrayMesh
|
||||
if duplicated == null:
|
||||
return
|
||||
mesh_instance.mesh = duplicated
|
||||
mesh = duplicated
|
||||
# _rebuild_mesh() replaces every surface from the retained native arrays, so
|
||||
# copying the source ArrayMesh would only duplicate data that is discarded.
|
||||
# Materials were captured before this call and are intentionally shared.
|
||||
var instance_mesh := ArrayMesh.new()
|
||||
mesh_instance.mesh = instance_mesh
|
||||
mesh = instance_mesh
|
||||
_unique_mesh_ready = true
|
||||
|
||||
|
||||
@@ -143,20 +171,20 @@ func _rebuild_mesh(time: float) -> void:
|
||||
continue
|
||||
var transform: Transform3D = bone_matrices[bone_index]
|
||||
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
|
||||
total_weight += weight
|
||||
if total_weight > 0.0:
|
||||
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()
|
||||
else:
|
||||
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]
|
||||
else:
|
||||
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]
|
||||
|
||||
var arrays := []
|
||||
|
||||
@@ -42,6 +42,9 @@ const WMO_RENDER_GROUP_MATERIALIZER_SCRIPT := preload(
|
||||
const WMO_RUNTIME_SCENE_PREPARER_SCRIPT := preload(
|
||||
"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_NATIVE_ANIMATOR_SCRIPT := preload("res://src/scenes/streaming/m2_native_animator.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_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 _wmo_placement_registry := WMO_PLACEMENT_REGISTRY_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)
|
||||
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:
|
||||
_prepare_runtime_wmo_instance(scene_instance)
|
||||
_world_wmo_root.add_child(scene_instance)
|
||||
@@ -3920,26 +3933,6 @@ func _get_resource_file_size(path: String) -> int:
|
||||
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:
|
||||
_wmo_runtime_scene_preparer.prepare_cached_instance(
|
||||
instance,
|
||||
@@ -4632,11 +4625,15 @@ func _instantiate_wmo_world(rel_path: String, placement: Dictionary) -> Node3D:
|
||||
if prototype == 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
|
||||
# offset — use world-space placement.pos directly (no tile_origin subtraction).
|
||||
instance.transform = _wmo_placement_resolver.resolve_world_transform(placement)
|
||||
# offset — the factory applies world-space placement.pos directly.
|
||||
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(
|
||||
instance,
|
||||
enable_occlusion_culling,
|
||||
@@ -4736,7 +4733,7 @@ func _get_or_load_wmo_prototype(rel_path: String) -> Node3D:
|
||||
var resource: Resource = load(cache_path)
|
||||
if resource is PackedScene:
|
||||
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
|
||||
return node as Node3D
|
||||
if node is Node:
|
||||
|
||||
@@ -176,14 +176,14 @@ func _bake_glb_animation_cache(
|
||||
if not force and FileAccess.file_exists(abs_out_glb):
|
||||
return true
|
||||
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):
|
||||
push_warning("M2 GLB converter not found: %s" % converter)
|
||||
return false
|
||||
var stdout := []
|
||||
var exit_code := OS.execute(
|
||||
python_exe,
|
||||
[abs_converter, abs_m2, abs_output],
|
||||
[abs_converter, abs_m2, converter_output_directory],
|
||||
stdout,
|
||||
true,
|
||||
false)
|
||||
|
||||
@@ -16,6 +16,7 @@ func _initialize() -> void:
|
||||
_verify_animation_selection_priority(failures)
|
||||
_verify_player_loop_play_and_seek(failures)
|
||||
_verify_native_copy_start_and_diagnostics(failures)
|
||||
_verify_native_single_rebuild_preparation(failures)
|
||||
_verify_ownership_boundaries(failures)
|
||||
var elapsed_milliseconds := _verify_bounded_timing(failures)
|
||||
if not failures.is_empty():
|
||||
@@ -24,7 +25,7 @@ func _initialize() -> void:
|
||||
quit(1)
|
||||
return
|
||||
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
|
||||
)
|
||||
quit(0)
|
||||
@@ -142,6 +143,47 @@ func _verify_native_copy_start_and_diagnostics(failures: Array[String]) -> void:
|
||||
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:
|
||||
var controller_source := FileAccess.get_file_as_string(CONTROLLER_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_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_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 [
|
||||
"Node.DUPLICATE_SIGNALS | Node.DUPLICATE_GROUPS | Node.DUPLICATE_SCRIPTS",
|
||||
"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
|
||||
@@ -4,6 +4,7 @@ extends SceneTree
|
||||
|
||||
const RESOLVER_SCRIPT := preload("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"
|
||||
|
||||
|
||||
@@ -25,7 +26,7 @@ func _initialize() -> void:
|
||||
quit(1)
|
||||
return
|
||||
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
|
||||
)
|
||||
quit(0)
|
||||
@@ -138,6 +139,7 @@ func _verify_node_property_equivalence(failures: Array[String]) -> void:
|
||||
func _verify_source_boundaries(failures: Array[String]) -> void:
|
||||
var loader_source := _read_text(LOADER_PATH, failures)
|
||||
var resolver_source := _read_text(RESOLVER_PATH, failures)
|
||||
var instance_factory_source := _read_text(INSTANCE_FACTORY_PATH, failures)
|
||||
_expect_true(
|
||||
loader_source.contains("WMO_PLACEMENT_RESOLVER_SCRIPT.new()"),
|
||||
"loader composes resolver",
|
||||
@@ -157,8 +159,13 @@ func _verify_source_boundaries(failures: Array[String]) -> void:
|
||||
)
|
||||
_expect_equal_int(
|
||||
loader_source.count("_wmo_placement_resolver.resolve_world_transform("),
|
||||
3,
|
||||
"three transform adapters",
|
||||
1,
|
||||
"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
|
||||
)
|
||||
_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
|
||||
@@ -69,6 +69,6 @@
|
||||
- Remaining risks: direct build-12340 timing evidence for movement/camera,
|
||||
jump/fall/swim/fly, autorun, left-button camera/select behavior, active camera
|
||||
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
|
||||
selects `Blizzlike335`. M02 makes no complete `1:1` compatibility claim.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# M03 — Renderer Facade and Safe Extraction
|
||||
|
||||
<!-- OPENWC_TARGET:ACTIVE -->
|
||||
<!-- OPENWC_TARGET:DONE -->
|
||||
<!-- OPENWC_TARGET_DONE:M03:3bf1c2c:2026-08-02 -->
|
||||
|
||||
## Outcome
|
||||
|
||||
@@ -12,9 +13,9 @@
|
||||
- [x] Запретить gameplay/editor доступ к внутренним очередям streamer.
|
||||
- [x] Извлечь pure `StreamingTargetPlanner`.
|
||||
- [x] Извлечь `RenderBudgetScheduler` с bounded queues/cancellation.
|
||||
- [ ] Затем извлекать terrain, M2, WMO и liquid services по одному.
|
||||
- [ ] Сохранить cache versioning и main-thread GPU finalization rules.
|
||||
- [ ] Добавить dependency и performance regression checks.
|
||||
- [x] Затем извлекать terrain, M2, WMO и liquid services по одному.
|
||||
- [x] Сохранить cache versioning и main-thread GPU finalization rules.
|
||||
- [x] Добавить dependency и performance regression checks.
|
||||
|
||||
## Fidelity evidence
|
||||
|
||||
@@ -27,7 +28,7 @@ Runtime и Editor используют facade; planner/scheduler тестиру
|
||||
## Evidence
|
||||
|
||||
- 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`,
|
||||
`M03-RND-FACADE-GROUND-QUERY-001`, `M03-RND-FACADE-ENVIRONMENT-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-RENDER-GROUP-MATERIALIZER-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-SCENE-FINALIZER-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
|
||||
adjacent WMO services, shutdown, materials, facade, internal-access `30`,
|
||||
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
|
||||
remained green (13 pre-existing expired M00 claim warnings).
|
||||
- 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,
|
||||
stateless WMO render group materializer and both loader node-attachment 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/
|
||||
drain/release/reset/shutdown/metrics 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
|
||||
loader; both WMO terminal I/O paths, runtime Mesh material refresh and
|
||||
lightweight group materialization plus cached/live subtree preparation are
|
||||
separated; recursive Mesh/shadow mutation and Node creation remain synchronous
|
||||
main-thread work behind existing orchestration/permits;
|
||||
separated; cached/live root creation and placement are also separated, while
|
||||
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;
|
||||
ADT water parsing and active worker interruption remain loader-owned;
|
||||
materialization is separated but remains synchronous main-thread work behind
|
||||
the existing permit, and asset-backed MH2O/MCLQ traversal/leak/p95/p99/
|
||||
original-client evidence is pending; M03 still needs further terrain/M2/WMO/
|
||||
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.
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# M05 — Content Project
|
||||
# M06 — Content Project
|
||||
|
||||
<!-- OPENWC_TARGET:OPEN -->
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# M06 — TrinityCore/AzerothCore Server Adapters
|
||||
# M07 — TrinityCore/AzerothCore Server Adapters
|
||||
|
||||
<!-- OPENWC_TARGET:OPEN -->
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# M07 — World Editor Vertical Slice
|
||||
# M08 — World Editor Vertical Slice
|
||||
|
||||
<!-- OPENWC_TARGET:OPEN -->
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# M08 — WoW 3.3.5a Network Foundation
|
||||
# M09 — WoW 3.3.5a Network Foundation
|
||||
|
||||
<!-- OPENWC_TARGET:OPEN -->
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# M09 — Gameplay Domain
|
||||
# M10 — Gameplay Domain
|
||||
|
||||
<!-- OPENWC_TARGET:OPEN -->
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# M10 — First Playable Client Slice
|
||||
# M11 — First Playable Client Slice
|
||||
|
||||
<!-- OPENWC_TARGET:OPEN -->
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# M11 — Quest End-to-End
|
||||
# M12 — Quest End-to-End
|
||||
|
||||
<!-- OPENWC_TARGET:OPEN -->
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# M12 — WoW 3.3.5a Client Completeness
|
||||
# M13 — WoW 3.3.5a Client Completeness
|
||||
|
||||
<!-- OPENWC_TARGET:OPEN -->
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# M13 — Dungeon and Encounter Authoring
|
||||
# M14 — Dungeon and Encounter Authoring
|
||||
|
||||
<!-- OPENWC_TARGET:OPEN -->
|
||||
|
||||
@@ -8,12 +8,15 @@ Godot-specific implementation rules обязательны из [`../docs/GODOT_
|
||||
|
||||
## Итоговый продукт
|
||||
|
||||
OpenWC должен предоставить четыре согласованных режима:
|
||||
OpenWC должен предоставить пять согласованных режимов/потребителей:
|
||||
|
||||
1. `Blizzlike335` — клиент WoW 3.3.5a build 12340 с максимально точным наблюдаемым поведением.
|
||||
2. `Enhanced` — opt-in графические, UX и accessibility улучшения, не меняющие wire protocol и server authority без отдельного profile.
|
||||
3. `AuthoringStudio` — инструменты мира, контента, БД и playtest внутри Godot Editor.
|
||||
4. `HeadlessTools` — import, validation, bake, packet replay, content build и CI без GUI.
|
||||
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 не являются источником истины.
|
||||
|
||||
@@ -21,7 +24,7 @@ OpenWC должен предоставить четыре согласованн
|
||||
|
||||
- MPQ/BLP/ADT/WDT/M2/WMO native parsing и renderer vertical slice уже существуют.
|
||||
- Runtime streaming, terrain quality, MultiMesh M2, WMO caches, liquids, sky и character experiments находятся в рабочем состоянии, но сосредоточены в крупных orchestration scripts.
|
||||
- Renderer baseline M00 завершён: 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 в основном предстоит реализовать.
|
||||
- Editor plugin пока решает extraction/preview задачи, но не является полноценной authoring platform.
|
||||
|
||||
@@ -45,14 +48,15 @@ M00 renderer baseline
|
||||
└─ M01 coordinates/contracts
|
||||
└─ M02 input/movement/camera/presentation split
|
||||
└─ M03 renderer facade
|
||||
├─ M04 editor shell → M05 content → M06 server adapters → M07 world editor
|
||||
└─ M08 network → M09 gameplay domain → M10 playable client
|
||||
M07 + M10 → M11 quest end-to-end
|
||||
└─ M12 completeness waves
|
||||
└─ M13 dungeon authoring
|
||||
└─ M04 renderer fidelity/graphics foundation
|
||||
├─ M05 editor shell → M06 content → M07 server adapters → M08 world editor
|
||||
└─ M09 network → M10 gameplay domain → M11 playable client
|
||||
M08 + M11 → M12 quest end-to-end
|
||||
└─ M13 completeness waves
|
||||
└─ M14 dungeon authoring
|
||||
```
|
||||
|
||||
M12 не является одним большим этапом. Он выполняется последовательными completeness waves:
|
||||
M13 не является одним большим этапом. Он выполняется последовательными completeness waves:
|
||||
|
||||
1. `C1 Core loop` — combat, spells, auras, death, loot, inventory, quests.
|
||||
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 |
|
||||
| M02 | FND composition, GMP movement, RND presenters | Player split без visual/control regression |
|
||||
| M03 | RND facade/planner/scheduler, FND jobs | Renderer доступен через API и сохраняет budgets |
|
||||
| M04 | EDT plugin shell, QAR editor tests | Reload-safe workspace, commands, undo/recovery |
|
||||
| M05 | FND content schemas/cache, EDT project | Deterministic Content Project build/migration |
|
||||
| M06 | SRV environments/snapshots/adapters | Two-core read/diff/dry-run round-trip |
|
||||
| M07 | EDT world editing/navigation/deploy | NPC placement на dev-core без ручного SQL |
|
||||
| M08 | NET transport/auth/protocol/entities | Headless auth → world и deterministic replay |
|
||||
| M09 | GMP world state/movement/targeting | Packet replay → deterministic gameplay snapshot |
|
||||
| M10 | FND app lifecycle, RND/UIA presentation | Игрок входит, видит entities и перемещается |
|
||||
| M11 | GMP quests, EDT quest tools, SRV deploy, UIA quest UI | Author → deploy → complete quest своим клиентом |
|
||||
| M12 C1–C8 | NET/GMP/RND/UIA completeness + QAR | Feature matrix `Compatible/Verified` по waves |
|
||||
| M13 | EDT dungeon/encounter, SRV modules, GMP instances | Custom dungeon group playtest end-to-end |
|
||||
| M04 | RND/QAR reference corpus, materials, environment, shadows, liquids, characters/effects | Original-client-paired graphics foundation и отделённый Enhanced/Racing profile |
|
||||
| M05 | EDT plugin shell, QAR editor tests | Reload-safe workspace, commands, undo/recovery |
|
||||
| M06 | FND content schemas/cache, EDT project | Deterministic Content Project build/migration |
|
||||
| M07 | SRV environments/snapshots/adapters | Two-core read/diff/dry-run round-trip |
|
||||
| M08 | EDT world editing/navigation/deploy | NPC placement на dev-core без ручного SQL |
|
||||
| M09 | NET transport/auth/protocol/entities | Headless auth → world и deterministic replay |
|
||||
| M10 | GMP world state/movement/targeting | Packet replay → deterministic gameplay snapshot |
|
||||
| M11 | FND app lifecycle, RND/UIA presentation | Игрок входит, видит entities и перемещается |
|
||||
| M12 | GMP quests, EDT quest tools, SRV deploy, UIA quest UI | Author → deploy → complete quest своим клиентом |
|
||||
| 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
|
||||
|
||||
@@ -101,6 +106,8 @@ M12 не является одним большим этапом. Он выпо
|
||||
## Cross-cutting contracts
|
||||
|
||||
- `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.
|
||||
- `AssetRepository` — original assets, overlays, caches и provenance.
|
||||
- `ProtocolProfile` — build, opcodes, update fields, codecs и server capabilities.
|
||||
|
||||
+23
-53
@@ -4,23 +4,23 @@
|
||||
|
||||
Этот каталог задаёт обязательную последовательность реализации. Архитектура описана в `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.
|
||||
|
||||
## Current target
|
||||
## Текущая цель
|
||||
|
||||
`M03` — [03-renderer-facade.md](03-renderer-facade.md)
|
||||
|
||||
Одновременно `ACTIVE` может быть только одна цель. Следующая цель становится `ACTIVE` после появления валидной `OPENWC_TARGET_DONE` метки у предыдущей.
|
||||
`M04` — [04-renderer-fidelity.md](04-renderer-fidelity.md)
|
||||
*Начало фазы выполнения:* `YYYY-MM-DD`
|
||||
Одновременно `АКТИВНАЯ` может быть только одна цель. Следующая цель становится `АКТИВНОЙ` после появления валидной метки `OPENWC_TARGET_DONE` у предыдущей.
|
||||
|
||||
Статусные метки применяются только к исполняемым файлам `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 для выбранного сценария:
|
||||
|
||||
- wire protocol и update fields;
|
||||
- жилл протокол и обновляемые поля;
|
||||
- игровые состояния и переходы;
|
||||
- координаты, timing и server authority;
|
||||
- UI/Lua/FrameXML semantics;
|
||||
@@ -36,53 +36,23 @@
|
||||
| M00 | [Renderer baseline](00-render-baseline.md) | — | DONE |
|
||||
| M01 | [Coordinates and architecture seams](01-coordinates-and-seams.md) | M00 | DONE |
|
||||
| M02 | [Player decomposition](02-player-decomposition.md) | M01 | DONE |
|
||||
| M03 | [Renderer facade and extraction](03-renderer-facade.md) | M02 | ACTIVE |
|
||||
| M04 | [Godot Editor shell](04-editor-shell.md) | M03 | OPEN |
|
||||
| M05 | [Content Project](05-content-project.md) | M04 | OPEN |
|
||||
| M06 | [Server database adapters](06-server-adapters.md) | M05 | OPEN |
|
||||
| M07 | [World Editor vertical slice](07-world-editor-slice.md) | M06 | OPEN |
|
||||
| M08 | [Network foundation](08-network-foundation.md) | M03 | OPEN |
|
||||
| M09 | [Gameplay domain](09-gameplay-domain.md) | M08 | OPEN |
|
||||
| M10 | [Playable client slice](10-playable-client.md) | M09, M03 | OPEN |
|
||||
| M11 | [Quest end-to-end](11-quest-end-to-end.md) | M07, M10 | OPEN |
|
||||
| M12 | [WoW 3.3.5a completeness](12-client-completeness.md) | M11 | OPEN |
|
||||
| M13 | [Dungeon authoring](13-dungeon-authoring.md) | M12 foundation | OPEN |
|
||||
| M03 | [Renderer facade and extraction](03-renderer-facade.md) | M02 | DONE |
|
||||
| M04 | [Renderer fidelity and graphics foundation](04-renderer-fidelity.md) | M03 | ACTIVE |
|
||||
| M05 | [Godot Editor shell](05-editor-shell.md) | M04 | OPEN |
|
||||
| M06 | [Content Project](06-content-project.md) | M05 | OPEN |
|
||||
| M07 | [Server database adapters](07-server-adapters.md) | M06 | OPEN |
|
||||
| M08 | [World Editor vertical slice](08-world-editor-slice.md) | M07 | OPEN |
|
||||
| M09 | [Network foundation](09-network-foundation.md) | M04 | OPEN |
|
||||
| M10 | [Gameplay domain](10-gameplay-domain.md) | M09 | OPEN |
|
||||
| M11 | [Playable client slice](11-playable-client.md) | M10, M04 | OPEN |
|
||||
| M12 | [Quest end-to-end](12-quest-end-to-end.md) | M08, M11 | 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.
|
||||
|
||||
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:
|
||||
```
|
||||
`rilua` зарегистрирован как Lua 5.1.1 runtime/test-oracle
|
||||
@@ -10,7 +10,7 @@
|
||||
- Отделить user settings, developer settings, server connection profiles и content project settings.
|
||||
- Secrets хранить вне Git/Resource; поддержать environment/OS credential provider.
|
||||
- Настройки иметь schema version, defaults, validation, migration и reset-by-section.
|
||||
- `Blizzlike335` сделать неизменяемым базовым профилем; enhanced settings наследуют его явно.
|
||||
- `Blizzlike335` сделать неизменяемым базовым профилем; Enhanced/Racing settings наследуют его явно и не меняют default behavior.
|
||||
|
||||
## FND-2. Application lifecycle
|
||||
|
||||
|
||||
@@ -4,6 +4,59 @@
|
||||
|
||||
Воспроизвести визуальные правила 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
|
||||
|
||||
- `WorldRenderFacade` принимает focus, environment snapshot и entity presentation commands.
|
||||
@@ -99,4 +152,9 @@
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -90,7 +90,19 @@
|
||||
|
||||
- Original 3.3.5a FrameXML/Lua extracted from legally owned data is primary behavior corpus.
|
||||
- `rilua`/PUC-Rio are VM candidates/oracles, not complete WoW UI.
|
||||
- WowBench/Wowser/WowUnreal/WoWee provide API/event/loader test ideas.
|
||||
- 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
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
|
||||
## 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.
|
||||
- Water/sky/weather/light preview with fixed time and reference capture.
|
||||
- Character/equipment/animation/attachment preview.
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user