Docs: END-1 session log + DR-034; CLAUDE.md losable-core bullet

DR-034 records the losable-Core decisions: CoreIntegrity on the global ghost,
the soft-loss edge inside CyclePhaseSystem, the Core-as-fallback-target and
despawn-on-breach forks, the transient OverrunTick (vs END-2's latching
outcome), and SaveData v4. Session log captures the build + validation
(330/330 EditMode; Play-verified server==client drain->regen->replicate).
CLAUDE.md adds the END-1 bullet; EB-1/EB-2/inventory bullets condensed
net-neutral to stay under the size budget.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 21:52:03 -07:00
parent 037ff66490
commit f7c63b6f41
3 changed files with 132 additions and 3 deletions
@@ -0,0 +1,68 @@
---
date: 2026-06-12
type: session
tags:
- session
- endgame
- netcode
- structures
- persistence
- end-1
permalink: gamevault/07-sessions/2026/2026-06-12-end1-losable-core
---
# END-1 — The base can be lost: a losable Engine Core with integrity
> Path A milestone (first **Endgame** beat); forks locked in [[DR-029_Path_A_Fork_Locks]] + a 2026-06-12 present-the-forks pass. Full [[dots-dev]] Feature track. Locked → [[DR-034_END1_Losable_Core]]. Continues [[2026-06-12_EB2_Felt_Spend]]. Spec: [[Path_to_Fun#END-1 — The base can be lost: a Core with integrity]].
## The hole END-1 closes
EB-1 made structures losable; EB-2 made defense *cost* the harvest. But there was no **aggregate** base-loss condition — a siege could grind machines without ever *taking the base*. END-1 adds the **Engine Core**: a base-integrity meter a breaking-through siege drains. The locked **soft loss** gives the siege teeth without a hard game-over.
## Forks (present-the-forks ritual, 2026-06-12)
DR-029 had already locked the big END-1 forks (soft loss, Core-bar-only, persist-wounded). Two genuinely-open implementation forks were surfaced + operator-decided:
- **How Husks reach the Core** → **FALLBACK target** (sought only when no live player/structure). Found while reading `EnemyAISystem`: it early-returns to *idle* when no player/structure is alive, so an undefended base would freeze the swarm — the Core must be reachable for the "walks to the Engine" fun-gate. Fallback preserves EB-1 structure stakes (the Core is the *last* thing).
- **Breach resolution** → **despawn remaining Husks** (clean reset-to-recover; the wave disperses).
## What shipped — ride the global ghost, no new wire
### `CoreIntegrity` on the GLOBAL CycleDirector ghost
- `{[GhostField] int Current, Max; [GhostField] uint OverrunTick}` — rides the untagged director ghost already carrying `CycleState`/`GoalProgress`/the ledger. **No new ghost, no relevancy.** `Max` baked from `CycleDirectorAuthoring` (100); born-correct `Current` at spawn (full, or persisted wounded). The new `[GhostField]` re-hashes the runtime-spawned ghost — Play-verified server==client (born full 100/100).
### Two server-only plain-group systems
- `CoreDamageSystem` `[UpdateAfter(EnemyAISystem)]`: a Husk within ~3u of `PlotCenter` drains `CoreDamagePerHusk` and is **consumed** (despawn, at-most-once ECB). Idles at `Current<=0`.
- `CoreRestoreSystem`: +1 every `CoreRegenIntervalTicks` **ONLY in Calm** (deterministic `now % interval`). A chipped Core heals between sieges.
### SOFT-loss edge — inside `CyclePhaseSystem` (sole Phase writer)
- Checked BEFORE survival: `Current<=0` in Siege → flip **Calm** (**NO** goal reward), `StorageMath.DrainFraction` the shared ledger by `CoreOverrunDrainPct` (0.5), **despawn all remaining Husks** + reset `WaveState`, stamp `OverrunTick`, autosave. Kept inside `CyclePhaseSystem` so it stays the sole Phase/WaveState writer (no second Phase writer → no invisible sort-cycle). New pure `StorageMath.DrainFraction` (floored per row, drops zeroed rows; unit-tested).
### Core = a FALLBACK target in `EnemyAISystem`
- When no live player/structure remains, Husks seek `PlotCenter` (target `Entity.Null`); both `DamageEvent` strike appends are guarded `!= Entity.Null` (the Core takes damage only via `CoreDamageSystem` proximity). Applies to both the Grunt and Charger passes.
### Transient overrun pulse, not a latching outcome
- `OverrunTick` is a TRANSIENT pulse (deliberate deviation from the spec's literal `RunOutcome{Overrun}`): a soft loss is **non-terminal**, so a latching terminal outcome is the wrong shape. The HUD edge-detects the tick change → ~3.5s "BASE OVERRUN — resources lost; the Core will recover" flash (overrides the location line, after the EB-2 cue so it wins). The latching `RunOutcome` (Victory) is **END-2's** job.
### Persistence — SaveData v4 (additive)
- `SaveData`**v4** adds `CoreCurrent` (`MinLoadableVersion` stays 2). Pre-v4 save lacks the field → JsonUtility 0 → born-correct maps 0 → baked Max (full), reusing EB-1's HP `0→Max` sentinel. Round-tripped through `PendingSave` + both write paths (`SaveWriteSystem` autosave + `WorldLauncher` quit-to-menu).
### HUD + tuning
- Red Core-integrity bar mirroring the GoalProgress bar (`CoreRed`, danger-lerp as it drops). 3 live `TuningConfig` knobs (`CoreDamagePerHusk`/`CoreRegenIntervalTicks`/`CoreOverrunDrainPct`) + `DebugOverlay` rows; `CoreReachRadius` a structural const.
## Validation
- **EditMode: 330/330** (318 prior + 12 END-1: 3 `DrainFraction`, 2 SaveData v4 / additive-CoreCurrent, 2 CyclePhase overrun soft-loss [ends siege / drains ledger / despawns husks / no goal charge / resolves-once], 5 CoreSystems [breach drains+consumes / idles-at-0 / regen-in-Calm-only / no-regen-in-Siege / caps-at-Max]). The existing `StructureSave_HP_RoundTrips_And_Writes_V3` was updated for the v4 bump (now asserts `SaveData.CurrentVersion`); TuningConfig golden pin extended with the 3 Core defaults.
- **Play (live netcode, focused editor):** world creation clean (no ordering cycle from the new `[UpdateAfter]` edges); `CoreIntegrity` replicates server==client (born 100/100); both new systems present in `ServerWorld`, absent in `ClientWorld` (server-only filter correct); a live drain test — 12 test Husks at the Core drained 100→0 (all consumed), then `CoreRestoreSystem` regenerated 0→23 in Calm, replicating server(23)→client(22) at the expected ~1-tick interpolation latency. Zero console errors throughout.
- **Operator hands-on fun-gate is OPEN:** in a live Host+client siege, let a Husk breach to the Engine and watch the integrity bar tick down; fail to defend → "BASE OVERRUN", lose resources, regroup in Calm as the Core regenerates. Tune `CoreDamagePerHusk` / regen / drain-pct to taste.
## Deliberate cuts / notes
- **Latching `RunOutcome` / hard-rollback lose mode** — NOT built (DR-029 locked soft). `RunOutcome` is END-2's (the Victory latch + win banner reuse the `OverrunTick`/banner path).
- **0-integrity-saved → restores full** — a base saved at exactly `Current=0` comes back full (the 0 sentinel = "unset → baked Max", same as EB-1 HP). Negligible: the Core regenerates in Calm anyway, and the autosave-on-breach is immediately followed by Calm regen, so a quit usually saves `Current>0`.
## Files
- New (Simulation): `World/CoreIntegrity.cs`. New (Server): `World/CoreDamageSystem.cs`, `World/CoreRestoreSystem.cs`. New (Tests): `CoreSystemsTests.cs`.
- Modified (Simulation): `HomeBase/StorageMath.cs` (`DrainFraction`), `Debug/TuningConfig.cs` (+3 Core knobs + wire), `Persistence/SaveData.cs` (v4 + `CoreCurrent`), `Persistence/SaveComponents.cs` (`PendingSave.CoreCurrent`).
- Modified (Server): `World/CycleDirectorSpawnSystem.cs` (born-correct Core), `World/CyclePhaseSystem.cs` (soft-loss edge), `Combat/EnemyAISystem.cs` (Core fallback target + strike guards), `Persistence/SaveWriteSystem.cs` (persist `CoreCurrent`).
- Modified (Authoring/Client): `Authoring/World/CycleDirectorAuthoring.cs` (bake `CoreIntegrityMax`), `Client/UI/WorldLauncher.cs` (stage + quit-save `CoreCurrent`), `Client/Presentation/HudSystem.cs` (Core bar + overrun flash), `Client/Debug/DebugOverlay.cs` (3 knob rows).
- Tests: `SavePersistenceTests.cs` (+2, +v4 fix), `StorageMathTests.cs` (+3), `CyclePhaseSystemTests.cs` (+2), `TuningConfigTests.cs` (golden pin +3).
- Docs: `CLAUDE.md` (END-1 bullet; EB-1/EB-2/inventory condensed net-neutral), `DR-034`, this log.
## Next-session intent
END-1 gives the base a real lose condition. Next Path A beat is **END-2** — the goal charge arms a final escalating siege and surviving it fires the WIN beat (the latching `RunOutcome{Victory}` + win banner, reusing END-1's pulse/banner path) — which **completes Path A** and triggers the mandatory **Decision Gate**. If defending the Core doesn't feel engaging in the operator's hands, tune the damage/regen/drain knobs before END-2.