Architecture Review Report — 2026-08-28
Mode:
/architecture-review(full) · Verdict: CONCERNS
Engine: Unity 6.3 LTS (6000.3) · URP 2D (Render Graph) · NGO 2.x · Input System 1.20 · Addressables 2.x
Loaded: 5 full GDDs + 8 quick-specs + art bible §4.3/§7/§8 (13 design docs) · 8 ADRs (all Proposed) ·architecture.mdv1.0 · engine reference (VERSION, breaking-changes, deprecated-apis, current-best-practices, modules ×4, plugins/addressables) ·tr-registry.yaml(was empty) ·docs/consistency-failures.mdabsent.
Method: three Sonnet verifiers checked every baseline row against the source doc text and confirmed the ADR-driven GDD amendments; the Unity specialist (Opus) audited every engine claim against the live 6000.3 / NGO 2.x / Addressables 2.x / Input System 1.20 docs.
Traceability Summary
architecture.md claims "102 requirements"; its table holds 94 rows. All 94 were confirmed against their source docs (3 misstated — corrected in the registry) and 17 additional architectural requirements were surfaced → 111 total, now registered as stable TR-IDs in tr-registry.yaml (version 2).
| Status | Count |
|---|---|
| ✅ Covered | 64 |
| ⚠️ Partial | 28 |
| ❌ Gap | 19 |
Foundation layer: 0 gaps. Every gap sits in Core/Feature/Presentation and is assigned to a not-yet-written ADR (0008–0014) or deliberately deferred to implementation. The full matrix is in traceability-index.md.
Baseline corrections (registry text updated, IDs unchanged)
- TR-save-003 — the quick-spec already restated Rule 3 to per-drain durability within
flushDeadlineMs(ADR-0004); the baseline described the superseded per-action model. - TR-ui-001 — "UI Toolkit screens" is an
architecture.mddecision; the ui-hud quick-spec explicitly defers the tech choice ("not a design fact"). - TR-net-006/007/008 — originate from the TD reviews / ADR-0002, not the netcode quick-spec (Rule 7 reserves the wire).
- TR-tide-008 — the GDD types tint intensities as
double, notfloat.
Coverage gaps (no ADR)
| ID | System | Requirement | Suggested ADR | Domain | Engine risk |
|---|---|---|---|---|---|
| TR-net-003 | Netcode | late-join full snapshot | ADR-0008 (playtest-gated) | Networking | HIGH |
| TR-net-005 | Netcode | Steam transport package | ADR-0008 (playtest-gated) | Transport | HIGH |
| TR-cam-002 | Camera | follow + aim bias, never detaches | deferred to implementation (architecture ruling) | Core | LOW |
| TR-move-002 | Movement | owner-auth predicted locomotion 6 u/s | ADR-0009 | Networking | HIGH |
| TR-move-003 | Movement | collision blocks never damages; layer matrix | ADR-0011 | Physics 2D | LOW |
| TR-move-004 | Movement | remotes interpolated (NetworkTransform.OnUpdate) | ADR-0009 | Networking | HIGH |
| TR-inv-002 | Inventory | CARRY owner-rail / STORAGE host-rail instances | ADR-0010 | Networking | HIGH |
| TR-inv-004 | Inventory | no world items; 2 equipment slots | ADR-0010 | Scope | LOW |
| TR-inv-007 new | Inventory | CARRY replication representation — undefined in the quick-spec and not flagged as deferred (only STORAGE is) | ADR-0010 | Networking | HIGH |
| TR-craft-008 | Crafting | craft beat client-predicted; host commit | ADR-0008 + crafting runtime ADR (see Required ADRs) | Networking | MEDIUM |
| TR-bb-009 | Base Building | ghost/grid overlay < 100 draw calls | ADR-0013 | Rendering | HIGH (Render Graph domain) |
| TR-ai-002 | Enemy AI | separation steering; aggro/leash | ADR-0012 | Core | LOW |
| TR-ui-001/002/003 | UI/HUD | HUD list; co-op affordances; reason strings | ADR-0014 | UI | MEDIUM (strict USS) |
| TR-tint-001/002/003 | Tide-Tint | sprite shader + globals; DriftLayer; Render-Graph-native future | ADR-0013 | Rendering | HIGH |
| TR-tint-004 new | Art/Netcode | flipbook animation via synced frameIndex (art bible §8.B.4) | ADR-0008 / ADR-0012 | Networking / Rendering | MEDIUM |
Cross-ADR Conflicts
C1 — Two callers of tide Advance · Pattern / Timing · CONCERN (blocking once code exists)
- ADR-0001 §4 claims:
Advanceis scheduled from anAwaitable.WaitForSecondsAsynctimer atNextBoundary()(and− PreloadLeadTime), with "ADR-0008 may override with an NGO-tick-aligned mechanism". - ADR-0015 §1 claims:
HostTickOrchestratoris the soleNetworkTickSystem.Ticksubscriber and calls tideAdvanceas step 1 of every host tick. ADR-0002 step 1 assumesAdvance"has already run this frame"; ADR-0005 hangsLandNetspawn/despawn andAppendoffAdvance's events (tide-edge carve-out). - Impact: a timer continuation has no documented ordering vs
NetworkTickSystem.Tick, so SoftClose-first is not guaranteed under the timer path; if both callers exist, a boundary tick callsAdvancetwice — idempotent for tint floats, not forNetworkObject.Spawn(). The specialist independently rated this the sharpest finding. - Resolution options: (1) recommended — ADR-0015 is recorded as amending ADR-0001 §4:
Advanceis called only by the orchestrator (per-tick, ≤ 50 µs); theAwaitabletimer survives solely as the Presentation-role /IPreloadSchedulepull wake-up, where clients genuinely need it. State the consequence for ADR-0004 §3 ("Persist after everyAdvancethat changest" → one dirty tide table per second while idle; either accept or rate-limit to boundaries). (2) keep the timer as the sole caller and have the orchestrator only read — loses same-tick guarantees; not recommended.
C2 — clientSeq dedup keyed by clientId vs PlayerKey · State · CONCERN
- ADR-0002 §8/§9 claims: the host keeps the last
clientSeqper client and dropsseq ≤ last; "rejoin replays are deduplicated". - ADR-0015 §1 claims:
clientIdis documented unstable across reconnects and may be reassigned; every session row is keyed byPlayerKey. - Impact: a rejoined client arrives with a fresh
clientIdand no seq history — the rejoin-replay claim does not hold as written; worse, a reassignedclientIdcould inherit another player's seq window. - Resolution options: (1) key the seq window by
PlayerKeyand have the owner persist its counter across rejoin; (2) scope dedup to one connection and drop the "rejoin replays" claim from §9.
C3 — Declared dependency cycles · Dependency · 🔴 flagged per protocol (substantively benign)
ADR-0002 → ADR-0006 → ADR-0003 → ADR-0002(ADR-0002 "Depends On" 0006 as "weak: one load-time value"; 0006 depends on 0003; 0003 depends on 0002).ADR-0006 ⇄ ADR-0004(mutual "Depends On" — 0006 amends 0004's tide block; 0004's gate reads 0006'sconfigVersion).- Resolution: reclassify ADR-0002→0006 as Related and ADR-0006→0004 as Amends. No decision changes.
C4 — ADR-0006 stage 4 "telegraph ≥ 400 ms is a NEW load rule with no combat AC" · Integration · NOTE
Combat AC-20 and the telegraphFloor knob ("hard floor 400 = the Rule 11 contract") already specify the load rejection. Correct the wording; Rule = combat.AC-20.
C5 — Unarmed SwingLanded · Integration · NOTE
Combat GDD (Crafting dependency row) says durability events are "never emitted for unarmed swings"; ADR-0003 emits SwingLanded with WeaponInstance = null for unarmed so presenters get DamageDealt. Keep the ADR shape; amend the GDD row to "Crafting ignores null instances".
C6 — Knob file homes · Data · NOTE
ADR-0005 places landTemplateBudgetMB in land-dock.json; ADR-0006 §1's land-dock.json row does not list it. PreloadLeadTime is owned by tide.json (ADR-0001) and only "cited" by land-dock.json — say "read-only reference".
C7 — TR-harv-001 assignment · Ownership · NOTE
architecture.md Required-ADR table assigns harvest-node replication to row 8; ADR-0005 and §2 assign it to ADR-0010 (QQ-13). Pick one (ADR-0010 recommended — it already owns the NodesReady gate).
Known conflict-prone areas: docs/consistency-failures.md does not exist; nothing to carry forward.
ADR Dependency Order (topologically sorted, after C3 relabel)
Foundation:
1. ADR-0001 Tide Scheduler pure assembly (no deps)
2. ADR-0002 Host commit model (0001; 0006 → Related)
3. ADR-0003 Assembly + purity boundaries (0001, 0002)
4. ADR-0006 Game data loading + validation (0001, 0003; 0004 → Amends)
5. ADR-0004 Save format + reconcile pipeline (0001, 0002, 0003, [0006 weak])
6. ADR-0005 Land lifecycle on Addressables (0001, 0002, 0003, 0004, 0006)
7. ADR-0007 Input action map (0003, 0004, 0006)
Core:
8. ADR-0015 Combat model (0002, 0003, 0006, 0007)
Not yet written:
ADR-0008 Netcode wire (friend-group playtest) → ADR-0009, 0010, 0011, 0012 → ADR-0013, 0014
⚠️ Every "Depends On" currently points at a Proposed ADR (all eight are Proposed). Per the TD sign-off condition, ADRs 1–7 must be Accepted before the first implementation story; this review recommends the amendments below land first (user ruling 2026-08-28: apply amendments 1–5, then flip).
GDD Revision Flags (Architecture → Design Feedback)
No GDD revision flags — no GDD assumption conflicts with verified engine behaviour. The specialist confirmed the tide purity model, the two rails, per-drain durability, SoftClose-first and the no-offline-simulation pillar are all deliverable on 6000.3 as designed. Systems index left unchanged.
Three doc-sync items (not design revisions):
| Doc | Item | Action |
|---|---|---|
| tide-scheduler.md | AC-44/45 header still says "advisory until the TD signs off"; OQ2 still open — ADR-0001 records the TD ruling as hard gates | mark AC-44/45 hard, close OQ2; if C1 resolves to per-tick Advance, the ±5 s tolerance becomes ±(5 s + 1 tick) — make that the GDD's number |
| save-persistence quick-spec | Rule 2 tide block lacks socketCount (ADR-0006 amended ADR-0004 §1 but not the quick-spec) | add the field |
| land-dock-undock quick-spec | Rule 9 "Concrete MB budget → asset pass" vs footer "→ ADR-0005 (64 MB)" | point Rule 9 at ADR-0005 |
Confirmed already landed: tide EC18 0.40; save Rule 3 + saveDebounceMs restatement; combat maxTargets / reviveReach rows + OQ10; movement Rule 5 downedCrawlSpeed carve-out; BB OQ6 resolved (gridCellSize = 1 u); art bible §8.A.5 / §8.B.1 atlas wording.
Engine Compatibility Issues
Engine Audit Results
Engine: Unity 6.3 LTS (6000.3) · ADRs with Engine Compatibility section: 8 / 8 · Version agreement: 8 / 8 Deprecated API references: none (every ServerRpc/ClientRpc/SetupRenderPasses/UnityEngine.Input/NetworkTransform.Update/Compatibility-Mode/Multiplay hit is in "don't use" context). Stale version references: none. Post-cutoff API conflicts between ADRs: none beyond C1.
Engine Specialist Findings (Opus, live-doc verified)
| # | ADR | Finding | Severity |
|---|---|---|---|
| E1 | 0001 vs 0015 | Two Advance callers — see C1 | CONCERN |
| E2 | 0007 §Post-Cutoff (a), §1 UI row | "UI Toolkit consumes the UI map via PanelEventHandler with no EventSystem" — wrong mechanism. PanelEventHandler (+PanelRaycaster) is created only when a uGUI EventSystem exists and then routes through InputSystemUIInputModule; the no-EventSystem path is UI Toolkit's built-in scene-independent default runtime event system. The outcome (no components needed; project-wide UI map consumed directly; map/action names must not change) is correct. | NOTE — fix the sentence |
| E3 | 0001 §4 | Whether Awaitable.WaitForSecondsAsync honours Time.timeScale is UNVERIFIED; scheduling a 600 s+ wall-clock lead on a possibly-scaled timer stalls under timeScale = 0. Timer = hint, UTC = truth. Moot if C1 resolves to per-tick Advance. | CONCERN |
| E4 | 0004 §4 | Application.wantsToQuit is a synchronous Func<bool>; "return false, await FlushAllAsync, re-Quit()" needs an explicit latch (or the second Quit() re-enters and loops) and an Awaitable continuation may never resume during player-loop teardown. Use the bounded blocking wait the ADR already permits, bound by flushDeadlineMs. Add EditorApplication.playModeStateChanged for Editor parity (wantsToQuit does not fire on Play-mode exit). | CONCERN |
| E5 | 0005 | NetworkList initial contents delivered before OnNetworkSpawn is not doc-attested (only NetworkVariable is) — keep the NodesReady fallback, mark UNVERIFIED not "specialist-confirmed". The NetworkPrefabHandler "~1 s" timeout figure is from the NGO 1.15 page; drop the number (the design does not depend on it). | NOTE |
| E6 | 0002 V5, 0015 V1 | "Multiplayer Play Mode 2.0.x unfit for automated tests" is UNVERIFIED (forum-sourced); MPPM @latest resolves to 3.0 on 6.3. The decision (two standalone builds under Network Simulator) is safe regardless. | NOTE |
| E7 | 0004 | "Newtonsoft is Unity-maintained" → Unity-distributed (v3.2.2 wrapping Json.NET 13.0.2); "STJ not in the 6.3 BCL" → "not shipped; vendoring required". Conclusions unchanged. | NOTE |
| E8 | 0015 | Burst MidpointRounding.AwayFromZero bug is staff-acknowledged (Apr 2023), not confirmed still-open on 6.3; the mitigation (Rules never references Burst) is costless — keep, reword. | NOTE |
| E9 | 0007 | InputFrame as a sealed record (class) ≈ 10 KB/s at 60 fps, not "~4 KB/s"; a record struct matches TideState/Cell/StrikeIntent. | NOTE |
| E10 | engine-reference | modules/networking.md teaches [ServerRpc]/[ClientRpc], Input.GetKey, and server-authoritative movement — three project-forbidden patterns; modules/input.md is 1.11-era. Both need a DEPRECATED — DO NOT USE banner, not just in-ADR disclaimers. | CONCERN (process) |
| E11 | process | No Unity project exists yet (Packages/manifest.json, packages-lock.json, ProjectSettings/, Assets/ absent); every package version in the ADRs is aspirational. ADR-0005's CI gate depends on a packages-lock.json pin — first implementation commit. | NOTE |
| E12 | 0002 | [Rpc(SendTo.Server, Delivery = RpcDelivery.Reliable, RequireOwnership = false)] — both properties documented independently; the combined literal is not doc-attested. Valid C#; flag as inference. | NOTE |
| E13 | 0005 V1 | AsyncOperationHandle.Task exists; no GetAwaiter on the handle itself in 2.x — await handle.Task is the only form; the Completed → MainThreadAsync fallback stays necessary. | NOTE |
Confirmed correct by live docs: NetworkTickSystem.Tick fires "before NetworkVariable changes are collected" (NGO 2.5 — the stage is still undocumented, so ADR-0002 Verification 1 stays open); reliable RPC order guaranteed per NetworkObject (NGO 2.6); NetworkVariable values delivered before OnNetworkSpawn; SubsystemRegistration precedes scene Awake; project-wide actions auto-instantiate and auto-enable; NetworkPrefabHandler asset-readiness warning is verbatim; all four Awaitable members incl. AwaitableCompletionSource<T>; all four Addressables 2.x members; Application.wantsToQuit semantics; File.Replace atomicity.
Architecture Document Coverage
- All 13 MVP systems + Tide-Tint appear in the layer map and module tables; data-flow §3.2 covers every cross-system event the GDDs define; no orphaned modules (
FeedbackPresentersis presentation-only and needs no GDD). - Stale: header "ADRs Referenced: ADR-0001 … ADR-0004, ADR-0006 — 10 remaining" (8 exist; 7 remain); "## ADR Audit … contains no ADRs" and "Traceability Coverage: 0 of 102" are pre-ADR text; the requirement count is 94 (now 111 registered), not 102; TR-craft-006 was assigned to no Required-ADR row.
.claude/docs/technical-preferences.md"Architecture Decisions Log: [No ADRs yet]" is stale.design/gdd/systems-index.mdhas two rows numbered 15 (Tide-Tint Polish and Base-Defense Events).tr-registry.yamlwas empty while ADR-0001/0002/0003/0004/0005/0006 Validation Criteria each say "/architecture-reviewshows TR-… covered" — registered by this review.
Verdict: CONCERNS
Foundation is fully covered and internally consistent in substance; no engine finding invalidates a decision. Off PASS because of: two real cross-ADR inconsistencies (C1, C2), a declared dependency cycle to relabel (C3), three engine CONCERNs (E3/E4 implementation-shaped, E10 process), and 19 gaps — all Core/Feature/Presentation, all assigned to ADRs 0008–0014 whose wire halves are gated on the friend-group playtest by CD ruling. Not FAIL: no Foundation requirement is uncovered and no conflict requires re-deciding anything.
Blocking issues
None at FAIL level. Required before flipping ADR-0001–0007 + 0015 to Accepted (user ruling 2026-08-28):
- ADR-0001 §4 ← ADR-0015 amendment: single
Advancecaller; timer = presentation/preload wake-up only (C1 / E1 / E3); state the ADR-0004 §3 tide-dirty consequence. - ADR-0002 §8/§9: dedup keyed by
PlayerKey(C2). - ADR-0002 / ADR-0006 dependency fields relabelled (C3).
- ADR-0007: fix the
PanelEventHandlersentence (E2). ADR-0004 §4:wantsToQuitlatch + bounded blocking wait + Editor parity (E4). - ADR-0006 stage 4 wording (C4); ADR-0005
NetworkList/ "~1 s" hedges (E5); ADR-0004 library wording (E7).
Required ADRs (most foundational first)
- ADR-0008 Netcode wire — playtest-gated; resolves 9 ⚠️ + 2 ❌ + TR-tide-013 + TR-tint-004.
- ADR-0010 Inventory replication — 5 inventory items incl. the newly-surfaced CARRY representation (TR-inv-007),
transferBatchWindow, harvest-nodeNetworkList. Recommend extending its charter — or adding Required-ADR row 16 "Crafting commit, durability drain andInstanceMetadata" — because TR-craft-003/004/006/008 currently have no owning row. - ADR-0009 Player transform authority — TR-move-002/004/005, TR-combat-008, TR-bb-007 write mode; pre-playtest snap check.
- ADR-0011 Structure registry + wall colliders (TR-bb-002/005/007, TR-move-003).
- ADR-0012 Enemy simulation (TR-ai-001/002/004, TR-ldu-005).
- ADR-0013 Tide-tint render pipeline — pulled to MVP by the index; TR-tint-001..003, TR-bb-009, TR-cam-001.
- ADR-0014 UI stack (TR-ui-001..004, TR-combat-013).
Pre-gate checklist (checked 2026-08-28)
- ❌
tests/unit/,tests/integration/— run/test-setup - ❌
.github/workflows/tests.yml— run/test-setup - ❌
design/accessibility-requirements.md— run/ux-design - ❌
design/ux/interaction-patterns.md— run/ux-design
Re-run /architecture-review after each new ADR to verify coverage improves.
History
| Date | Verdict | Covered / Partial / Gap | Notes |
|---|---|---|---|
| 2026-08-28 | CONCERNS | 64 / 28 / 19 of 111 | First review; 8 ADRs Proposed; TR registry v2 created |
| 2026-08-28 (same session) | CONCERNS → amendments applied | — | Amendments 1–5 applied to ADR-0001/0002/0004/0005/0006/0007 (+ ADR-0015 amendment table); all eight ADRs flipped to Accepted; architecture.md header/audit and technical-preferences.md ADR log synced. Open: E10 engine-reference banners, the three GDD doc-sync items, ADR-0008–0014. |