The 2026-08-06 audit found the shipping scene was still the abandoned
co-op-Hades game with LANTERN combat bolted on, and that a third of the
codebase was live code for a direction abandoned on 2026-07-13. Operator
chose deletion over freezing: "everything is saved in source control if
needed. I want the project to be clean."
DELETED (~140 source files, Scripts 335->231, Tests 77->43):
- Enemy variants + boss (H3). ChargerAuthoring / SpitterAuthoring /
SwarmerAuthoring were attached to ZERO prefabs, so LungeState /
SpitterState / SwarmerTag were never baked: ~272 lines of Bursted AI
passes, BossAISystem (261 lines) and the whole MixBands escalation
curve could not match a single chunk at runtime, while 734 lines of
green tests certified them. Both shipping enemy prefabs were already
byte-identical in stats.
- Run/room lifecycle: RunDirector FSM, RunInfo/RunMap/RoomPlan/RoomTag,
route select, portal interact, ready-check, room field/teardown.
- Meta shop, prep loadout, boons (incl. KillRewardSystem and
DashTrailDamageSystem, which existed only to serve boon flags).
- Build palette + structures, shared storage, inventory/equipment
(already recorded PAUSED in CLAUDE.md).
- The HUD panels driving all of the above (HudSystem 1168 -> 610).
KEPT deliberately: BaseGridMath + BaseAnchor (8 systems use PlotCenter
for spawn rings, respawn and dynamic light), the resource ledger +
StorageMath, the save system, region/relevancy. Three of these were in
the delete set until I checked their consumers — worth remembering that
the file-level manifest was wrong about them.
Also folds in audit finding M5: PlayerClass was a second, server-only
copy of the byte FrameId already replicates. It existed for the meta
shop; with that gone, FrameId is the single frame identity.
Harvest is now single-sink (ledger). HarvestMath keeps its shape so
LANTERN's carried-vs-banked cargo split lands in one place, not two.
295/295 EditMode green, zero compile errors. Subscene re-bake and Play
validation follow in the next commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Enum renamed in StatIds.cs (byte values unchanged - serialized definitions/saves
never re-mean); all call sites + doc mentions swept (ClassTraits, PlayerAuthoring,
CharacterStatsDefinition field type, menu/UI, tests). CharacterStatsDefinition SO
CLASS name kept (asset-binding risk; deferred per plan). 390 green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deletes the M7 production systems, automation components/math, authoring, 3
machine prefabs, and 6 test files (-43 tests, 459 green). Trims the automation
paths out of BaseRestoreSystem/SaveStructureScan/BuildPlaceSystem/BuildSendSystem/
HudSystem/HudTheme/StructureCatalogAuthoring/Tuning. RuntimePlacedTag (save
marker, a keeper) re-homed into StructureComponents.cs. StructureType byte codes
stay reserved.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AbilityDefBlob/AbilityDefinition gain WindupTicks. AbilityFireSystem's socket
loop resolves each socket at the historical command (serverTick - WindupTicks)
via GetDataAtTick — a pure fn of replicated data on both worlds (Build Spec §4/
DB-6: no local fireAtTick that would diverge under reprediction; fireCount from
that command, spawn uses the current tick's aim/pos; history gap = no-fire).
WindupTicks set on the 4 non-Blink Sparks (Blink is movement, resolved by
BlinkSystem). 497 EditMode green (no regression). Client telegraph VFX + the
reprediction L3 test are operator-eyes / netcode-world follow-ups.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GymTag + GymEnemyRoster (+ GymRosterAuthoring) bake the gym singletons. New
DebugOp.SpawnEnemy (client SpawnEnemy wrapper + server receive case) spawns a
chosen enemy KIND (Drowner/Grindylow) from the baked roster near the sender —
replacing the wave-roster hack per the roadmap's Enemy-test GYM direction.
GoInGameServerSystem takes a GymTag branch: bypass the meta-catalog spawn guard
(a fresh gym has no CycleDirector) + seed a default Spark loadout on keys 1-4.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Aoe/Hitscan archetype dispatch in AbilityFireSystem (both-worlds cooldown;
server-only ecb.Instantiate — the client-never-instantiates invariant; ZoneEffect
vs DecoyTag chosen by prefab membership). Harpooner REEL as a homing state
(ReelState + ReelSystem re-aim the hit Husk's KnockbackState toward the caster's
live position each tick; EnemyAISystem stays sole Position writer), stamped by
ProjectileDamageSystem on a Reel-flag hit. ZonePulseSystem (enemy-only periodic
AoE, caster attribution, no friendly fire, Vortex pull, born-0 lazy-stamp).
Decoy aggro in EnemyAISystem + DecoySystem despawn. AbilityDefBlob gains
EffectFlags (Reel) + DurationTicks. +3 ZonePulseSystem EditMode tests (497 green).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per Phase1_Combat_Gym_Build_Spec §6 (the Blink Spark = a second, socketed dash):
- AbilityArchetype += Movement (falls through AbilityFireSystem; a sibling system drives it).
- AbilityId += the 5 Phase-1 Sparks (DecoyWisp/HookPull/Vortex/Blink/LightZone).
- BlinkState: predicted, non-replicated blink window (DashState sibling).
- BlinkSystem: velocity blink (never a LocalTransform write), triggered by the Movement-
archetype socket, dash-wins precedence ([UpdateAfter(DashSystem)]), sole writer of its
socket's SocketCooldown row; reads socket loadout/cooldown by entity via lookups (query at 6
args, under the 7-cap). Baked BlinkState on the player.
L1 clean; L2 494/494. Dead until a Blink Spark def is socketed (ability-database SO = next).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per Phase1_Combat_Gym_Build_Spec §7 (additive; legacy single EffectiveAbilityStats + its
fold stay until steps 2/2.5 migrate readers, then retired):
- EffectiveSocketStats: 4-wide derived (non-replicated) per-socket effective-stats buffer.
- PlayerAuthoring bakes it (4 rows).
- StatRecomputeSystem: a SECOND fold loop (separate query, under the 7-arg cap) folds each
socket's SparkId blob base with the SHARED StatModifier band (uniform; per-Spark warping =
Phase 4) into EffectiveSocketStats[i]; empty socket (SparkId 0) -> zeroed default.
Fixed CS1654 by writing through a mutable local copy of the buffer handle.
L1 clean; L2 489/489 (new SocketFold_Folds_Per_Socket_With_Uniform_Band).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Additive socket data model per Phase1_Combat_Gym_Build_Spec §3 (no behaviour; legacy
AbilityRef/AbilityCooldown left intact for the step-2 migration):
- AbilitySocket: cold per-socket loadout buffer (EquipmentSlot-modelled, [GhostField] byte
SparkId, SendToOwnerType.All, 4 rows baked empty).
- SocketCooldown: hot owner-predicted per-socket cooldown (4x [GhostField] uint + Burst-safe
switch indexer; SendToOwnerType.All for remote ability-feel parity with AbilityCooldown).
- FrameId: replicated [GhostField] byte frame/class signal (SendToOwner; the class source the
socket model needs since no single ability id maps 1:1 to a class).
Baked onto the player ghost in PlayerAuthoring. L1 clean compile + domain reload.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review-first netcode slice (design review wf_900e9965-8f0 -> 11 folded;
post-impl wf_5c8299f8-299 clean). A PERMANENT periodic telegraphed AoE in
Blight-biome rooms only; one new [GhostField] uint NextEruptTick.
- Geyser component + GeyserEruptSystem (server): inverted invalid-tick guard
(a baked-0 tick must NEVER erupt -> lazy-stamps born-correct instead of the
party-wiping per-tick barrage), inline both-sides gather (SourceNetworkId=-1),
reschedule = now + period (never +=), never destroyed.
- GeyserTelegraphSystem (client): absolute-tick growing warning disc +
erupt burst on the >0->=<0 crossing, latched per NextEruptTick + arm-guard
(never edge-detects the replicated field -> no phantom on 0->stamp /
relevancy re-entry). Reuses ScorchDecalSystem + DynamicLightSystem.
- Seeded in RoomFieldSystem (Blight-gated on plan.Biome, born-correct staggered
stamp from live ServerTick), GeyserFieldSpawner + authoring wired into the
Gameplay subscene. Geyser.prefab duplicated from ResourceNode (no collider).
- BuildDisc promoted to FeedbackFx (shared with the barrel fuse ring).
- 474/474 EditMode incl. the unstamped-storm regression; live no-storm end-to-end.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 1.5 long-term #8, the LAST 1.5 item (design review wf_e14dd739-069:
both HIGHs + the distinct MEDs verified before the session limit ate the
duplicate verifiers; unverified leftovers reasoned-closed in the spec).
Cover = a BlightClutter ghost (Variant 4) with a baked Environment-layer
BoxCollider: the runtime ghost's collider joins BuildPhysicsWorld ->
blocks the player CC and the enemy sweep while alive, and the gap opens
the instant the entity dies. Both hit paths already break it (the
clutter chassis); 8 hits to carve.
Review folds:
- HIGH: the 07-07 anti-stuck nudge would phase enemies THROUGH cover
after ~1.5s (it exists to prevent room soft-locks). The nudge is now
COVER-AWARE: a SphereCast identifies the blocker; a live BlightClutter
ghost suppresses the phase (no soft-lock possible - the blocker is
destructible); static-geometry wedges still nudge.
- HIGH: yield==durability made tanky cover an economy faucet. Deposit
is DECOUPLED from the decrement at both hit sites: ScrapPerHit=0 =
breaks in Remaining hits, yields NOTHING; fractional POSITIVE yields
still credit >=1 (the B1 immortal-sink test pinned this - zero means
zero, positive floors to 1).
- MEDs: HitRadius 1.2 (visual core, not a shot-eating disc), NO cover
in Boss rooms (no boss depenetration backstop), scatter keep-out >=7u
from the room origin (player landing + portal), Variant=4 explicit
(the clutter block's explosive reroll cannot leak).
- Old static layer removed: 8 WorldColliders_RoomCover colliders
(subscene) + the RoomCover_Visuals root (Game.unity).
Verified live: 3 cover ghosts spawned with SOLID baked colliders;
BLOCK - NudgeUntilTick stayed 0 across 3.5s of forced zero-progress
(old code arms at 1.5s); DEPENETRATE - an enemy shoved inside a live
rock exited to 1.7u (backstop holds against ghost colliders); CARVE -
the rock died to the real sweep and the spot probed OPEN. 470/470
EditMode; console clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The BlightClutter destructible-ghost chain (per-room seeding, unified
DR-018-safe sweep, melee + projectile breaking, personal-inventory
drops) already existed end-to-end - the roadmap item's gap was FEEL:
- Visual: prefab Model mesh swapped SM_Env_Rock_Chunk_01 (read as
scenery) -> SM_Prop_Barrel_01 (FantasyKingdom, same proven atlas
material). Base-pivot barrel offset -1 on the Model child (scatter
y=1 is the capsule plane - the base-pivot float gotcha).
- One-hit pop: prefab Remaining 8 -> 2 (both hit paths decrement by
max(1,(int)ScrapPerHit)=2, so one swing/shot shatters); authoring
default kept in sync.
- Density: MaxClutterPerRoom 6 -> 8.
Verified live: 8 barrels seeded in a combat room, grounded (screenshot);
an injected projectile through the real ResourceHarvestSystem sweep
popped one barrel in ONE hit (8 -> 7) and credited 2 Biomass; 466/466
EditMode, console clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Repoint CLAUDE.md testing example from the deleted HeartbeatSystemTests to HealthApplyDamageSystemTests.
- Stale deleted-system refs: ResourceFieldSpawner/ClutterFieldSpawner(+Authoring) now cite RoomFieldSystem (was ExpeditionFieldSystem); BuildPlaceRequest drops the deleted RegionTransitRequest from its 'mirrors' list.
- Stale package version: 'Netcode 1.13.2' -> 'Netcode 1.x' in ProjectileClassificationSystem + BuildPlaceRequest.
- WorldCollisionComponents: remove the duplicated EnvironmentMask summary line; FeelConfig: fix the mangled 'is hitmap)' sentence.
Comment/doc only; compiles clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- StructureCatalogAuthoring: WallCostOre -> WallCostBiomass (it bakes a Biomass cost; [FormerlySerializedAs] preserves the scene value).
- Harvester/Fabricator authoring: resource-id byte defaults reference ResourceId.Ore/.Charge instead of magic 2/4.
- RegionMath.RegionBoundaryX (= ExpeditionOffsetX*0.5) single-sources the region-flip X used by HudSystem + OnboardingSystem (was 500f in 3 places).
- CharacterComponent.DefaultGroundedSharpness single-sources the CC sharpness 15f (GetDefault, DashSystem, PlayerDeathStateSystem, PlayerCharacterAuthoring).
- InventorySlot [InternalBufferCapacity] references Tuning.InventoryMaxSlots.
- ConnectionMode enum -> byte-const class (project convention; removes the latent enum-in-Burst trap); field + one Seed() param become byte.
- Tuning.ChargerWindupTicks single-sources the Charger telegraph windup (EnemyBaker + TuningConfig.Defaults; was a bare 30 that could drift).
- (TicksPerSecond deliberately NOT added: no seconds->ticks conversion site exists; the tick-count fields are per-authoring designer tunables, so a const would be unreferenced.)
451/451 EditMode tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Delete (unreferenced in any scene, per operator decision):
- TrainingDummy* chain (authoring/spawner/system/components/prefab) + UpgradePickup* chain (authoring/spawner/systems/components/prefab); remove TrainingDummyTag from the 3 live combat queries (AbilityFire/Melee/HealthApplyDamage now key on EnemyTag) and repoint HealthApplyDamageSystemTests to EnemyTag.
- Dead RPC RegionTransitSystem + RegionTransitRequest (no sender, ungated) + its test.
- Heartbeat + HeartbeatSystem (no WorldSystemFilter, ran no-op every tick) + its test.
- BuildSendSystem dead conveyor/pylon dev hooks (s_ConveyorDir, [/] rotation, Place{Pylon,Harvester,Conveyor} statics).
- Deprecated CyclePhase alias consts (Expedition/Defend/Build/*Ticks); repoint WaveSystemTests to Siege/Calm.
- Unread FeelConfig fields (HitFlashDurationMs, RumbleHeavy) + dangling HudUi doc-comments + stale crefs.
451/451 EditMode tests pass. CLAUDE.md HeartbeatSystemTests example ref to be repointed in B7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Restore swallowed [Test] on EnemyAIMathTests.SlideVelocity_DegenerateNormal_DeflectsToTangent (dead regression guard for the shipped enemy-stuck-on-cover fix; now runs + passes).
- Extract shared HarvestMath.DepositYield used by ResourceHarvestSystem + MeleeComboSystem; fixes melee mining silently ignoring per-item StackMax (it hard-coded DefaultStackMax) and hoists the melee ledger-buffer fetch out of the per-target loop.
- StatMath.Apply switches on the raw byte (case (byte)ModOp.X) instead of casting to the enum inside the Bursted fold — removes the latent cross-assembly enum-in-Burst ICE trap.
- Add [Min] guards on CycleDirectorAuthoring loss-critical ints (CoreIntegrityMax>=1 so a mis-authored 0 can't bake an instant-loss core; siege sizes >=0).
456/456 EditMode tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lap-1 Fabricator affordable (StartingOre 50->90); mine prompt shows IN the room;
Return no longer false-completes on death (WasOnExpedition latch + soft timeout,
never the 1-tick Returning edge); HUD keeps room/siege/ammo cues during combat
steps; Welcome needs an explicit confirm; READY panel shows FINAL DEFENSE when
goal-full; dev Force-Each-Launch hidden from public Settings. GoalProgress.Target
4->2 (~10-20 min demo). Onboarding tests updated to the new contracts (456/456).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BossState (server-only) + BossAISystem: sole mover, seek -> telegraphed
radial slam (AttackWindup) -> phase-2 speed + swarmer summon. Excluded from
the EnemyAISystem Charger pass; knockback-immune at all 3 stamp sites;
arena-anchored spawn; scaled Health/HitRadius/AttackRange. Grunt windup now
commits in its last ~30% (whiffs out-of-range); Charger stagger roots; grunt
speed 4.2->5.2; swarmer telegraph synced. SweptMove + zone-enemy spawn-stamp
extracted to shared helpers. Zero new [GhostField].
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Operator: turrets were "super duper cheap", spammable "unlimited", "spaced weirdly", "massive and not textured".
All four were real and independently rooted (placement grid was actually fine).
- Cost: TurretCostOre 10 -> 40 (authoring default + the serialized Gameplay subscene value, which overrides the
code default). A node yields 30 Ore, so a turret is now ~1.3 nodes instead of 1/3 of one.
- Cap: new Tuning.TurretCap=6, enforced server-authoritatively in BuildPlaceSystem (count live Base turrets while
building the occupancy set; reject placement at the cap, same-tick-safe). Was unlimited.
- Model: the 1.6x Synty ballista (~5m on a 1m cell, clipping neighbours) scaled to 0.8 to fit one cell; the C5
BoxCollider shrunk to match (0.8x1.2x0.8, center y 0.6); all 6 sub-renderers swapped off the flat untextured
teal Mat_StructureOwned_Cyan to the Synty atlas PolygonFantasyKingdom_Mat_01_A (textured). Play-verified
TurretCost=40 Ore / cap=6 baked; no exceptions.
Also fixes 3 EditMode tests that pinned the old dash knobs (the prior tuning commit changed iframe 12->14 /
cooldown 45->36 but I committed it without re-running tests): DashSystemTests now derives the expected dash speed
from TuningConfig.Defaults() (robust to future tuning) + asserts now+14/+36; TuningConfigTests pins the new
defaults. 390/390 EditMode green.
Investigation: wf_c6c87dc5-9c3 (turret lane). Operator fork: 40 Ore + cap 6 (stricter).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Player-built structures now physically block enemies (husks walked straight through walls before). Dedicated
"Structure" physics layer (slot 9) so the player passes its own walls while enemies are stopped:
- New WorldCollisionConfig.StructureMask, baked from the "Structure" layer in WorldCollisionAuthoring (mirrors
EnvironmentMask). EnemyAISystem ORs it into the movement sweep filter (CollidesWith = envMask | structMask) —
no new system, same 1-2 SphereCasts per enemy.
- Wall/Turret/Pylon prefabs get a cell-sized BoxCollider on the Structure layer (Wall's existing one relayered).
- Physics matrix: Default x Structure unchecked, so the kinematic player CC (Default) passes its own walls while
the enemy's explicit cast still hits them. Despawn frees collision for free (collider dies with the entity).
Play-verified baked filters: StructMask=512; structure colliders BelongsTo=512, CollidesWith=0xFFFFFFFE
(includes Environment for the enemy cast, EXCLUDES bit 0 so the player passes). 389/389 EditMode, no exceptions.
Server-only/static colliders -> deterministic, no client divergence. SaveData stays v5.
Phase C complete (C5-C7). A visual fun-gate (husk stops at wall, player walks through) is the operator's eyes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Scoping/design-gated (wf_7c5a555e-136). Fixes "the base reads as inert after Phase A":
- C7b objective readout: new replicated ExpeditionObjective{[GhostField] byte State, short Remaining} on the
untagged CycleDirector ghost (cross-region safe). Sole writer ZoneEnemyDirectorSystem, written ABOVE its
early-returns (snapshot-above-early-return) so the HUD never freezes stale. Play-verified it replicates
server->client.
- C7a gate prompt + C7b HUD readout: HudSystem shows "GO TO THE EXPEDITION GATE" / "EXPEDITION IN PROGRESS - N
remaining" / "CLEARED - return to claim", below the siege/overrun overrides.
- C6a Aether upgrade button: un-gated BuildSendSystem.UpgradeAbility (was #if UNITY_EDITOR); HudSystem adds a
MenuUi.Button with live affordability tint (the only Aether sink was U-key only).
- C6c cold-start seed: CycleDirectorSpawnSystem seeds Tuning.StartingOre (50) into the ledger on a NEW game only
(born-correct, pre-Playback), killing the silent turret-before-fabricator deadlock. Play-verified seededOre=50.
- C6b Biomass sink: Wall cost Ore->Biomass (the dead currency now has a home). Play-verified WallCostRes=Biomass.
- C6d palette declutter: hide dead Pylon/Harvester/Conveyor from the build palette + trimmed their dev hotkeys
(catalog/prefabs stay baked, code-intact per DR-020).
389/389 EditMode + clean netcode Play smoke (ghost re-hash OK, no exceptions). SaveData stays v5.
C5 (walls block enemies) is the remaining Phase C item, sequenced separately.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Design-review-gated (wf_ebef4e81-dba, GREEN-WITH-CHANGES). The win-driver moves
from "survive N base sieges" to "clear N expeditions". The review overturned the
literal plan: credit on RETURN, not at the clear edge (clear-edge crediting arms
the undefended final base siege -> uncontestable terminal Loss).
- ExpeditionGateSystem: now the sole production writer of GoalProgress.Charge —
a clamped +1 per cleared expedition folded into the existing once-per-epoch
reward block, reusing the LastRewardedEpoch latch (Ore + Charge share fate) +
a SaveRequest checkpoint. No new latch, no new GhostField, no ordering change.
- CyclePhaseSystem: deleted the survived-siege +1 (the AFK win path). Victory
latch unchanged; GoalReached still arms the final base siege at cap.
- CycleDirectorAuthoring + CycleDirector.prefab: ScheduleEnabled baked OFF
(retaliation-only). A serialized prefab bool ignores the C# field initializer,
so the value is flipped in the prefab, not just the code default.
- Tests: re-pointed CyclePhaseSystemTests + EndgameWinLoseTests survived-siege
assertions; extended ExpeditionGateRewardTests (+1, no-double-credit, clamp).
389/389 EditMode green; clean netcode Play smoke (no sort-cycle, Schedule=0).
SaveData stays v5. Docs: DR-042 build record + forks resolved, CLAUDE.md
base-loop line, Backlog (A done).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes the Combat Depth slice on top of the MC-2 server spine (56cf60cce):
MC-3 impact juice (client, observe-only):
- 7 FeelConfig fields + ResetDefaults; magnitude-scaled player-dealt-hit camera
PunchFov on the enemy-Health-decrease edge (camera-only hit-stop, never timeScale).
- Spitter Kind==2 aim-LANE telegraph (BuildLaneMesh) — reads baked SpitterState
client-side, falls back to a fixed length. True freeze + material flash deferred.
Content / wiring:
- SpitterProjectilePrefabAuthoring (the SpitterProjectilePrefab singleton).
- Both directors rebuilt to a 4-entry KIND-INDEXED roster [Grunt,Charger,Spitter,
Swarmer] + mix/MaxAlive config + the SpitterProjectileConfig singleton in the subscene.
- Real rigged models: EnemySpitter (re-skinned Kaiju, ranged poker) + EnemySwarmerUndead
(Undead-Werewolf, fast/low-HP); grunt/charger keep Werewolf/ChargerMuscle. EnemySpit =
ownerless interpolated ghost (no Health, no collider).
Post-impl adversarial review fixes (wf_febdcfdb-665):
- [MED] in-band fire gate: the Spitter committed its telegraph from ANY range (fired while
advancing from far). Now commits only when sInBand || sCornered (gives CorneredRange a
real read site) — a Spitter out-of-band holds fire and repositions.
- [LOW] EnemyProjectileDamageSystem early-returns on !ServerTick.IsValid (sibling parity).
- [LOW] EnemyAuthoring bake-time guard: errors if a prefab composes both Charger+Spitter
(would match zero AI passes -> never move).
- [LOW] tests: Spitter brain fires from Expedition (kills the Base==0 region false-green);
a direct partition-exclusion test replaces the order-masked claim; added out-of-band +
cornered negative tests.
388/388 EditMode green + two Play smokes (clean boot, fire, swept-hit, region, server==
client; rigged Kaiju spitter bakes + fires with zero console errors). Accepted as-is
(documented in DR-041): global spit soft-cap, co-op punch attribution.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the server-authoritative mechanics for three new enemy archetypes on top of
the Grunt/Charger base, plus the weighted wave-composition that introduces them:
- Spitter: a ranged Husk variant (SpitterState) that holds a preferred range-band
(advance/retreat/hold via EnemyAIMath.BandVelocity) and fires a telegraphed,
dodgeable EnemyProjectile. New server EnemyProjectileMoveSystem (integrate +
store LastStep) + EnemyProjectileDamageSystem (region-filtered swept hit-test
rebuilt from LastStep — DR-018 anti-tunnelling; players use HitRadius, structures
a const radius; at-most-once destroy). Concurrent-spit soft cap, soft-fail retry.
- Swarmer: marker tag + deterministic cluster spawn (1 slot = 1 pack;
EnemyAIMath.ClusterOffset), MaxAlive counts ENTITIES so a pack defers if it
won't fit.
- 4-type weighted mix: MixBands -> ZoneEnemyMath.WaveSlots/KindForSlot/
PackSizeForSlot drives both the expedition director and (fork-4a) the base siege,
with a mandatory MaxAlive cap. Legacy WaveSize/IsChargerSlot kept + parity-tested.
- Discriminator stays component-presence (no enum in Bursted systems): query-
partition guards keep each enemy moved by exactly one EnemyAISystem pass
(sole-Position-writer). EnemyTelegraph.IsCharger -> Kind byte for the client cue.
New authoring (Spitter/Swarmer/EnemyProjectile) + expanded director authorings with
tunable mix/cluster defaults. 13 new EditMode tests (mix composition + legacy parity,
band/cluster math, projectile move + cross-region + swept anti-tunnelling regressions);
full suite green before commit.
Dormant until the prefab/subscene wiring lands (next): the new systems guard on
TryGetSingleton/RequireForUpdate, so with no prefabs wired the new types stay inert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reactivate the dormant Expedition region as a procedural combat venue.
v1 loop: walk the gate -> fight an epoch-seeded enemy wave in the
expedition -> clear -> return -> flat Ore reward (once per epoch) ->
escalated retaliation base siege.
- New sim types: ZoneEnemyTag, ZoneEnemyDirector (+ ZoneEnemyPrefab
buffer), ZoneEnemyState, ZoneEnemyMath (grunt->charger composition
by epoch). ZoneEnemyDirectorSystem (server, Burst): drip-spawns the
wave at a deterministic ring under a MaxAlive cap while a player is
out and the base is Calm; marks ClearedThisEpoch on a real clear.
[UpdateAfter(ExpeditionFieldSystem)] only (avoids a sort cycle).
- BLOCKER 1: EnemyAISystem region-filters target selection (player +
structure snapshots gain parallel region lists; no base structures /
no Core fallback for expedition husks).
- BLOCKER 3: WaveSystem, ThreatDirectorSystem timeout cull, and
CyclePhaseSystem DefendCleared + Core-breach cull all count/cull
RegionTag{Base} husks only (the breach cull was caught region-blind
by the post-impl review: a base breach wiped the live expedition
wave and spuriously paid the reward).
- BLOCKER 4: reward de-duped via CycleRuntime.LastRewardedEpoch +
ClearedThisEpoch; ExpeditionGateSystem deposits RewardOre once/epoch.
- ExpeditionFieldSystem teardown also culls zone enemies + region-
guards the clutter loop. Subscene wired with the director + roster.
368/368 EditMode green + clean netcode Play smoke. Docs: DR-040 ->
built, session log, CLAUDE.md cross-region tag-reaudit rule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Four playtest do-now wins:
- Enemy health bars: pooled world-space Canvas, on-damage-sticky + fade,
always-on <25% HP (CombatFeedbackSystem; no new replication).
- Telegraph fix: new baked client-safe EnemyTelegraph sizes the danger-cone ramp
per enemy (0->1 ending at impact, fixes the Charger plateau); windup 18->22;
a windup scale-pulse.
- Build-mode toggle: BuildPaletteState.PaletteOpen hides the palette by default,
Tab / gamepad-Y toggles, with a discovery chip (HudSystem/BuildSendSystem).
- Charger committed-lunge tell: [GhostEnabledBit] IsLunging derived once/tick from
LungeState (the Dead idiom); the danger cone persists through the lunge.
345/345 EditMode (+3 IsLunging derive tests); Play-validated: ghost-hash change
did not break the handshake, bake correct (telegraph on all enemies, IsLunging
baked-disabled on the Charger, replicated to client), no runtime errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
At GoalProgress.Charge>=Target a new server-only GoalReachedSystem arms a larger final siege (x live FinalSiegeMultiplier) and flips RunPhase=FinalDefense; CyclePhaseSystem latches a REPLICATED RunOutcome (Victory on clear / Loss on Core breach) and halts the director. RunOutcome is a [GhostField] byte on the global CycleDirector ghost (the client banner observes it); RunPhase stays server-only. ThreatDirector/CoreRestore/CoreDamage halt once decided; SiegeTimeout is off during the final siege. SaveData v5 persists the outcome so a won/lost run loads finished. GoalProgress.Target 10->4. Completes Path A's spine. See DR-036.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds CoreIntegrity{[GhostField] Current,Max,OverrunTick} on the GLOBAL
CycleDirector ghost (no new ghost/relevancy). CoreDamageSystem (server,
after EnemyAISystem): a Husk within ~3u of PlotCenter drains + is consumed;
CoreRestoreSystem regenerates only in Calm. The SOFT-loss edge lives inside
CyclePhaseSystem (sole Phase writer): Current<=0 in Siege flips to Calm with
NO goal reward, StorageMath.DrainFraction drains the shared ledger, all Husks
despawn, and OverrunTick is stamped (a transient HUD-flash pulse, not a
latching outcome - the Victory latch is END-2's). EnemyAISystem treats the
Core as a FALLBACK target so an undefended base is overrun instead of idling.
SaveData -> v4 persists CoreCurrent (0 -> born full, the EB-1 HP sentinel);
3 live TuningConfig knobs + a red HUD Core bar. Soft-loss + targeting +
breach-resolution forks operator-locked.
See DR-034.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mined Ore now has an ongoing sink: a ledger-fed Fabricator converts Ore->Charge
(1 Ore -> 3 Charge / 30t) and turrets spend Charge per shot, soft-failing (no
shot, no cooldown burn) when the shared pool runs dry.
- ResourceId.Charge=4 rides the existing [GhostField] StorageEntry ledger (no new wire).
- TurretFireSystem: single ledger resolve + atomic spend / soft-fail / partial-refund.
- Fabricator.InputFromLedger (byte, server-only) feeds input from the shared ledger,
read live in-loop so two machines split a finite pool; both modes deposit to ledger.
- HudSystem: violet Charge chip + global quiet-turret cue when siege && Charge==0.
- StorageMath.TotalOf backs the affordability read; catalog re-enables the Fabricator (4 entries).
See DR-033.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Structures (Turret/Wall/Pylon) reuse the combat spine: authoring bakes Health(GhostField)+DamageEvent buffer+a Destructible tag (no HitRadius -> no friendly projectile fire; no EffectiveCharacterStats -> clamp-to-0). HealthApplyDamageSystem destroys a Destructible at 0 (occupancy auto-frees). EnemyAISystem fortress-targets the weighted-nearest of players+structures via the shared EnemyAIMath.PickWeightedNearest (StructureAggroWeight TuningConfig knob, <1 prefers structures, squared factor; snapshot above the early-return so an undefended base is razed). Persistence v3: per-structure HP threaded through 5 sites (SaveData/PendingStructure/scan-guarded/BaseRestore same-ECB born-correct/WorldLauncher via SaveApply.ToPending); SaveService floor-gate [2,3] loads old saves. Loss feedback: proximity-gated StructureFeedbackSystem; CombatFeedbackSystem suppressed for structures. Pre-code review caught the DamageEvent-buffer crash blocker + 8 majors; post-code review clean. See DR-032.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Consolidate the divorced combat + economy halves into one base-local loop. BaseFieldSpawnSystem tops up RegionTag{Base} Ore nodes around the plot; harvest routes Base->shared ledger and Expedition/untagged->personal inventory for BOTH the projectile (ResourceHarvestSystem) and melee (MeleeComboSystem server-only block, writes Remaining back for VFX). Activate the reserved Schedule source in ThreatDirectorSystem so base sieges arm WITHOUT an expedition trip (the loop-closer: previously zero waves ever attacked a base-only player). Region-filter the ExpeditionFieldSystem teardown so it no longer wipes the permanent base field. HudSystem shows phase-aware loop copy. See DR-031.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
byte Archetype on AbilityDefBlob (AbilityArchetype enum: Projectile/Hitscan/Cone/Aoe), authored on AbilityDefinition + baked in AbilityDatabaseAuthoring, read at dispatch in AbilityFireSystem -- NOT folded through EffectiveAbilityStats/StatRecomputeSystem (static identity, not a tunable stat; MC-4 review BURST-1). All current abilities are Projectile (0) -> zero behaviour change; the dispatch read-point is the de-risk spike for MC-6 hitscan/cone/aoe.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Melee combo (left-click / pad-West) becomes the player's primary verb; the ranged projectile is demoted to right-click / pad-left-trigger. Predicted, owner-replicated combo Step (path-dependent -> [GhostField] anchor + absolute-write idempotency, NOT derived like the dash), server-only cleave mirroring ProjectileDamageSystem (SourceTick-stamped DamageEvent + KnockbackState), dash-cancellable movement-commit, 9 live TuningConfig knobs, and swing juice scaling with the combo step. The MC-6 archetype byte is deferred (the melee is its own verb). See DR-030.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Data-driven ItemDatabase catalog + per-player replicated InventorySlot ([GhostField] OwnerSendType.All, a StatModifier twin). Harvest reroutes to the firing player's personal inventory (optional ComponentLookup<GhostOwner> in ResourceHarvestSystem; remainder/un-owned -> ledger); the G-key InventoryDepositRequest RPC moves the bag into the shared ledger the build economy spends. Catalog asset (Aether/Ore/Biomass + Stone Pickaxe) wired into the Gameplay subscene; read-only HUD inventory panel. ushort ItemId subsumes ResourceId; byte Category/Tier baked for gear-tier progression. Session-only (no SaveData bump).
Play-validated host+client: catalog baked into both worlds, the re-baked player ghost carries InventorySlot with a clean handshake, a server write replicates to the client owner, the deposit RPC round-trips, and the HUD renders catalog names. See DR-026.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Server-only production chains (never predicted): components + server systems + pure byte-only math (ProductionMath/ConveyorMath/MachineSlotMath), authoring + 3 machine prefabs wired into the Gameplay subscene, StructureCatalog rows, BuildPlace Direction/RuntimePlacedTag, Tuning, and 35 EditMode tests (catch-up gating, conveyor shuffle-invariance, SaveData v2 round-trip).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>