Docs: EB-2 session log + DR-033; CLAUDE.md felt-spend bullet
DR-033 records the felt-spend design (shared Charge ammo, atomic soft-fail, ledger-fed Fabricator, no-ordering-edge trade-off, global HUD cue, no SaveData bump). CLAUDE.md adds the EB-2 ★ bullet net-zero (trimmed bullets archived to the gotchas archive under a 2026-06-12 heading). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
---
|
||||
date: 2026-06-12
|
||||
type: session
|
||||
tags:
|
||||
- session
|
||||
- combat
|
||||
- structures
|
||||
- economy
|
||||
- automation
|
||||
- netcode
|
||||
- eb-2
|
||||
permalink: gamevault/07-sessions/2026/2026-06-12-eb2-felt-spend
|
||||
---
|
||||
|
||||
# EB-2 — felt spend (turrets burn a shared Charge pool; a ledger-fed Fabricator mints it from Ore)
|
||||
|
||||
> Path A milestone; forks locked in [[DR-029_Path_A_Fork_Locks]] (EB-2 = the mined resource must be *spent* on defense). Operator picked the **ledger-fed Fabricator** route. Full [[dots-dev]] Feature track under **ultracode**: mandatory pre-code adversarial review (folded in before coding) + post-code adversarial review (**CLEAN** — 2 quality nits, both fixed in-commit). Locked → [[DR-033_EB2_Felt_Spend_Charge_Economy]]. Continues [[2026-06-11_EB1_Machines_Can_Die]].
|
||||
|
||||
## The hole EB-2 closes
|
||||
EB-1 gave a structure stakes (it can be lost). But mined Ore had no ongoing **sink** — a turret defended for free, so the harvest loop had no *point* past the first build. EB-2 makes defense *consume* the harvest: **mine Ore → a ledger-fed Fabricator converts it to Charge → turrets spend Charge per shot and soft-fail when the base runs dry.**
|
||||
|
||||
## What shipped — reuse the ledger + the recipe machine
|
||||
|
||||
### Charge = a shared ammo RESOURCE (no new wire surface)
|
||||
- `ResourceId.Charge = 4` (byte id space None0/Aether1/Ore2/Biomass3/**Charge4**). It rides the EXISTING `[GhostField] StorageEntry` ledger buffer — replicates to the HUD for free, **no new `[GhostField]`, no per-turret AmmoCount**. Ammo is a base-wide pool (not a magazine) so a siege applies *economy* pressure, not a reload minigame.
|
||||
|
||||
### Turrets spend Charge atomically, soft-fail when dry
|
||||
- `TurretFireSystem` resolves the ledger ONCE (`GetSingletonEntity<ResourceLedger>`+`GetBuffer<StorageEntry>` — never `GetSingleton<StorageEntry>`, a 2nd buffer exists on the base container). Per turret with a target: `cost=max(1,Tuning.TurretChargeCostPerShot)`; `got=StorageMath.Withdraw(ledger,Charge,cost)`; **got≥cost** ⇒ append `DamageEvent` + advance `NextTick`; **got>0** (future cost>1 partial) ⇒ refund the partial (never consume without firing); **else** ⇒ soft-fail. A soft-fail burns **no cooldown** → the turret fires the instant Charge returns. Turrets share the finite pool in query order (deterministic; later turrets soft-fail as it empties).
|
||||
- New `StorageMath.TotalOf(buffer,itemId)` (sums matching rows; 0 if itemId==0) backs the affordability read.
|
||||
|
||||
### A ledger-fed Fabricator mints the Charge
|
||||
- `Fabricator.InputFromLedger` (a **`byte`**, server-only, **NOT a `[GhostField]`, NOT an enum**): `!=0` ⇒ input from the SHARED ledger; `0` ⇒ legacy `MachineInput` chain. **Both** deposit the output to the ledger. The ledger total is read **LIVE inside the per-machine loop** (`StorageMath.TotalOf`) so two ledger-fed machines split a finite pool — the 2nd sees the 1st's same-tick withdrawal (a hoisted read would double-spend negative). Reuses the input-limited catch-up (`runs=min(ProductionMath.CyclesDue, affordable)`; ticks via `TickUtil.NonZero`+`IsNewerThan`).
|
||||
- `Fabricator.prefab` re-baked to **1 Ore → 3 Charge / 30 ticks, `InputFromLedger=1`**. Subscene `StructureCatalogAuthoring` re-enables ONLY the Fabricator → **4 entries** (Turret/Wall/Pylon/Fabricator); Harvester/Conveyor stay `null` (reserved M7, code intact). The `MachineInput` buffer is **kept** on the prefab (the production query needs it as a column).
|
||||
- **No `[UpdateBefore/After]` edge** between `TurretFireSystem` and `FabricatorProductionSystem` — a Fabricator deposit lands next tick (~16 ms); ordering for same-tick visibility risks a sorter cycle for no felt gain.
|
||||
|
||||
### The spend is legible (HUD, observe-only)
|
||||
- A 4th **violet** `Charge` chip (flat color, no icon — HudTheme has no Charge sprite, null-safe) reads `ItemId==ResourceId.Charge` in the EXISTING single ledger loop. A **GLOBAL** quiet-turret cue overrides the location banner when `siege && ledger Charge==0 && !onExpedition` → "TURRETS OUT OF CHARGE — build a Fabricator (Ore→Charge)". Global (not per-turret) so the deterministic finite-pool split never reads as one broken turret. `HudSystem` only observes; the cue is derived client-side from already-replicated state.
|
||||
|
||||
### No persistence change
|
||||
- `SaveData` stays **v3**: the recipe + ledger-fed mode are **prefab identity** (`InputFromLedger` baked, not saved). A restored player-built Fabricator Instantiates the baked prefab ⇒ inherits `InputFromLedger=1` for free.
|
||||
|
||||
## Adversarial reviews (both workflowed, ultracode)
|
||||
- **Pre-code** (multi-lens → synthesis): set the contract folded in before coding — Charge as a ledger resource (no new GhostField), the live in-loop read, the single ledger resolve, the atomic soft-fail/refund, the no-ordering-edge trade-off, the global (not per-turret) HUD cue, no SaveData bump.
|
||||
- **Post-code** (3 lenses → adversarial verify → synthesis): **CLEAN** — 2 of 4 candidate findings confirmed, **both quality nits** (no correctness bug): (1) a stale `FabricatorAuthoring` class summary still said "2 Ore → 1 Aether" (doc-only; tooltips were already right) → corrected; (2) the ledger-fed *catch-up* affordance clamp (`CyclesDue>1` bound by the ledger) had no regression pin → added `LedgerFed_Runs_Are_Clamped_To_Affordable_Ledger_Under_CatchUp`. Both folded into this commit for zero debt.
|
||||
|
||||
## Validation
|
||||
- **EditMode: 318/318** (313 prior + 5 EB-2: turret soft-fail / consume-one-Charge / two-turrets-share-finite-pool; ledger-fed withdraw-from-ledger / two-machines-split-via-live-read; + the post-review catch-up-clamp pin). The existing 4 turret tests were updated for the new `RequireForUpdate<ResourceLedger>` (they now seed a Charge pool — else they'd silently soft-fail and fail). Zero console errors at every compile.
|
||||
- **Play (live netcode, unfocused editor, runInBackground ticking):** world creation clean (no ordering cycle); baked `StructureCatalog` = **4 entries**, Fabricator row Prefab≠Null; baked Fabricator recipe = **In=Ore(2)×1, Out=Charge(4)×3, Period=30, InputFromLedger=1**; a live instantiated ledger-fed Fabricator converted **Ore 100→53 (−47) / Charge 0→141 (+47×3)** exactly over 47 periods — the mined-Ore→turret-ammo pipe is live and deterministic. Only console error = a benign FMOD audio-device init (machine audio, unrelated).
|
||||
- **Operator hands-on fun-gate is OPEN:** mine Ore → build a Fabricator (Ore→Charge) → survive a Siege as turrets burn the pool → feel the base run dry ("TURRETS OUT OF CHARGE"). Tune `TurretChargeCostPerShot` + the recipe ratio to taste.
|
||||
|
||||
## Deliberate cuts / notes
|
||||
- **Per-turret magazine/reload, a `[GhostField] AmmoCount`, a dedicated Charge system, and ordering the two systems** — all rejected (see [[DR-033_EB2_Felt_Spend_Charge_Economy]]); the shared-pool + Fabricator-reuse path is leaner and more economically coupled.
|
||||
- **END-2 heads-up:** the Fabricator was repurposed Ore→Aether → **Ore→Charge**, so the default palette no longer has an automated *Aether* source — END-2's win economy must not assume one.
|
||||
|
||||
## Files
|
||||
- Modified (Simulation): `Economy/ResourceNode.cs` (`ResourceId.Charge`), `HomeBase/StorageMath.cs` (`TotalOf`), `Automation/AutomationComponents.cs` (`Fabricator.InputFromLedger`), `Tuning.cs` (`TurretChargeCostPerShot`).
|
||||
- Modified (Server): `Automation/FabricatorProductionSystem.cs` (ledger-fed branch, live in-loop read), `Building/TurretFireSystem.cs` (single ledger resolve + atomic Charge spend / soft-fail / partial-refund).
|
||||
- Modified (Authoring/Client): `Authoring/Automation/FabricatorAuthoring.cs` (recipe defaults + bake `InputFromLedger` + corrected summary), `Client/Presentation/HudSystem.cs` (violet Charge chip + global quiet-turret cue).
|
||||
- Assets: `Prefabs/Fabricator.prefab` (recipe 1 Ore→3 Charge/30t, ledger-fed), `Subscenes/Gameplay.unity` (catalog re-enables Fabricator → 4 entries).
|
||||
- Tests: `FabricatorProductionSystemTests.cs` (+3 ledger-fed cases), `TurretFireSystemTests.cs` (seed Charge pool + 3 spend/soft-fail cases).
|
||||
|
||||
## Next-session intent
|
||||
EB-2 closes the factory→defense pipe (mined Ore is now *spent*). The next Path A braid is **END-1 (a losable Core integrity bar)** — the aggregate base-health meter EB-1/EB-2 deliberately kept out — then **END-2 (explicit win/lose + run resolution)**. If the spend doesn't *bite* in the operator's hands, tune `TurretChargeCostPerShot` ↑ / the recipe ratio ↓ before moving on; if turrets feel starved too easily, the inverse.
|
||||
Reference in New Issue
Block a user