Pushes current branch and creates or updates a PR without ever
merging into main. Prevents accidental PR merges by restricting
the skill to branch-side operations only.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Scaffolds the full content directory structure per D-057 spec:
district-as-atomic-pack layout with Sova Transit as the first
district. Includes 17 NPC stubs, 3 locations, 5 triangles,
9 dialogue pools, 8 monologue pools, routines, 7 factions,
7 knowledge catalogs, 8 enum definitions, and global region.
Implements ticket #385.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace binary paused flag with TickRate enum (Full/Half/Paused)
per D-052. Full rate advances every frame, Half every 2 frames
via fractional accumulation, Paused blocks all advances. Add
SetTickRate player action for client-driven rate changes.
Implements ticket #406.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement compute_nearby_interactions system that detects entities
within close (≤2) and mid (≤5) Manhattan distance, computes
available verbs per D-060 spec. NPCs get Talk+Observe at close
range, Observe-only at mid range; PersonOfInterest flips priority.
Objects get Examine. Results populate nearby_interactions[] on
ObserverSnapshot v4. Bump protocol version 3→4. Implements #404.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The PR implements tick rate scaling, proximity interactions, and content schemas with generally solid code quality. However, there are critical issues around system ordering, wire format consistency, and error handling that must be fixed before merge.
#
File
Severity
Issue
1
observer.rs / interaction.rs
critical
NearbyInteractionBuffer.interactions is pub and cleared at start of compute_nearby_interactions -- if system ordering is violated, snapshot gets stale/empty data. Make field private with accessor.
2
input.rs:88-89
critical
apply_move logs warning if player entity missing but continues. Missing player is a fatal invariant -- should panic, not warn silently.
3
types.rs:29-30
critical
#[serde(default)] on nearby_interactions hides serialization bugs. If v4 snapshot is missing the field, client gets empty data without error. Remove the default.
4
observer.rs:172-175
critical
Remembered entities use stable_id.0 as fallback entity_id, but visible entities use Entity::to_bits(). ID spaces can collide. Wire entity_id should always be StableId.
5
bridge/mod.rs
critical
No explicit .after() between compute_nearby_interactions and send_bridge_snapshot in BridgePlugin schedule.
6
interaction.rs:73
warning
verbs.sort_by_key with no secondary key -- equal priorities produce undefined ordering.
7
interaction.rs constants
warning
CLOSE_RANGE and MID_RANGE are pub -- should be pub(crate) or config resource.
8
types.rs:108
warning
NearbyInteraction.distance is f32 but manhattan_distance() returns u32. Type mismatch.
9
observer.rs:143-145
warning
Remembered entity filter chain is fragile -- 6 sequential if/continue checks. Extract to helper.
10
observer.rs:165
warning
No debug assertion that last_observed_tick <= current tick.
11
knowledge/events.rs:33
warning
Unregistered target entities silently skipped. Should be error! or debug_assert!.
12
types.rs:75
warning
Entity::to_bits() is bevy-version-dependent. Add roundtrip test.
13
observer.rs:172-175
warning
Showing remembered entity for despawned entity is a client footgun.
14
time.rs (missing test)
warning
No test for floating point drift over 10,000+ frames at Half rate.
15
interaction.rs (missing test)
warning
No test for POI NPC at mid-range, equidistant NPCs, or 50+ entities.
16
serialization.rs:90
warning
Fixture deserialize test doesn't assert version == 4.
17
observer.rs
suggestion
320 lines, one 150-line function. Split into helpers.
18
types.rs
suggestion
Will grow rapidly. Split at 200 lines.
19
_schema/
suggestion
No validation tooling. Add make validate-content.
20
observer.rs:202
suggestion
interaction_buffer.interactions.clone() every frame. Use .take() or Arc.
Tyre (Architecture): REQUEST_CHANGES
The code quality is high, but the architecture is making promises it can't keep. D-010 (multiplayer-ready) and D-017 (perception modes) will break the current design. The interaction system is exemplary. But observer.rs is a growing monolith, snapshot versioning is fragile, and per-entity state is hiding in global Resources.
#
File
Severity
Issue
1
types.rs ObserverSnapshot
critical
Snapshot versioning via version: u8 + #[serde(default)] is a time bomb. No per-version deserialization, no protocol negotiation. Violates D-020. Need version-tagged enum.
2
observer.rs compute_observer_snapshot
critical
Function does 6 things. D-017 perception modes will explode this. Split into single-responsibility functions.
3
interaction.rs NearbyInteractionBuffer
critical
Global Resource for per-observer data. Breaks with 2+ players (D-009). Replace with per-entity Component.
4
_schema/ cross-file validation
critical
No cross-file constraints. Routine refs npc:kael but nothing validates kael.yaml exists. Need Rust ContentValidator.
Split compute_observer_snapshot into composable pipeline steps
Replace NearbyInteractionBuffer Resource with per-entity Component (multiplayer-ready)
Wire-format entity_id consistency (always StableId, never Entity::to_bits)
System ordering guarantees between interaction computation and snapshot send
Content cross-file validation
Should fix:
Decouple interaction system from knowledge graph direct queries
Remove redundant paused / tick_rate in GameTime
Interaction system LOS integration (or remove LOS claims from spec comments)
## Review: server -> main (type: code)
### Hoshe (Code Quality): REQUEST_CHANGES
The PR implements tick rate scaling, proximity interactions, and content schemas with generally solid code quality. However, there are critical issues around system ordering, wire format consistency, and error handling that must be fixed before merge.
| # | File | Severity | Issue |
|---|------|----------|-------|
| 1 | observer.rs / interaction.rs | critical | `NearbyInteractionBuffer.interactions` is pub and cleared at start of `compute_nearby_interactions` -- if system ordering is violated, snapshot gets stale/empty data. Make field private with accessor. |
| 2 | input.rs:88-89 | critical | `apply_move` logs warning if player entity missing but continues. Missing player is a fatal invariant -- should panic, not warn silently. |
| 3 | types.rs:29-30 | critical | `#[serde(default)]` on `nearby_interactions` hides serialization bugs. If v4 snapshot is missing the field, client gets empty data without error. Remove the default. |
| 4 | observer.rs:172-175 | critical | Remembered entities use `stable_id.0` as fallback entity_id, but visible entities use `Entity::to_bits()`. ID spaces can collide. Wire entity_id should always be StableId. |
| 5 | bridge/mod.rs | critical | No explicit `.after()` between `compute_nearby_interactions` and `send_bridge_snapshot` in BridgePlugin schedule. |
| 6 | interaction.rs:73 | warning | `verbs.sort_by_key` with no secondary key -- equal priorities produce undefined ordering. |
| 7 | interaction.rs constants | warning | `CLOSE_RANGE` and `MID_RANGE` are `pub` -- should be `pub(crate)` or config resource. |
| 8 | types.rs:108 | warning | `NearbyInteraction.distance` is `f32` but `manhattan_distance()` returns `u32`. Type mismatch. |
| 9 | observer.rs:143-145 | warning | Remembered entity filter chain is fragile -- 6 sequential if/continue checks. Extract to helper. |
| 10 | observer.rs:165 | warning | No debug assertion that `last_observed_tick <= current tick`. |
| 11 | knowledge/events.rs:33 | warning | Unregistered target entities silently skipped. Should be `error!` or `debug_assert!`. |
| 12 | types.rs:75 | warning | `Entity::to_bits()` is bevy-version-dependent. Add roundtrip test. |
| 13 | observer.rs:172-175 | warning | Showing remembered entity for despawned entity is a client footgun. |
| 14 | time.rs (missing test) | warning | No test for floating point drift over 10,000+ frames at Half rate. |
| 15 | interaction.rs (missing test) | warning | No test for POI NPC at mid-range, equidistant NPCs, or 50+ entities. |
| 16 | serialization.rs:90 | warning | Fixture deserialize test doesn't assert version == 4. |
| 17 | observer.rs | suggestion | 320 lines, one 150-line function. Split into helpers. |
| 18 | types.rs | suggestion | Will grow rapidly. Split at 200 lines. |
| 19 | _schema/ | suggestion | No validation tooling. Add `make validate-content`. |
| 20 | observer.rs:202 | suggestion | `interaction_buffer.interactions.clone()` every frame. Use `.take()` or `Arc`. |
### Tyre (Architecture): REQUEST_CHANGES
The code quality is high, but the architecture is making promises it can't keep. D-010 (multiplayer-ready) and D-017 (perception modes) will break the current design. The interaction system is exemplary. But observer.rs is a growing monolith, snapshot versioning is fragile, and per-entity state is hiding in global Resources.
| # | File | Severity | Issue |
|---|------|----------|-------|
| 1 | types.rs ObserverSnapshot | critical | Snapshot versioning via `version: u8` + `#[serde(default)]` is a time bomb. No per-version deserialization, no protocol negotiation. Violates D-020. Need version-tagged enum. |
| 2 | observer.rs compute_observer_snapshot | critical | Function does 6 things. D-017 perception modes will explode this. Split into single-responsibility functions. |
| 3 | interaction.rs NearbyInteractionBuffer | critical | Global `Resource` for per-observer data. Breaks with 2+ players (D-009). Replace with per-entity `Component`. |
| 4 | _schema/ cross-file validation | critical | No cross-file constraints. Routine refs npc:kael but nothing validates kael.yaml exists. Need Rust `ContentValidator`. |
| 5 | interaction.rs:3,50-51 | warning | Interaction system directly queries KnowledgeGraph. Simulation reading perception = boundary violation. |
| 6 | observer.rs (entire file) | warning | Missing `PerceptionQuery` trait abstraction. Hardcoded for natural vision. D-017 will force duplication. |
| 7 | interaction.rs tests | warning | No LOS occlusion tests. System claims sightline check but doesn't query FOV. |
| 8 | types.rs GameTime | suggestion | `paused` and `tick_rate` are redundant. Remove one. |
| 9 | time.rs:47-66 | suggestion | Fractional accumulation undocumented. Add explanation. |
| 10 | content directory | suggestion | District name in path, filename, AND YAML content. Pick one source of truth. |
### Verdict: CHANGES REQUESTED
**Both reviewers agree: solid implementation quality, architectural boundaries need work.**
**Must fix (blocking):**
- Snapshot versioning strategy (version-tagged enum, not flat struct + serde defaults)
- Split `compute_observer_snapshot` into composable pipeline steps
- Replace `NearbyInteractionBuffer` Resource with per-entity Component (multiplayer-ready)
- Wire-format entity_id consistency (always StableId, never Entity::to_bits)
- System ordering guarantees between interaction computation and snapshot send
- Content cross-file validation
**Should fix:**
- Decouple interaction system from knowledge graph direct queries
- Remove redundant `paused` / `tick_rate` in GameTime
- Interaction system LOS integration (or remove LOS claims from spec comments)
Re-added system/station/district fields to district.yaml that were
incorrectly removed during Tyre10 cleanup. These carry hierarchical
context (planet, station), not redundant identity. Made canonical_id
optional in schema since it's derived from directory path at load time.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The current content/districts/{slug}/ flat structure works for v0.1 but has critical structural weaknesses at galaxy scale (300 planets, 1000s of districts). Changing this after 100+ districts exist is 10x the cost. Fix now.
Failure Scenarios at Scale
#
Problem
Scale Threshold
Severity
1
Flat 1000-directory explosion — content/districts/ with 1000 sibling dirs x 35 files = 35,000+ files. Git status 10-30s, editor indexing thrashes, CI checkout doubles.
~200 districts
critical
2
ID-to-path mismatch — Canonical IDs are {system}.{station}.{district} but paths are flat districts/{slug}/. "All districts in Krenn system" requires parsing 1000 YAMLs instead of ls systems/krenn/.
~50 districts
critical
3
NPC slug collisions — npc:{slug} is a global namespace but NPCs live in district-scoped dirs. Two districts with npcs/dock-worker.yaml = ID collision. No enforcement exists.
~50 districts
critical
4
content.yaml manifest bloat — 1000 districts = 3000+ lines. Every branch that adds a district touches this file = constant merge conflicts.
~100 districts
high
5
Procedural gen undefined — D-023 Tier 2/3 content is templated/procedural. Where do generated districts live? YAML on disk = file explosion. In-memory only = dual content systems.
Sprint 5+
critical
6
Cross-district references unspecified — D-025 says NPCs can hold roles in multiple social sites. If NPC in District A relates to NPC in District B, where does that live? No ownership model defined.
~20 districts
high
7
Git perf degrades catastrophically — 35,000 small YAML files. Branch touching 50 districts (1750 files) takes minutes to rebase.
~200 districts
critical
8
D-003 multi-campaign incompatible — No campaign namespace in directory structure. Adding "Foundation Saga" campaign = mixed with Commonwealth content.
Filesystem matches canonical IDs — directory walk replaces YAML parsing for spatial queries
Git performance: 10-20 stations/system, 5-10 districts/station — no flat explosion
Multi-campaign ready (D-003)
Content loader scans via glob systems/**/districts/ — no per-district manifest entry
Required: NPC ID Namespace Strategy
Recommended: district-scoped slugs — npc:sova-transit.dock-worker. File stays npcs/dock-worker.yaml, loader prepends district context. Cross-district refs use full ID. Within-district refs use short form.
Replace flat content/districts/ with hierarchical campaign/system/
station/district structure. Path mirrors canonical IDs, enables
glob-based discovery, and is multi-campaign/DLC ready.
- git mv 46 files preserving history
- New metadata: campaign.yaml, system.yaml, station.yaml
- Rewrite content.yaml with glob-based district discovery
- Add campaign, system, station JSON schemas
- Update district schema: hierarchy fields derived from path
- Update npc-profile schema: accept district-scoped IDs
- Add TODO to 17 dialogue/monologue pool files for generator revision
- Update _meta/README.md with new hierarchy documentation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Delete generate_snapshot from bridge/mod.rs — superseded by
perception::observer::compute_observer_snapshot. Not referenced
in any schedule or test.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace silent Entity::to_bits() fallbacks with tracing::error in
observer.rs and interaction.rs (makes unregistered entities loud)
- Add PROTOCOL_VERSION constant to types.rs, use in observer snapshot
- Document single-observer assumption on NearbyInteractionBuffer
- Document proximity-only (no LOS) limitation on compute_nearby_interactions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
FIXED — test added but doesn't assert priority value
16
Fixture version assertion
FIXED — asserts version == 4
17
observer.rs file size
WORSE — now 798 lines (was ~320)
19
Content validation tooling
NOT FIXED — no make validate-content
20
interaction_buffer.clone()
FIXED — uses take()
Remaining Issues:
#
File
Severity
Issue
1
server/tests/serialization.rs
critical
No Entity::to_bits() roundtrip test. Bevy version upgrade could silently break wire IDs.
2
Makefile
warning
No make validate-content target. JSON Schema files exist but nothing validates content.
3
observer.rs
warning
798 lines — 2.5x the previous concern. Extract remembered_entities.rs or move tests to separate file.
4
interaction.rs test
suggestion
poi_npc_at_mid_range_gets_observe_only should also assert priority value.
Tyre (Architecture): REQUEST_CHANGES
Summary: ~70% of feedback addressed. Content restructure is excellent — hierarchical directory matches canonical IDs, glob discovery eliminates manifest bloat. Three critical architectural time bombs remain: snapshot versioning is cosmetic (no enforcement), observer.rs is still a monolith, NearbyInteractionBuffer is still a global Resource.
Per-entity Component attached to PlayerCharacter instead of global
Resource. Makes the interaction buffer multiplayer-ready (D-009) —
each observer gets their own buffer without pipeline refactoring.
Updated all 8 files touching the buffer: system signatures, player
spawn bundles, and ~30 test spawn sites.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split observer/mod.rs (820 lines) into production code (244 lines) and
tests (480 lines). Reduces module size per Hoshe #3 review item.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Entity::to_bits() roundtrip test guards against bevy version changes
silently breaking wire IDs (Hoshe #1)
- PROTOCOL_VERSION constant used in test helpers instead of hardcoded 4
- TickRate switch mid-accumulation test verifies Half→Full→Paused→Half
transitions preserve accumulator state correctly (Tyre N3)
- POI mid-range test now asserts priority=1 (Hoshe #4)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Half->Full->Paused->Half with accumulator carry-over
10
Error-logging-without-recovery
PARTIALLY
debug_assert + error! pattern — loud but still continues
11
POI priority assertion
NOT FIXED
Tests check verb kind, not priority field values
12
observer.rs file size
PARTIALLY
Tests extracted (574 LOC), mod.rs still 244 LOC
Remaining Issues:
#
File
Severity
Issue
1
observer/mod.rs
critical
Pipeline decomposition not done. 130-line function mixing 7 responsibilities. D-017 modes will force duplication.
2
(none)
critical
No PerceptionQuery trait abstraction for D-017 mode switching
3
interaction.rs:166-189
suggestion
poi_npc_observe_takes_priority doesn't assert priority field values (1 vs 2)
Tyre (Architecture): REQUEST_CHANGES
Summary: ~60% addressed. NearbyInteractionBuffer refactor, content validation, NPC namespace, error handling, TickRate tests all solid. Three critical architectural issues remain: snapshot versioning has no enforcement, observer is still a monolith, and interaction system violates architecture boundaries by reading KnowledgeGraph in simulation phase.
Punch List Scorecard:
#
Item
Status
Assessment
1
Snapshot versioning enforcement
PARTIALLY
PROTOCOL_VERSION constant exists but client doesn't check it. No rejection mechanism.
2
Observer monolith decomposition
NOT FIXED
compute_observer_snapshot still 130 lines doing 7 steps. Only tests extracted.
3
NearbyInteractionBuffer -> Component
FIXED
Per-entity Component. Multiplayer-ready. Solid.
4
Content validation CLI
FIXED
validate-content script with schema mapping. make target exists.
5
Interaction/Knowledge boundary
STILL VIOLATED
compute_nearby_interactions reads KnowledgeGraph — simulation reading perception state
6
PerceptionQuery trait
NOT FIXED
No trait. Hardcoded for natural vision.
7
NPC ID namespace
FIXED
npc:district.slug pattern in schema.
8
PROTOCOL_VERSION cosmetic
PARTIALLY
Constant with docs but zero enforcement. Client doesn't check.
9
Error logging without recovery
FIXED
debug_assert + tracing::error — loud in dev, survives in prod.
10
TickRate switch test
FIXED
Comprehensive test covering all transitions.
Critical Blockers:
#
Issue
Impact
1
Snapshot versioning cosmetic
Silent data corruption on protocol drift. Client MUST validate version.
2
Observer monolith
D-017 perception modes will force 130-line function duplication per mode.
New concern from Tyre: The interaction system's KnowledgeGraph dependency creates a cross-phase data flow violation. Relationship lookup should move to compute_observer_snapshot (which already reads KG), not live in simulation-phase code.
Extract visibility geometry into a separate system behind a
PerceptionQuery trait, enabling D-017 perception mode swapping.
Two-stage pipeline: compute_visibility_geometry writes to
VisibilityGeometry resource, compute_observer_snapshot reads it.
Remove KnowledgeGraph from compute_nearby_interactions (simulation
phase boundary violation). Verb availability stays in simulation;
POI-based priority adjustment moves to observer via
apply_poi_verb_priority helper.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Client now rejects snapshots where version != PROTOCOL_VERSION (4).
Returns null with error log on mismatch. Test snapshot updated to
use Protocol.PROTOCOL_VERSION and v4 game_time format (tick_rate
replaces paused field).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary: All 3 Round 3 blockers fixed correctly. Observer pipeline genuinely decomposed into two-stage system. Client enforces PROTOCOL_VERSION with fail-fast rejection. Interaction system no longer reads KnowledgeGraph. Bonus: PerceptionQuery trait abstraction and POI priority test with kind+value assertions.
mod.rs 273 LOC + query.rs 103 LOC. Tests in separate 617 LOC file.
No new issues found. No regressions detected.
Tyre (Architecture): APPROVE
Summary: All three blockers architecturally resolved, not just cosmetically patched. PerceptionQuery trait is well-designed for D-017 mode swapping. Phase boundary between simulation and perception is now correct per D-010. System ordering is explicit and correct. Foundation is multiplayer-ready, perception-mode-ready, and testable.
#
Blocker
Status
Architectural Assessment
1
Snapshot versioning
FIXED
Client enforces version check before field access. Fail-fast. D-020 compliant.
2
Observer decomposition
FIXED
Two-stage pipeline via VisibilityGeometry resource. PerceptionQuery trait enables D-017 mode swapping without touching entity filtering.
3
Interaction/KG boundary
FIXED
Simulation determines verb availability by proximity only. Observer applies POI priority from knowledge graph. Phase boundary correct per D-010.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Test plan
🤖 Generated with Claude Code
Review: server -> main (type: code)
Hoshe (Code Quality): REQUEST_CHANGES
The PR implements tick rate scaling, proximity interactions, and content schemas with generally solid code quality. However, there are critical issues around system ordering, wire format consistency, and error handling that must be fixed before merge.
NearbyInteractionBuffer.interactionsis pub and cleared at start ofcompute_nearby_interactions-- if system ordering is violated, snapshot gets stale/empty data. Make field private with accessor.apply_movelogs warning if player entity missing but continues. Missing player is a fatal invariant -- should panic, not warn silently.#[serde(default)]onnearby_interactionshides serialization bugs. If v4 snapshot is missing the field, client gets empty data without error. Remove the default.stable_id.0as fallback entity_id, but visible entities useEntity::to_bits(). ID spaces can collide. Wire entity_id should always be StableId..after()betweencompute_nearby_interactionsandsend_bridge_snapshotin BridgePlugin schedule.verbs.sort_by_keywith no secondary key -- equal priorities produce undefined ordering.CLOSE_RANGEandMID_RANGEarepub-- should bepub(crate)or config resource.NearbyInteraction.distanceisf32butmanhattan_distance()returnsu32. Type mismatch.last_observed_tick <= current tick.error!ordebug_assert!.Entity::to_bits()is bevy-version-dependent. Add roundtrip test.make validate-content.interaction_buffer.interactions.clone()every frame. Use.take()orArc.Tyre (Architecture): REQUEST_CHANGES
The code quality is high, but the architecture is making promises it can't keep. D-010 (multiplayer-ready) and D-017 (perception modes) will break the current design. The interaction system is exemplary. But observer.rs is a growing monolith, snapshot versioning is fragile, and per-entity state is hiding in global Resources.
version: u8+#[serde(default)]is a time bomb. No per-version deserialization, no protocol negotiation. Violates D-020. Need version-tagged enum.Resourcefor per-observer data. Breaks with 2+ players (D-009). Replace with per-entityComponent.ContentValidator.PerceptionQuerytrait abstraction. Hardcoded for natural vision. D-017 will force duplication.pausedandtick_rateare redundant. Remove one.Verdict: CHANGES REQUESTED
Both reviewers agree: solid implementation quality, architectural boundaries need work.
Must fix (blocking):
compute_observer_snapshotinto composable pipeline stepsNearbyInteractionBufferResource with per-entity Component (multiplayer-ready)Should fix:
paused/tick_ratein GameTimeSupplementary Review: Content Directory Scalability (Tyre, Architecture)
Verdict: REQUEST_CHANGES
The current
content/districts/{slug}/flat structure works for v0.1 but has critical structural weaknesses at galaxy scale (300 planets, 1000s of districts). Changing this after 100+ districts exist is 10x the cost. Fix now.Failure Scenarios at Scale
content/districts/with 1000 sibling dirs x 35 files = 35,000+ files. Git status 10-30s, editor indexing thrashes, CI checkout doubles.{system}.{station}.{district}but paths are flatdistricts/{slug}/. "All districts in Krenn system" requires parsing 1000 YAMLs instead ofls systems/krenn/.npc:{slug}is a global namespace but NPCs live in district-scoped dirs. Two districts withnpcs/dock-worker.yaml= ID collision. No enforcement exists.Recommended Fix: Hierarchical Directory Structure
Before (current):
After (proposed):
Path derivation:
campaigns/{campaign}/systems/{system}/stations/{station}/districts/{district}/systems/**/districts/— no per-district manifest entryRequired: NPC ID Namespace Strategy
Recommended: district-scoped slugs —
npc:sova-transit.dock-worker. File staysnpcs/dock-worker.yaml, loader prepends district context. Cross-district refs use full ID. Within-district refs use short form.Schema change:
"^npc:[a-z][a-z0-9-]+(\\.[a-z][a-z0-9-]+)?$"Required: Replace content.yaml with Discovery Convention
Loader scans for
district.yamlunder glob. No per-district listing = no merge conflicts, no manifest bloat.Required: Content Validation CLI
Even simple: walk tree, validate JSONSchema, check cross-references (no dangling
npc:*), detect slug collisions. Expand to full build tool later.Can Wait (Sprint 5+)
Cost Estimate
After 50 districts: 10x the cost (merge conflicts, author retraining, tool updates).
Re-Review Round 2: server -> main. See full review below.
Re-Review: server -> main (type: code) — Round 2
PR #16: feat(server): sprint 3 — tick rate, interactions, content skeleton
7 fix commits since initial review.
Hoshe (Code Quality): REQUEST_CHANGES
Summary: 16/20 known issues fixed correctly. Critical improvements: private interaction buffer with
take(), panic on missing player,pub(crate)visibility, u32 distance type, deterministic verb sorting, drift test, debug assertions.Previous Issues Status:
take().expect()+ test#[serde(default)]on nearby_interactions(priority, kind as u8)pub(crate)error!+debug_assert!pausedversion == 4make validate-contenttake()Remaining Issues:
Entity::to_bits()roundtrip test. Bevy version upgrade could silently break wire IDs.make validate-contenttarget. JSON Schema files exist but nothing validates content.remembered_entities.rsor move tests to separate file.poi_npc_at_mid_range_gets_observe_onlyshould also assert priority value.Tyre (Architecture): REQUEST_CHANGES
Summary: ~70% of feedback addressed. Content restructure is excellent — hierarchical directory matches canonical IDs, glob discovery eliminates manifest bloat. Three critical architectural time bombs remain: snapshot versioning is cosmetic (no enforcement), observer.rs is still a monolith, NearbyInteractionBuffer is still a global Resource.
Previous Issues Status:
New Architectural Concerns:
Tyre's Recommendation — Path C (partial fix, 2-3 dev-days):
Verdict: CHANGES REQUESTED (Round 2)
What's improved: Content restructure (excellent), code quality fixes (16/20 verified), test coverage, system ordering, dead code removal.
What's still blocking:
make validate-contenttarget (Hoshe) — 1 dev-dayProject lead decision needed: Accept Tyre's Path C (fix #2 + #3, defer observer monolith) or Path B (document all as v0.1 debt with tickets)?
Re-Review Round 3: server -> main (type: code)
5 new commits since Round 2. Evaluating full punch list.
Hoshe (Code Quality): REQUEST_CHANGES
Summary: 5/12 fully fixed, 3 partially fixed, 4 unfixed. Strong execution on tactical fixes (roundtrip test, Buffer refactor, validation tooling, TickRate tests). Observer pipeline decomposition and PerceptionQuery trait still missing.
Punch List Scorecard:
Remaining Issues:
Tyre (Architecture): REQUEST_CHANGES
Summary: ~60% addressed. NearbyInteractionBuffer refactor, content validation, NPC namespace, error handling, TickRate tests all solid. Three critical architectural issues remain: snapshot versioning has no enforcement, observer is still a monolith, and interaction system violates architecture boundaries by reading KnowledgeGraph in simulation phase.
Punch List Scorecard:
Critical Blockers:
New concern from Tyre: The interaction system's
KnowledgeGraphdependency creates a cross-phase data flow violation. Relationship lookup should move tocompute_observer_snapshot(which already reads KG), not live in simulation-phase code.Verdict: CHANGES REQUESTED (Round 3)
What's fixed and solid:
Still blocking (both reviewers agree):
Hoshe also flags: PerceptionQuery trait (not blocking per Tyre — can defer to Sprint 4 pre-D-017)
Estimated remaining work: ~7-8 hours focused effort for the 3 blockers.
Re-Review Round 4: server -> main (type: code)
2 new commits addressing final 3 blockers.
Hoshe (Code Quality): APPROVE
Summary: All 3 Round 3 blockers fixed correctly. Observer pipeline genuinely decomposed into two-stage system. Client enforces PROTOCOL_VERSION with fail-fast rejection. Interaction system no longer reads KnowledgeGraph. Bonus: PerceptionQuery trait abstraction and POI priority test with kind+value assertions.
compute_visibility_geometry->compute_observer_snapshot. Helper functions extracted.No new issues found. No regressions detected.
Tyre (Architecture): APPROVE
Summary: All three blockers architecturally resolved, not just cosmetically patched. PerceptionQuery trait is well-designed for D-017 mode swapping. Phase boundary between simulation and perception is now correct per D-010. System ordering is explicit and correct. Foundation is multiplayer-ready, perception-mode-ready, and testable.
D-017 readiness: Thermal vision implements PerceptionQuery with different geometry, observer pipeline unchanged.
D-009 readiness: Per-entity Component buffer, per-observer geometry. Multi-observer ready.
D-030 readiness: Simulation no longer reads perception state. Deterministic replay unbroken.
No new architectural concerns.
Verdict: APPROVED
Both reviewers approve. All blockers from Rounds 1-4 resolved. PR ready for merge.
Final tally across 4 review rounds:
Architecture highlights:
Pull request closed