Fix Sins architectural sins
Policies that keep the org coherent
name: Sins architecture sins fix overview: "Fix the Sins campaign architectural debt in one coherent pass: harden replay saves (1A), enforce in-app mesh Kernel/Sins boundaries (2A), session-scope claims, delete debug leftovers, shared captain actions, bridge-thread isolation, catalog memoize, shared hull finance, thinner pulse extraction, and honest dual-engine retention." todos:
- id: claims-tracker
content: Replace ClaimsPulse static Seen* with session-owned ClaimsTracker status: completed
- id: delete-agent-debug
content: Remove AgentDebugLog type and all call sites status: completed
- id: harden-replay-saves
content: Add SimHash/cash integrity on CampaignSaveRecord; verify on FromSaveAsync status: completed
- id: bridge-capture
content: Capture CaptainBridgeModel on sim path; UI binds last snapshot status: completed
- id: captain-actions
content: Add CaptainActions; retarget MainWindow + CaptainConsole status: completed
- id: hull-finance
content: Extract HullFinance remit helpers; wire SurvivalCaptain, PlayerTrampAgent, InsurancePulse status: completed
- id: catalog-memoize
content: Memoize SinsCatalog.Load status: completed
- id: campaign-pulse
content: Extract PulseDaysAsync into CampaignPulse collaborator status: completed
- id: mesh-boundary
content: Kernel vs Sins namespaces; internalize kernel; update SPEC status: completed
- id: bios-and-app-options
content: Fix ObserveDeliveries hub names; move RunOptions off Program statics status: completed
- id: verify
content: Unit tests + short campaign run + save integrity + nuget-only check status: completed isProject: false
Decisions locked: 1A harden replay (no Economy dump APIs); 2A in-app mesh boundary (no Novolis.Mesh.Core package).
Do not delete the core BM engine or invent full-world serialization. Align CaptainActions with the in-flight event-driven bridge protocol: this pass builds the in-process command layer that bridge service can wrap later.
flowchart TB
subgraph shells [Shells]
UI[MainWindow]
CLI[CaptainConsole]
end
Actions[CaptainActions]
Session[LiveSession]
Pulse[CampaignPulse]
Claims[ClaimsTracker]
MeshK[Mesh.Kernel]
MeshS[Mesh.Sins]
UI --> Actions
CLI --> Actions
Actions --> Session
Session --> Pulse
Pulse --> Claims
Pulse --> MeshS
MeshS --> MeshK1. Session-scoped claims (high)
`ClaimsPulse.cs`: replace static SeenCancelled / SeenBombEdge / ResetSeen with a small ClaimsTracker instance owned by LiveSession (field on session or on `CampaignWorld.Ids`).
ClaimsPulse.TickDay(..., ClaimsTracker tracker)- Remove process-global sets; ctor of
LiveSessioncreates a fresh tracker (dropResetSeencall) - Update unit tests that hit claims if any
2. Delete AgentDebugLog (high)
- Remove `AgentDebugLog.cs`
- Strip all
AgentDebugLog.Write+#region agent logfrom `PlayerTrampAgent.cs` and `CaptainConsole.cs`
3. Harden replay saves (high, 1A)
Keep seed → HoursDone warm. Make the contract honest and checkable:
- Extend `CampaignSaveRecord` with integrity fields:
SimHash,DayIndex(already partly present),OpsCash(rename/clarify vs displayCash) - On save in `CampaignSaveStore`: write
sim.State.Hashand tramp ops cash - On `FromSaveAsync`: after
AdvanceHoursAsync, verify hash/day/cash; on mismatch log a clear error and fail load (do not silently diverge) - Docs: `gameplay.md` / `architecture.md` — checkpoint = deterministic replay + integrity, not a world dump
- No sidecar unless warm needs player mid-queue (interactive saves already warm with autopilot); skip DTO sidecars in this pass
4. Bridge thread isolation (high)
`CaptainJobBoard` / `MainWindow`: stop reading live Economy collections from the UI thread during sim.
- Add
LiveSession.CaptureBridge(systemId)(or similar) that builds `CaptainBridgeModel` on the sim path atDayEnded/AwaitingDecision(or under a session lock held only for the capture) - UI binds the last captured immutable model; refresh = use pending capture, not ad-hoc
Frommid-tick - Keep
HubOrders.ToArray()as defense-in-depth inside board builders when called from capture
5. Shared `CaptainActions` (medium)
New `Universe/Player/CaptainActions.cs`: single place that enqueues PlayerOrder kinds + optional Continue().
- Retarget overlapping verbs in MainWindow and CaptainConsole (accept/charter/market/travel/depart/wait/refuse/premium/overhaul/profile/board/time)
- Shells keep selection/parsing/UX only
- Shape results as
Result(Ok, Message, Advanced)so bridge protocol can wrap later without another fork
6. Shared hull finance posts (medium)
Extract HullFinance.TryRemitPremium / TryPayOverhaul (same ledger posts used today).
- `SurvivalCaptain` and `PlayerTrampAgent` call helpers
- `InsurancePulse` keeps idle/arrears/station-advance policy on top of the same remit helpers
7. Catalog memoize (medium)
`SinsCatalog.Load`: memoize NearSol pack (Lazy or static cached catalog). Bridge refresh stops rebuilding every frame.
8. Thinner pulse extraction (medium)
Move PulseDaysAsync body out of LiveSession into CampaignPulse (hour loop + day commerce pulses) taking (Sim, Ids, Agents, …, ClaimsTracker).
LiveSessionkeeps pause/gates/save/events; pulse is one collaborator- Do not explode
Idsinto many types this pass — claims tracker is the only new owned field
9. Mesh in-app boundary (medium, 2A)
No new package / GPR.
- Kernel files under
Universe/Mesh/→ namespaceSinsOfACapitalismTycoon.Universe.Mesh.Kernel(engines,MeshState, pipeline, invariants) - Glue under
Universe/Mesh/Sins/→ namespaceSinsOfACapitalismTycoon.Universe.Mesh.Sins - Prefer
internalon kernel types; keepInternalsVisibleTounit tests - Update `SPEC.md` / mesh docs: extraction remains future; this pass is the boundary
10. Delivery bios + shell globals (low)
- `ObserveDeliveries`: resolve origin/dest hub names from shipment/event (match ClaimsPulse style), not
"?" - Replace `Program.UiOptions` / `ReportText` handoff with
Appstartup args /App.RunOptionsset once before desktop lifetime (same pattern, no cross-static from Program for UI)
11. Dual engines (explicit non-delete)
Keep campaign + core in one exe as documented BM regression. Only clarify `architecture.md` / Program comments that core is intentional and orthogonal — no project split this pass.
Verification
dotnet testonSinsOfACapitalismTycoon.Unit(mesh + any claims/save tests)- Headless:
dotnet run … -- --engine campaign --days 2d --seed 1001 --quiet - Save/load round-trip same seed/hours → integrity assert passes
pwsh -File novolis-governance/scripts/verify-nuget-only.ps1(and project-ref check if touched) — no local feeds; no new packages for mesh
Out of scope
- Economy platform checkpoint APIs / full world dumps
- Publishing
Novolis.Mesh.Core - Full
novolis-game-bridgeprotocol (separate plan; consumeCaptainActionslater) - Removing core smoke engine