#679 — Location-to-culture resolver. New server/src/knowledge/culture.rs, CultureResolver reading systems.db (read-only, mutex-wrapped), 3-pass system→body→station lookup, 8 unit tests. Unblocks client #680.
NPC cleanup (narrow scope per R-012):
#842 — Audited server/src/npc/, content/global/, and the conversation runtime to identify what is actually dead (D-078 only) vs. what is live (D-024 entity model, D-142 zone-types). Audit drove #848.
Surviving NPC component types (NpcName, NpcColorIndex, NpcConversation) relocated to simulation/npc_components.rs — still used by D-080 knowledge propagation
PROTOCOL_VERSION 22 → 23 (wire-format change)
Annotated D-061 and D-080 cross-refs in decisions/perception.md
server/src/npc/ and server/content/global/ are untouched (verified — those are live systems).
CultureResolver with Arc<Mutex<Connection>> over systems.db (SQLITE_OPEN_READ_ONLY).
3-pass lookup: system_id → body_id (COALESCE parent fallback) → station_id.
CultureResolverResource registered in main.rs with graceful warn-on-missing.
BookmarkRegistry.build_catalog() uses resolver for allowed_locations_cultures.
8 unit tests including concurrent safety. SQLite fixture at
server/src/knowledge/fixtures/culture_test.db.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove overheard.ron (1629 lines) and overheard.yaml.deprecated. D-078 overheard
system is retired — the content and production pipeline for ambient NPC dialogue
is deferred until the world is walkable (Phase 6). Deep module interdependencies
(perception, simulation, bridge) mean the server-side plumbing stays in place;
only the content files with no live consumers are removed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Per R-012: delete conversation.rs, both overheard content files, and
remove all 6 wire-up points (social_plugin, bridge/types, monologue,
voice/integration). Protocol version 22 → 23. Scope confirmed by
#842 audit — npc/ and content/global/ untouched. Surviving NPC
components (NpcName, NpcColorIndex, NpcConversation) migrated to
simulation/npc_components.rs for use by D-080 knowledge propagation.
Also applies pre-existing cargo fmt debt (names.rs and 4 others).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PR test plan: 871 tests pass (3 pre-existing save_io failures unrelated). cargo deny check claimed passing — see blocker #1.
Runtime smoke test: not mentioned in PR description — process gap.
Hoshe (QA): REQUEST_CHANGES
2 blockers, 3 issues, 1 nit. Key findings: cargo deny check is configured but never invoked by any Makefile path — #726 shipped dead config; ConfirmBookmark silently overwrites an already-confirmed selection with no guard.
Tyre (architecture): REQUEST_CHANGES
2 blockers, 5 issues, 3 nits. Architectural scaffolding is sound (bookmark spec honored, culture resolver correctly opens read-only with mutex, generate_brands honors source-canonical discipline), but two design-contract gaps block: D-080 knowledge propagation has no production trigger after D-078 retirement (decision doc aspirational), and SelectedBookmark isn't Serialize despite spec §4.4 promising save/load persistence.
Key cross-cutting finding (good news)
generate_brands reads systems.db but does NOT call generate_atlas.py — verified by Tyre. The merge-integration safety guard we've been planning is simpler than feared: make economy-db after server merges only touches brand_products / brand_inputs tables, not atlas_*. Copy's hand-refined atlas is not at risk from server's post-merge regen. The /tmp/atlas-copy-baseline.sql diff will confirm this empirically when the merge happens.
Consolidated comments
#
File
Sev
Issue
1
Makefile (audit target)
blocker
cargo deny check not wired anywhere — pre-pr-server depends on audit which runs cargo audit, not cargo deny. server/deny.toml shipped by #726 is dead config. Add a deny target and wire it into pre-pr-server.
2
server/src/simulation/input.rs:1210
blocker
handle_confirm_bookmark unconditionally overwrites SelectedBookmark. Spec §4.3 calls this the character-creation-to-live-world transition trigger. A network retry or double-click silently changes selection after server acted on first. Add if sel.bookmark_id.is_some() guard and emit SimError { kind: ProtocolError }. Benign today, corrupts Sprint 37 apartment/skill consumers.
3
server/src/simulation/npc_knowledge_transfer.rs (system registered at social_plugin.rs:21)
blocker
After D-078 retirement, NpcConversation is inserted in zero production code paths — only #[cfg(test)] mod tests (lines 559/609/657/717/766/833). transfer_npc_knowledge reacts to Added<NpcConversation> but nothing production adds it. decisions/perception.md claim "knowledge propagation remains active independently" is now aspirational. Fix options: (a) update decision doc honestly to "retained for Phase 5 rewire," (b) move to a clearly-marked dormant submodule, or (c) wire a stand-in trigger in this PR.
4
server/src/bookmark/mod.rs:95-99
blocker
SelectedBookmark derives Resource, Debug, Clone, Default — NOT Serialize, Deserialize. Spec §4.4 promises "Preserved across save/load (#553) as part of SaveState. Downstream systems (apartment generator, skill seeder) read from this resource." Grep of save_state.rs for SelectedBookmark returns zero. Either add derives + register in save_state, or amend spec to "v0.2 scope: transient; save/load deferred to Sprint 37" with inline TODO + follow-up ticket.
All fixture snapshots set bookmark_catalog: None. Client #618 needs a snapshot_with_bookmark_catalog fixture to test decoder against real MessagePack bytes (rmp_serde field-order and string-encoding edge cases may diverge from GDScript-constructed data).
load_corps() and load_valid_commodity_ids() use raw .unwrap() on conn.prepare(...) and stmt.query_map(...). Rest of binary uses process::exit(1) with diagnostic. Malformed DB produces unstructured panic instead of clean error. Match conventions.
Comments still reference run_npc_conversations (deleted in e86e53ec). Line 118: /// System ordering: after(run_npc_conversations). Line 208: // separate conversation pairs (run_npc_conversations creates symmetric…. Update or remove.
9
server/src/simulation/monologue.rs:398 + module-level docs in monologue.rs, dialogue.rs, social_plugin.rs
issue
monologue.rs:398 doc reads "Checks observation events, sound events, overheard conversations, and completed dialogues…" — priority list at 407-409 no longer includes overheard. Review all three modules' top-level docs for residual D-078 language.
Stray blank lines left behind where conversation_events/conversation_ended field inits were removed. Diff pattern - conversation_events: vec![],\n+\n. serialization.rs:1456 is inside a JSON string literal — blank line lives IN the JSON test input. 10-min cleanup.
11
server/src/bridge/types.rs:268-271
issue
BookmarkCatalog derives Debug, Clone, Serialize, Deserialize but NOT PartialEq, Eq. Sibling BookmarkWire at line 251 has the full set. Tests can't assert_eq! two catalogs directly. Add PartialEq, Eq.
12
server/src/bookmark/mod.rs:107-118
issue
BookmarkPlugin::build calls register_default_bookmarks(&mut registry) unconditionally. No hook to substitute a test registry. Spec §5 anticipates promotion to TOML — the loading code will need to be factored out of Plugin::build anyway. Consider BookmarkPlugin::new(registry) injection. Not a Sprint 36 blocker — follow-up ticket acceptable.
13
server/src/bridge/types.rs:259-262
nit
Doc says "Empty string placeholder until #679 lands." #679 IS landing in this PR. Update to describe the actual behavior (populated by BookmarkRegistry::build_catalog when CultureResolverResource present).
Default seed=1 is the canonical committed-output seed, but this isn't stated anywhere. Future reviewers seeing a seed-42 diff would wonder. One comment in tooling/generate-brands or the binary header.
Verdict: CHANGES REQUESTED
4 blockers, 8 issues, 2 nits = 14 actionable comments. Blockers cluster around: dead cargo-deny wiring, protocol-validation gap (ConfirmBookmark), decision-doc vs reality drift (D-080), and spec-implementation gap (SelectedBookmark save/load).
Please address each (or push back per the review-disagreement protocol).
Merge-integration note: Tyre verified generate_brands does not call generate_atlas.py. This means make economy-db on the rebased server branch is safe against copy's atlas work — only brand_products/brand_inputs diffs expected. The /tmp/atlas-copy-baseline.sql diff we planned will confirm this empirically.
## Review: `sprint-36/server` → main (type: code)
**Pre-review checks:**
- `cargo clippy` clean.
- PR test plan: 871 tests pass (3 pre-existing save_io failures unrelated). `cargo deny check` claimed passing — see blocker #1.
- Runtime smoke test: not mentioned in PR description — process gap.
---
### Hoshe (QA): REQUEST_CHANGES
2 blockers, 3 issues, 1 nit. Key findings: `cargo deny check` is configured but never invoked by any Makefile path — #726 shipped dead config; `ConfirmBookmark` silently overwrites an already-confirmed selection with no guard.
### Tyre (architecture): REQUEST_CHANGES
2 blockers, 5 issues, 3 nits. Architectural scaffolding is sound (bookmark spec honored, culture resolver correctly opens read-only with mutex, generate_brands honors source-canonical discipline), but two design-contract gaps block: D-080 knowledge propagation has no production trigger after D-078 retirement (decision doc aspirational), and `SelectedBookmark` isn't `Serialize` despite spec §4.4 promising save/load persistence.
### Key cross-cutting finding (good news)
**`generate_brands` reads `systems.db` but does NOT call `generate_atlas.py`** — verified by Tyre. The merge-integration safety guard we've been planning is simpler than feared: `make economy-db` after server merges only touches `brand_products` / `brand_inputs` tables, not atlas_*. Copy's hand-refined atlas is not at risk from server's post-merge regen. The `/tmp/atlas-copy-baseline.sql` diff will confirm this empirically when the merge happens.
---
### Consolidated comments
| # | File | Sev | Issue |
|---|------|-----|-------|
| 1 | `Makefile` (audit target) | blocker | `cargo deny check` not wired anywhere — `pre-pr-server` depends on `audit` which runs `cargo audit`, not `cargo deny`. `server/deny.toml` shipped by #726 is dead config. Add a `deny` target and wire it into `pre-pr-server`. |
| 2 | `server/src/simulation/input.rs:1210` | blocker | `handle_confirm_bookmark` unconditionally overwrites `SelectedBookmark`. Spec §4.3 calls this the character-creation-to-live-world transition trigger. A network retry or double-click silently changes selection after server acted on first. Add `if sel.bookmark_id.is_some()` guard and emit `SimError { kind: ProtocolError }`. Benign today, corrupts Sprint 37 apartment/skill consumers. |
| 3 | `server/src/simulation/npc_knowledge_transfer.rs` (system registered at `social_plugin.rs:21`) | blocker | After D-078 retirement, `NpcConversation` is inserted in zero production code paths — only `#[cfg(test)] mod tests` (lines 559/609/657/717/766/833). `transfer_npc_knowledge` reacts to `Added<NpcConversation>` but nothing production adds it. `decisions/perception.md` claim "knowledge propagation remains active independently" is now aspirational. Fix options: (a) update decision doc honestly to "retained for Phase 5 rewire," (b) move to a clearly-marked dormant submodule, or (c) wire a stand-in trigger in this PR. |
| 4 | `server/src/bookmark/mod.rs:95-99` | blocker | `SelectedBookmark` derives `Resource, Debug, Clone, Default` — NOT `Serialize, Deserialize`. Spec §4.4 promises "Preserved across save/load (#553) as part of SaveState. Downstream systems (apartment generator, skill seeder) read from this resource." Grep of `save_state.rs` for `SelectedBookmark` returns zero. Either add derives + register in save_state, or amend spec to "v0.2 scope: transient; save/load deferred to Sprint 37" with inline TODO + follow-up ticket. |
| 5 | `server/src/simulation/input.rs` + `server/src/bookmark/mod.rs` | issue | Spec §7 item 8 explicitly requires `ConfirmBookmark` validation unit tests. None exist. Missing: unknown `bookmark_id` → `SimError`, invalid `starting_location_id` → `SimError`, valid inputs → `SelectedBookmark` populated. |
| 6 | `server/tests/gen_fixtures.rs` | issue | All fixture snapshots set `bookmark_catalog: None`. Client #618 needs a `snapshot_with_bookmark_catalog` fixture to test decoder against real MessagePack bytes (rmp_serde field-order and string-encoding edge cases may diverge from GDScript-constructed data). |
| 7 | `server/src/bin/generate_brands/main.rs:210,222,230,232,629` | issue | `load_corps()` and `load_valid_commodity_ids()` use raw `.unwrap()` on `conn.prepare(...)` and `stmt.query_map(...)`. Rest of binary uses `process::exit(1)` with diagnostic. Malformed DB produces unstructured panic instead of clean error. Match conventions. |
| 8 | `server/src/simulation/npc_knowledge_transfer.rs:118, 208` | issue | Comments still reference `run_npc_conversations` (deleted in `e86e53ec`). Line 118: `/// System ordering: after(run_npc_conversations).` Line 208: `// separate conversation pairs (run_npc_conversations creates symmetric…`. Update or remove. |
| 9 | `server/src/simulation/monologue.rs:398` + module-level docs in `monologue.rs`, `dialogue.rs`, `social_plugin.rs` | issue | `monologue.rs:398` doc reads "Checks observation events, sound events, **overheard conversations**, and completed dialogues…" — priority list at 407-409 no longer includes overheard. Review all three modules' top-level docs for residual D-078 language. |
| 10 | `server/tests/{bridge_ipc,bridge_tcp,error_handling,gen_fixtures,serialization}.rs` (multiple lines) | issue | Stray blank lines left behind where `conversation_events/conversation_ended` field inits were removed. Diff pattern `- conversation_events: vec![],\n+\n`. `serialization.rs:1456` is inside a JSON string literal — blank line lives IN the JSON test input. 10-min cleanup. |
| 11 | `server/src/bridge/types.rs:268-271` | issue | `BookmarkCatalog` derives `Debug, Clone, Serialize, Deserialize` but NOT `PartialEq, Eq`. Sibling `BookmarkWire` at line 251 has the full set. Tests can't `assert_eq!` two catalogs directly. Add `PartialEq, Eq`. |
| 12 | `server/src/bookmark/mod.rs:107-118` | issue | `BookmarkPlugin::build` calls `register_default_bookmarks(&mut registry)` unconditionally. No hook to substitute a test registry. Spec §5 anticipates promotion to TOML — the loading code will need to be factored out of `Plugin::build` anyway. Consider `BookmarkPlugin::new(registry)` injection. Not a Sprint 36 blocker — follow-up ticket acceptable. |
| 13 | `server/src/bridge/types.rs:259-262` | nit | Doc says "Empty string placeholder until #679 lands." #679 IS landing in this PR. Update to describe the actual behavior (populated by BookmarkRegistry::build_catalog when CultureResolverResource present). |
| 14 | `server/src/bin/generate_brands/main.rs:63` + commit/docs | nit | Default seed=1 is the canonical committed-output seed, but this isn't stated anywhere. Future reviewers seeing a seed-42 diff would wonder. One comment in `tooling/generate-brands` or the binary header. |
---
### Verdict: CHANGES REQUESTED
4 blockers, 8 issues, 2 nits = 14 actionable comments. Blockers cluster around: dead cargo-deny wiring, protocol-validation gap (ConfirmBookmark), decision-doc vs reality drift (D-080), and spec-implementation gap (SelectedBookmark save/load).
Please address each (or push back per the review-disagreement protocol).
**Merge-integration note:** Tyre verified `generate_brands` does not call `generate_atlas.py`. This means `make economy-db` on the rebased server branch is safe against copy's atlas work — only `brand_products`/`brand_inputs` diffs expected. The `/tmp/atlas-copy-baseline.sql` diff we planned will confirm this empirically.
Review round 2 pushed (commit 13530910). All 14 comments addressed: 4 blockers, 8 issues, 2 nits. Two follow-up tickets filed for items deferred per reviewer's acceptance: #862 (BookmarkPlugin injection) and #863 (SelectedBookmark save/load to Sprint 37). Hoshe verified each fix against the original review entry; make pre-pr-server clean (fmt, clippy, build, deny). Ready for re-review.
Review round 2 pushed (commit 13530910). All 14 comments addressed: 4 blockers, 8 issues, 2 nits. Two follow-up tickets filed for items deferred per reviewer's acceptance: #862 (BookmarkPlugin injection) and #863 (SelectedBookmark save/load to Sprint 37). Hoshe verified each fix against the original review entry; make pre-pr-server clean (fmt, clippy, build, deny). Ready for re-review.
Round 1 recap: 4 blockers, 8 issues, 2 nits (14 comments). Team response: one commit (13530910) addressing all 14 items + 2 follow-up tickets (#862, #863) for reviewer-accepted deferrals.
Hoshe (QA): APPROVE
All 7 Hoshe-relevant items RESOLVED with substance. Four ConfirmBookmark validation tests present (unknown id / invalid location / valid path / double-confirm). snapshot_with_bookmark_catalog fixture populates a complete catalog. All 5 raw .unwrap() sites in generate_brands replaced with eprintln! + process::exit(1). BookmarkCatalog now derives PartialEq, Eq. Stray blank lines in 5 test files cleaned. deny target wired at Makefile:389-390 with pre-pr-server:306 depending on it.
Observational note (non-blocking): idempotency guard fires after location validation — a double-confirm with invalid location returns "not in allowed_locations" rather than "already confirmed." Both are ProtocolError, spec §4.3 only requires rejection. save_io nextest failures verified pre-existing (git log on save_io.rs shows no Sprint-36 commits; AtomicU64-based temp-path scheme predates this PR).
Tyre (architecture): APPROVE
Architecturally sound rework. All 4 blockers resolved with substance:
D-080 dormancy amendment (blocker #3):decisions/perception.md:427-428 explicitly uses "dormant", names the Added<NpcConversation> trigger condition, and instructs readers to "guard against assuming it runs." Matches code reality — production inserts of NpcConversation are zero outside #[cfg(test)] modules.
SelectedBookmark v0.2 transient (blocker #4): triple-anchored — inline TODO at bookmark/mod.rs:101 (// TODO(sprint-37): serialize — see #863), spec §4.4 at sprint-36-bookmark-spec.md:212-242 amended with "v0.2 scope: transient only", ticket #863 DoD captures all four items (Serialize derives, save_state.rs registration, round-trip test, spec cleanup).
ConfirmBookmark idempotency (blocker #2): reuses existing SimError::ProtocolError — no new SimError kind, no new wire shape. Validation order (unknown id → invalid location → already-confirmed) follows fail-fast-on-input convention.
cargo-deny wiring (blocker #1):deny target added as hard gate in pre-pr and pre-pr-server, not advisory.
Deferred items #862 (BookmarkPlugin injection, priority low) and #863 (SelectedBookmark SaveState, priority medium) correctly scoped per reviewer protocol. No new architectural concerns — the 179-line input.rs test addition stays inside mod tests (no public surface leak), and the 33-line gen_fixtures.rs addition exercises already-committed wire types.
Overall verdict: APPROVED
All 14 round-1 items resolved (12 fixed, 2 deferred with filed tickets + inline anchors). No new blockers.
Merge-integration reminder: Tyre's round-1 finding stands — generate_brands reads systems.db but does NOT call generate_atlas.py. When this branch rebases on current main (which has copy's atlas work), running make economy-db on the rebased branch should produce only brand_products/brand_inputs diffs. The /tmp/atlas-copy-baseline.sql diff we captured will confirm empirically.
Recommend merge.
## Review: `sprint-36/server` → main (round 2)
**Round 1 recap:** 4 blockers, 8 issues, 2 nits (14 comments).
**Team response:** one commit (`13530910`) addressing all 14 items + 2 follow-up tickets (#862, #863) for reviewer-accepted deferrals.
---
### Hoshe (QA): APPROVE
All 7 Hoshe-relevant items RESOLVED with substance. Four ConfirmBookmark validation tests present (unknown id / invalid location / valid path / double-confirm). `snapshot_with_bookmark_catalog` fixture populates a complete catalog. All 5 raw `.unwrap()` sites in `generate_brands` replaced with `eprintln! + process::exit(1)`. `BookmarkCatalog` now derives `PartialEq, Eq`. Stray blank lines in 5 test files cleaned. `deny` target wired at `Makefile:389-390` with `pre-pr-server:306` depending on it.
**Observational note (non-blocking):** idempotency guard fires after location validation — a double-confirm with invalid location returns "not in allowed_locations" rather than "already confirmed." Both are `ProtocolError`, spec §4.3 only requires rejection. `save_io` nextest failures verified pre-existing (`git log` on `save_io.rs` shows no Sprint-36 commits; `AtomicU64`-based temp-path scheme predates this PR).
### Tyre (architecture): APPROVE
Architecturally sound rework. All 4 blockers resolved with substance:
- **D-080 dormancy amendment (blocker #3):** `decisions/perception.md:427-428` explicitly uses "dormant", names the `Added<NpcConversation>` trigger condition, and instructs readers to "guard against assuming it runs." Matches code reality — production inserts of `NpcConversation` are zero outside `#[cfg(test)]` modules.
- **SelectedBookmark v0.2 transient (blocker #4):** triple-anchored — inline TODO at `bookmark/mod.rs:101` (`// TODO(sprint-37): serialize — see #863`), spec §4.4 at `sprint-36-bookmark-spec.md:212-242` amended with "v0.2 scope: transient only", ticket #863 DoD captures all four items (Serialize derives, save_state.rs registration, round-trip test, spec cleanup).
- **ConfirmBookmark idempotency (blocker #2):** reuses existing `SimError::ProtocolError` — no new SimError kind, no new wire shape. Validation order (unknown id → invalid location → already-confirmed) follows fail-fast-on-input convention.
- **cargo-deny wiring (blocker #1):** `deny` target added as hard gate in `pre-pr` and `pre-pr-server`, not advisory.
Deferred items #862 (BookmarkPlugin injection, priority low) and #863 (SelectedBookmark SaveState, priority medium) correctly scoped per reviewer protocol. No new architectural concerns — the 179-line `input.rs` test addition stays inside `mod tests` (no public surface leak), and the 33-line `gen_fixtures.rs` addition exercises already-committed wire types.
---
### Overall verdict: APPROVED
All 14 round-1 items resolved (12 fixed, 2 deferred with filed tickets + inline anchors). No new blockers.
**Merge-integration reminder:** Tyre's round-1 finding stands — `generate_brands` reads `systems.db` but does NOT call `generate_atlas.py`. When this branch rebases on current main (which has copy's atlas work), running `make economy-db` on the rebased branch should produce only `brand_products`/`brand_inputs` diffs. The `/tmp/atlas-copy-baseline.sql` diff we captured will confirm empirically.
Recommend merge.
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
Sprint 36 (Forge) server work — closes Phase 3 Atlas (brand pipeline) and opens Phase 4 foundations (bookmarks, location-culture). Seven tickets shipped.
Phase 3 close:
#829—generate_brandsbinary: 126 templates × 48 corps → 10,000brand_productsrows + 26,750 inputs. Wired intomake economy-dbaftergenerate_corporations. V-B01..V-B06 structural validation passes.#827(Sprint 35) brand schema is now consumed.Phase 4 open:
#614— Bookmark definition system. NewBookmarkPlugin,BookmarkRegistry, tycoon bookmark,ConfirmBookmarkPlayerAction handler. NewObserverSnapshot.bookmark_catalogfield,BookmarkInputParamsSystemParam bundle. Unblocks client #618.#679— Location-to-culture resolver. Newserver/src/knowledge/culture.rs,CultureResolverreadingsystems.db(read-only, mutex-wrapped), 3-pass system→body→station lookup, 8 unit tests. Unblocks client #680.NPC cleanup (narrow scope per R-012):
#842— Auditedserver/src/npc/,content/global/, and the conversation runtime to identify what is actually dead (D-078 only) vs. what is live (D-024 entity model, D-142 zone-types). Audit drove#848.#848— Surgical retirement of D-078:overheard.ron(1629 lines),overheard.yaml.deprecated(855 lines),simulation/conversation.rs(1440 lines)social_plugin.rs×3,bridge/types.rs×2 fields onObserverSnapshot,monologue.rs,voice/integration.rs)NpcName,NpcColorIndex,NpcConversation) relocated tosimulation/npc_components.rs— still used by D-080 knowledge propagationdecisions/perception.mdserver/src/npc/andserver/content/global/are untouched (verified — those are live systems).Tooling/hygiene:
#726—server/deny.tomlconfigured. Allowed licenses: MIT, Apache-2.0, BSD-2/3, ISC, Unlicense, Zlib, CC0-1.0. RUSTSEC advisory DB + duplicate dep detection enabled.#636— bincode v1 removed (Tyre's audit found it was an orphan dep with zero call sites). RUSTSEC-2025-0141 resolved by deletion.Test plan
make pre-pr-serverclean (fmt, clippy, build, fixtures, audit)cargo test: 871 passed. 3 failures are pre-existingsave_iotests (load_from_file_assigns_background_sim_tier,load_from_file_preserves_triangle_tension,load_from_file_does_not_duplicate_triangles) — confirmed against clean HEAD, unrelated to this sprintmake economy-dbregenerates brand layer cleanly; 10,008brand_productstotal (10,000 generated + 8 hand-authored notable brands)cargo deny checkpasses with the new config#848against audit checklist — no scope violations, npc/ and content/global/ untouchedBookmarkWire+culture_taground-trip (unblocked by this PR)Architecture specs
Tyre's contracts under
docs/architecture/:sprint-36-bookmark-spec.md—BookmarkDefinitionshape + bridge protocolsprint-36-culture-api-spec.md—resolve_culturesignature + module placementsprint-36-bincode-audit.md— bincode orphan finding + v1→v2 cheatsheet (kept for future reference)Notes for reviewer
save_iotest failures are tracked separately — recommend a follow-up bug ticket but not blocking.archetype_monologuetest failures verified against clean HEAD — also unrelated.generated_brands.tomlis gitignored (regenerated bymake economy-db); committeddata/systems.dbincludes the populated brand layer.🤖 Generated with Claude Code
Review:
sprint-36/server→ main (type: code)Pre-review checks:
cargo clippyclean.cargo deny checkclaimed passing — see blocker #1.Hoshe (QA): REQUEST_CHANGES
2 blockers, 3 issues, 1 nit. Key findings:
cargo deny checkis configured but never invoked by any Makefile path — #726 shipped dead config;ConfirmBookmarksilently overwrites an already-confirmed selection with no guard.Tyre (architecture): REQUEST_CHANGES
2 blockers, 5 issues, 3 nits. Architectural scaffolding is sound (bookmark spec honored, culture resolver correctly opens read-only with mutex, generate_brands honors source-canonical discipline), but two design-contract gaps block: D-080 knowledge propagation has no production trigger after D-078 retirement (decision doc aspirational), and
SelectedBookmarkisn'tSerializedespite spec §4.4 promising save/load persistence.Key cross-cutting finding (good news)
generate_brandsreadssystems.dbbut does NOT callgenerate_atlas.py— verified by Tyre. The merge-integration safety guard we've been planning is simpler than feared:make economy-dbafter server merges only touchesbrand_products/brand_inputstables, not atlas_*. Copy's hand-refined atlas is not at risk from server's post-merge regen. The/tmp/atlas-copy-baseline.sqldiff will confirm this empirically when the merge happens.Consolidated comments
Makefile(audit target)cargo deny checknot wired anywhere —pre-pr-serverdepends onauditwhich runscargo audit, notcargo deny.server/deny.tomlshipped by #726 is dead config. Add adenytarget and wire it intopre-pr-server.server/src/simulation/input.rs:1210handle_confirm_bookmarkunconditionally overwritesSelectedBookmark. Spec §4.3 calls this the character-creation-to-live-world transition trigger. A network retry or double-click silently changes selection after server acted on first. Addif sel.bookmark_id.is_some()guard and emitSimError { kind: ProtocolError }. Benign today, corrupts Sprint 37 apartment/skill consumers.server/src/simulation/npc_knowledge_transfer.rs(system registered atsocial_plugin.rs:21)NpcConversationis inserted in zero production code paths — only#[cfg(test)] mod tests(lines 559/609/657/717/766/833).transfer_npc_knowledgereacts toAdded<NpcConversation>but nothing production adds it.decisions/perception.mdclaim "knowledge propagation remains active independently" is now aspirational. Fix options: (a) update decision doc honestly to "retained for Phase 5 rewire," (b) move to a clearly-marked dormant submodule, or (c) wire a stand-in trigger in this PR.server/src/bookmark/mod.rs:95-99SelectedBookmarkderivesResource, Debug, Clone, Default— NOTSerialize, Deserialize. Spec §4.4 promises "Preserved across save/load (#553) as part of SaveState. Downstream systems (apartment generator, skill seeder) read from this resource." Grep ofsave_state.rsforSelectedBookmarkreturns zero. Either add derives + register in save_state, or amend spec to "v0.2 scope: transient; save/load deferred to Sprint 37" with inline TODO + follow-up ticket.server/src/simulation/input.rs+server/src/bookmark/mod.rsConfirmBookmarkvalidation unit tests. None exist. Missing: unknownbookmark_id→SimError, invalidstarting_location_id→SimError, valid inputs →SelectedBookmarkpopulated.server/tests/gen_fixtures.rsbookmark_catalog: None. Client #618 needs asnapshot_with_bookmark_catalogfixture to test decoder against real MessagePack bytes (rmp_serde field-order and string-encoding edge cases may diverge from GDScript-constructed data).server/src/bin/generate_brands/main.rs:210,222,230,232,629load_corps()andload_valid_commodity_ids()use raw.unwrap()onconn.prepare(...)andstmt.query_map(...). Rest of binary usesprocess::exit(1)with diagnostic. Malformed DB produces unstructured panic instead of clean error. Match conventions.server/src/simulation/npc_knowledge_transfer.rs:118, 208run_npc_conversations(deleted ine86e53ec). Line 118:/// System ordering: after(run_npc_conversations).Line 208:// separate conversation pairs (run_npc_conversations creates symmetric…. Update or remove.server/src/simulation/monologue.rs:398+ module-level docs inmonologue.rs,dialogue.rs,social_plugin.rsmonologue.rs:398doc reads "Checks observation events, sound events, overheard conversations, and completed dialogues…" — priority list at 407-409 no longer includes overheard. Review all three modules' top-level docs for residual D-078 language.server/tests/{bridge_ipc,bridge_tcp,error_handling,gen_fixtures,serialization}.rs(multiple lines)conversation_events/conversation_endedfield inits were removed. Diff pattern- conversation_events: vec![],\n+\n.serialization.rs:1456is inside a JSON string literal — blank line lives IN the JSON test input. 10-min cleanup.server/src/bridge/types.rs:268-271BookmarkCatalogderivesDebug, Clone, Serialize, Deserializebut NOTPartialEq, Eq. SiblingBookmarkWireat line 251 has the full set. Tests can'tassert_eq!two catalogs directly. AddPartialEq, Eq.server/src/bookmark/mod.rs:107-118BookmarkPlugin::buildcallsregister_default_bookmarks(&mut registry)unconditionally. No hook to substitute a test registry. Spec §5 anticipates promotion to TOML — the loading code will need to be factored out ofPlugin::buildanyway. ConsiderBookmarkPlugin::new(registry)injection. Not a Sprint 36 blocker — follow-up ticket acceptable.server/src/bridge/types.rs:259-262server/src/bin/generate_brands/main.rs:63+ commit/docstooling/generate-brandsor the binary header.Verdict: CHANGES REQUESTED
4 blockers, 8 issues, 2 nits = 14 actionable comments. Blockers cluster around: dead cargo-deny wiring, protocol-validation gap (ConfirmBookmark), decision-doc vs reality drift (D-080), and spec-implementation gap (SelectedBookmark save/load).
Please address each (or push back per the review-disagreement protocol).
Merge-integration note: Tyre verified
generate_brandsdoes not callgenerate_atlas.py. This meansmake economy-dbon the rebased server branch is safe against copy's atlas work — onlybrand_products/brand_inputsdiffs expected. The/tmp/atlas-copy-baseline.sqldiff we planned will confirm this empirically.Review round 2 pushed (commit
13530910). All 14 comments addressed: 4 blockers, 8 issues, 2 nits. Two follow-up tickets filed for items deferred per reviewer's acceptance: #862 (BookmarkPlugin injection) and #863 (SelectedBookmark save/load to Sprint 37). Hoshe verified each fix against the original review entry; make pre-pr-server clean (fmt, clippy, build, deny). Ready for re-review.Review:
sprint-36/server→ main (round 2)Round 1 recap: 4 blockers, 8 issues, 2 nits (14 comments).
Team response: one commit (
13530910) addressing all 14 items + 2 follow-up tickets (#862, #863) for reviewer-accepted deferrals.Hoshe (QA): APPROVE
All 7 Hoshe-relevant items RESOLVED with substance. Four ConfirmBookmark validation tests present (unknown id / invalid location / valid path / double-confirm).
snapshot_with_bookmark_catalogfixture populates a complete catalog. All 5 raw.unwrap()sites ingenerate_brandsreplaced witheprintln! + process::exit(1).BookmarkCatalognow derivesPartialEq, Eq. Stray blank lines in 5 test files cleaned.denytarget wired atMakefile:389-390withpre-pr-server:306depending on it.Observational note (non-blocking): idempotency guard fires after location validation — a double-confirm with invalid location returns "not in allowed_locations" rather than "already confirmed." Both are
ProtocolError, spec §4.3 only requires rejection.save_ionextest failures verified pre-existing (git logonsave_io.rsshows no Sprint-36 commits;AtomicU64-based temp-path scheme predates this PR).Tyre (architecture): APPROVE
Architecturally sound rework. All 4 blockers resolved with substance:
D-080 dormancy amendment (blocker #3):
decisions/perception.md:427-428explicitly uses "dormant", names theAdded<NpcConversation>trigger condition, and instructs readers to "guard against assuming it runs." Matches code reality — production inserts ofNpcConversationare zero outside#[cfg(test)]modules.SelectedBookmark v0.2 transient (blocker #4): triple-anchored — inline TODO at
bookmark/mod.rs:101(// TODO(sprint-37): serialize — see #863), spec §4.4 atsprint-36-bookmark-spec.md:212-242amended with "v0.2 scope: transient only", ticket #863 DoD captures all four items (Serialize derives, save_state.rs registration, round-trip test, spec cleanup).ConfirmBookmark idempotency (blocker #2): reuses existing
SimError::ProtocolError— no new SimError kind, no new wire shape. Validation order (unknown id → invalid location → already-confirmed) follows fail-fast-on-input convention.cargo-deny wiring (blocker #1):
denytarget added as hard gate inpre-prandpre-pr-server, not advisory.Deferred items #862 (BookmarkPlugin injection, priority low) and #863 (SelectedBookmark SaveState, priority medium) correctly scoped per reviewer protocol. No new architectural concerns — the 179-line
input.rstest addition stays insidemod tests(no public surface leak), and the 33-linegen_fixtures.rsaddition exercises already-committed wire types.Overall verdict: APPROVED
All 14 round-1 items resolved (12 fixed, 2 deferred with filed tickets + inline anchors). No new blockers.
Merge-integration reminder: Tyre's round-1 finding stands —
generate_brandsreadssystems.dbbut does NOT callgenerate_atlas.py. When this branch rebases on current main (which has copy's atlas work), runningmake economy-dbon the rebased branch should produce onlybrand_products/brand_inputsdiffs. The/tmp/atlas-copy-baseline.sqldiff we captured will confirm empirically.Recommend merge.
Pull request closed