--- date: 2026-06-05 type: session tags: [session, m7, automation, production-chains, conveyor, persistence, netcode, determinism] --- # Session 2026-06-05 — M7 Automation: self-running production chains (Harvester → Conveyor → Fabricator) ## Goal Operator: *"Let us start the automation milestone."* → **M7 — Automation**: self-running, tick-based production chains with deterministic offline catch-up, building on the DR-014 locked structure model. Intake decisions (AskUserQuestion): - **Scope:** the FULL chain (Harvester → Conveyor → Fabricator), not a reduced harvester-only slice. - **What it makes:** AUTO-GATHER EXISTING resources (no new resource ids) — the chain refines existing resources (default 2 Ore → 1 Aether) into the global ledger so setup compounds. - **Persistence:** FOLDED IN now (SaveData → v2; player-built structures survive quit→Continue). - (Informed re-confirm after the adversarial review:) **keep the full chain** with a deterministic conveyor; **"offline catch-up" = within-session tick math + preserved stockpile** across quit (NO wall-clock minting — honors the determinism pillar). ## Process (ultracode: workflows + adversarial verification) - **Phases 1–4 (research + design):** code-graph scan + context7 API verification (NetworkTick/NetworkTime accessors, ECB singleton, server-only `IBufferElementData` stays off the wire); a 3-lens design swarm — code architect · persistence/authoring architect · **adversarial netcode/determinism/reuse critic**. The critic earned its keep: flagged the conveyor as the dominant complexity/risk, a catch-up `period=0` div-by-zero + per-tick/catch-up **double-count** footgun, the restore **tick-rebase** blocker, and the `StorageEntry` singleton-collision; all folded into the plan. - **Phase 5:** plan approved; the two informed forks resolved (keep full chain; within-session catch-up + preserved stockpile). - **Phase 6 (source workflow → controlled MCP apply):** a draft→critique Workflow produced the source bundle. **Incident:** the Workflow's general-purpose draft agents ignored the "return source only" instruction and raw-`Write`-wrote the 3 systems + 5 tests into `Assets/` — which **compile but get no `.meta`/test-discovery until `refresh_unity scope=all mode=force`** (logged as native memory [[raw-written-cs-needs-full-refresh]]). Recovered: kept the (high-quality, internally-consistent) agent systems + tests, authored the foundation to satisfy them, and applied everything via MCP with reconciliations — the key one: **`CyclesDue` lower-bound 0, not 1** (prevents a premature mint on a restored `remaining==0` machine), and `BuildPlaceSystem` stamps `LastProcessedTick=0` so runtime-placed machines hit `NeedsInit`. ## Done — code (compiles clean; EditMode 187→**190**) - **Components** (`Simulation/Automation/AutomationComponents.cs`): `Harvester{byte ResourceId;int Yield;int PeriodTicks}`, `Fabricator{byte In/OutResourceId;int In/OutAmount;int PeriodTicks}`, `Conveyor{byte Direction;int PeriodTicks}`, server-only buffers `MachineInput`/`MachineOutput`, `ConveyorItem` (enableable, baked DISABLED), `RuntimePlacedTag`. The DR-014 "recipe column on `StructureCatalogEntry`" was **deliberately dropped** — dead weight; the recipe is baked on each machine component (server-only, not needed client-side). - **Pure math** (unit-tested, byte-only): `ProductionMath` (`NeedsInit`/`CyclesDue`/`RemainingTicks`/`RestoreNextTick` — the single gated catch-up path), `ConveyorMath` (`DirOffset`/`CellKey`/**`ResolveMoves`** — deterministic + order-independent: snapshot → stable-sort by CellKey → at-most-one destination claim → stall losers no-loss), `MachineSlotMath` (byte-id deposit/withdraw/total, the non-replicated twin of `StorageMath`). - **Systems** (`Server/Automation/`, server-only, ordered `[UpdateAfter(PredictedSimulationSystemGroup)]` chain Harvester→Conveyor→Fabricator): `HarvesterProductionSystem` (fixed-yield → its `MachineOutput`), `ConveyorTransportSystem` (pull from adjacent upstream `MachineOutput` → `ResolveMoves` → settle `ConveyorItem` enable-bit / deposit to sink `MachineInput`), `FabricatorProductionSystem` (input-limited recipe → GLOBAL ledger). `Tuning.MaxProductionCatchup = 600`. - **Persistence (SaveData v2):** `StructureSave[]` + flat `StructureIoRow[]` (cooldown as epoch-independent REMAINING ticks; in-flight conveyor item inline); `SaveComponents` `PendingStructure`/`PendingStructureIo` staging buffers; **shared `SaveStructureScan.Collect`** (both autosave + quit-save scan ONLY `RuntimePlacedTag` structures → kills the two-path drift the critic flagged); `SaveWriteSystem` + `WorldLauncher` (StagePendingSave + TrySaveFromServer) extended; new **one-shot `BaseRestoreSystem`** (gates on `StructureCatalog`+`BaseAnchor`+`NetworkTime`, replays each saved structure CHARGE-FREE, refills buffers + conveyor item, re-tags, self-destructs). - **Build wiring:** `BuildPlaceRequest` +`byte Direction` (additive scalar; RPC wire-hash shifts → single-build); `BuildPlaceSystem` stamps conveyor `Direction` (via a `ComponentLookup` on the prefab) + `RuntimePlacedTag` + `LastProcessedTick=0`; `BuildSendSystem` H/F/C keys + `[`/`]` conveyor-rotate + editor statics `PlaceHarvester/Fabricator/Conveyor`. - **Authoring + prefabs:** 3 MonoBehaviours + bakers; `StructureCatalogAuthoring` +3 rows (Harvester 20 / Fabricator 30 / Conveyor 2 Ore); `Harvester/Fabricator/Conveyor.prefab` (duplicated from `Turret.prefab` → swapped authoring; ownerless-interpolated `GhostAuthoringComponent` + mesh free) wired into the catalog in the Gameplay subscene. ## Validation - **EditMode 190/190** (35 new automation tests): catch-up gating/clamp/no-mint/period-0-guard; conveyor Y-junction tie-break + 4-cell line + shuffle-invariance + blocked-stall; fabricator input-limit (no mint-from-nothing); SaveData v2 round-trip + epoch-independent cooldown. - **Runtime (real netcode ServerWorld+ClientWorld, focused editor, via `execute_code`):** catalog bakes all 6 entries (T1/T5/T6 + **T2/T3/T4 machine prefabs valid**); placed a live H(18,18)→C(19,18 +X)→F(20,18) chain (RPC → cost Ore 200→148); the chain **ran end-to-end** (Harvester output flowed → Conveyor transported → Fabricator `2 Ore → 1 Aether` → ledger), **SERVER Aether == CLIENT Aether** (22, then 52 — replicated). **Quit autosaved** v2 (3 structures + rebased `RemainingTicks`); **Continue restored** all 3 at the exact cells, **charge-free** (Ore unchanged at 148), born-correct ledger, and the **restored chain resumed producing**. Console clean of errors throughout. ## Decisions [[DR-020_M7_Automation_Production_Chains]]. Resolves the open [[Backlog]] "production replication (predicted vs server-only)" question: **server-only** — production runs in the plain server `SimulationSystemGroup`; results replicate via the existing global ledger + `PlacedStructure` ghosts, never predicted. ## Next-session intent - **Throughput visuals** (item-on-belt): server-only machine buffers don't reach the client (clients see only `PlacedStructure.Type`); add ONE small replicated byte if live belt visuals are wanted. - **Build-palette HUD + ghost preview** + a conveyor-facing indicator (dev `[`/`]` rotate + H/F/C keys only today). - **Relevancy ceiling:** `RegionRelevancySystem`'s O(structures×connections)/tick scan becomes load-bearing at higher conveyor counts (DR-014 ceiling note) — batch it when counts grow. - **Recipe depth:** multi-input fabricator recipes; fabricator→conveyor output chaining (additive — give the fabricator a `MachineOutput`); per-machine distinct meshes (machines reuse the Turret mesh as a placeholder). - **Operator live play-through** + a real 2-build LAN run to exercise the chain under latency (single-client validated).