LANTERN purge B1: delete the automation chain (Harvester/Conveyor/Fabricator)
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>
This commit is contained in:
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4eba886d11c07eb4d97ca0d821a1560f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,91 +0,0 @@
|
||||
using Unity.Entities;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// A fixed-yield resource generator — the FRONT of the M7 auto-gather chain (Harvester -> Conveyor ->
|
||||
/// Fabricator). Each period it deposits <see cref="Yield"/> of <see cref="ResourceId"/> into its OWN
|
||||
/// server-only <see cref="MachineOutput"/> buffer (a conveyor pulls it onward). Server-only data (NO
|
||||
/// [GhostField]); the client only ever sees <c>PlacedStructure.Type</c>. Reuses
|
||||
/// <c>PlacedStructure.NextTick</c>/<c>LastProcessedTick</c> for the deterministic, within-session catch-up
|
||||
/// cadence (see <c>HarvesterProductionSystem</c>).
|
||||
/// </summary>
|
||||
public struct Harvester : IComponentData
|
||||
{
|
||||
/// <summary>Resource id produced (a byte; see <see cref="ResourceId"/>).</summary>
|
||||
public byte ResourceId;
|
||||
/// <summary>Units produced per elapsed period.</summary>
|
||||
public int Yield;
|
||||
/// <summary>Server ticks between productions.</summary>
|
||||
public int PeriodTicks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A recipe machine — the BACK of the M7 chain. Consumes <see cref="InAmount"/> of <see cref="InResourceId"/>
|
||||
/// per run from its own <see cref="MachineInput"/> buffer (fed by a conveyor) and deposits <see cref="OutAmount"/>
|
||||
/// of <see cref="OutResourceId"/> into the GLOBAL ledger. Strictly input-limited (never mints from an empty
|
||||
/// slot). Server-only data.
|
||||
/// </summary>
|
||||
public struct Fabricator : IComponentData
|
||||
{
|
||||
public byte InResourceId;
|
||||
public int InAmount;
|
||||
public byte OutResourceId;
|
||||
public int OutAmount;
|
||||
public int PeriodTicks;
|
||||
|
||||
/// <summary>EB-2: 0 = consume the input from the MachineInput buffer (the M7 conveyor chain); !=0 = consume
|
||||
/// the input from the SHARED ledger (a base-loop ledger-fed Fabricator, e.g. Ore -> Charge). Server-only, NO [GhostField].</summary>
|
||||
public byte InputFromLedger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A directional transport belt — the MIDDLE of the M7 chain. Each period it pulls one item off an adjacent
|
||||
/// upstream <see cref="MachineOutput"/> (when empty) and advances a held <see cref="ConveyorItem"/> exactly one
|
||||
/// cell toward <see cref="Direction"/>. <see cref="Direction"/> is a byte (0=+X,1=-X,2=+Z,3=-Z) — never an enum
|
||||
/// (the cross-assembly enum-in-Burst hazard). Server-only data.
|
||||
/// </summary>
|
||||
public struct Conveyor : IComponentData
|
||||
{
|
||||
/// <summary>Belt facing: 0=+X, 1=-X, 2=+Z, 3=-Z (see <c>ConveyorMath.DirOffset</c>).</summary>
|
||||
public byte Direction;
|
||||
public int PeriodTicks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A machine's INPUT staging buffer (server-only, NO [GhostField] -> never replicated). A DISTINCT element type
|
||||
/// from the global ledger's <see cref="StorageEntry"/> (so <c>GetSingleton<StorageEntry></c> stays
|
||||
/// unambiguous) and from <see cref="MachineOutput"/> (so a machine can carry both without a buffer-type clash).
|
||||
/// </summary>
|
||||
public struct MachineInput : IBufferElementData
|
||||
{
|
||||
public byte ResourceId;
|
||||
public int Count;
|
||||
}
|
||||
|
||||
/// <summary>A machine's OUTPUT staging buffer (server-only, NO [GhostField]). See <see cref="MachineInput"/>.</summary>
|
||||
public struct MachineOutput : IBufferElementData
|
||||
{
|
||||
public byte ResourceId;
|
||||
public int Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The single in-flight item a conveyor carries. An ENABLEABLE component (enabled = the belt is occupied) so a
|
||||
/// transport step is a bit-flip + field copy, never a structural change. Baked DISABLED (an empty belt).
|
||||
/// Server-only.
|
||||
/// </summary>
|
||||
public struct ConveyorItem : IComponentData, IEnableableComponent
|
||||
{
|
||||
public byte ResourceId;
|
||||
public int Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks a structure PLACED by a player at runtime (BuildPlaceSystem) or restored from a save — i.e. the
|
||||
/// persistable set, as opposed to anything baked into the subscene. SaveWriteSystem scans only these and
|
||||
/// BaseRestoreSystem re-adds the tag, so save/restore is the single source of truth for player builds.
|
||||
/// Server-only (not replicated).
|
||||
/// </summary>
|
||||
public struct RuntimePlacedTag : IComponentData { }
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6eeef378186b39d41a2db7adcc620dd9
|
||||
@@ -1,97 +0,0 @@
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Pure, deterministic, ORDER-INDEPENDENT conveyor move resolver (the server <c>ConveyorTransportSystem</c>
|
||||
/// applies the result). Determinism: sources are processed sorted by <see cref="CellKey"/> (NEVER hashmap
|
||||
/// order); a destination belt cell accepts AT MOST ONE item and only if it was EMPTY in the pre-move snapshot
|
||||
/// (double-buffering -> exactly one cell/tick); ties break to the lowest-CellKey source and losers STALL with no
|
||||
/// loss; machine-input SINK cells always accept (a merge). World-free so it is exhaustively unit-tested.
|
||||
/// </summary>
|
||||
public static class ConveyorMath
|
||||
{
|
||||
/// <summary>Cardinal grid step for a belt direction byte (0=+X,1=-X,2=+Z,3=-Z).</summary>
|
||||
public static int2 DirOffset(byte dir)
|
||||
{
|
||||
switch (dir)
|
||||
{
|
||||
case 1: return new int2(-1, 0);
|
||||
case 2: return new int2(0, 1);
|
||||
case 3: return new int2(0, -1);
|
||||
default: return new int2(1, 0); // 0 = +X
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>A stable, collision-free total order over grid cells (the deterministic tie-break key).</summary>
|
||||
public static long CellKey(int2 cell) => ((long)cell.x << 32) | (uint)cell.y;
|
||||
|
||||
/// <summary>
|
||||
/// Resolve, for each belt holding an item, whether it advances one cell toward its direction this tick.
|
||||
/// Inputs are read-only snapshots; outputs are the accepted moves (<paramref name="outMoveSrcIdx"/> ->
|
||||
/// <paramref name="outMoveDst"/>), length <paramref name="moveCount"/>. A move is accepted when the
|
||||
/// destination is a SINK cell (always, a merge) or an EMPTY, unclaimed belt cell. Sources are iterated in
|
||||
/// CellKey order so the result is identical regardless of input array order. Scratch is Temp + disposed.
|
||||
/// </summary>
|
||||
public static void ResolveMoves(
|
||||
NativeArray<int2> srcCells, NativeArray<byte> dirs,
|
||||
NativeArray<int> itemRes, NativeArray<int> itemCnt,
|
||||
NativeHashMap<int2, int> cellToIndex, NativeHashSet<int2> sinkCells,
|
||||
NativeArray<int2> outMoveDst, NativeArray<int> outMoveSrcIdx, out int moveCount)
|
||||
{
|
||||
int n = srcCells.Length;
|
||||
moveCount = 0;
|
||||
|
||||
// Stable iteration order = sources sorted by CellKey (insertion sort; n is small).
|
||||
var order = new NativeArray<int>(n, Allocator.Temp);
|
||||
for (int i = 0; i < n; i++) order[i] = i;
|
||||
for (int i = 1; i < n; i++)
|
||||
{
|
||||
int cur = order[i];
|
||||
long curKey = CellKey(srcCells[cur]);
|
||||
int j = i - 1;
|
||||
while (j >= 0 && CellKey(srcCells[order[j]]) > curKey)
|
||||
{
|
||||
order[j + 1] = order[j];
|
||||
j--;
|
||||
}
|
||||
order[j + 1] = cur;
|
||||
}
|
||||
|
||||
var claimed = new NativeHashSet<int2>(n, Allocator.Temp);
|
||||
for (int oi = 0; oi < n; oi++)
|
||||
{
|
||||
int i = order[oi];
|
||||
if (itemCnt[i] <= 0) continue; // nothing to move
|
||||
|
||||
int2 dst = srcCells[i] + DirOffset(dirs[i]);
|
||||
|
||||
bool accept = false;
|
||||
bool isSink = sinkCells.Contains(dst);
|
||||
if (isSink)
|
||||
{
|
||||
accept = true; // sinks merge -> unlimited acceptors, never claimed
|
||||
}
|
||||
else if (cellToIndex.TryGetValue(dst, out int dstIdx))
|
||||
{
|
||||
// dst is a belt cell: accept only if EMPTY in the snapshot AND not already claimed this tick.
|
||||
if (itemCnt[dstIdx] == 0 && !claimed.Contains(dst))
|
||||
accept = true;
|
||||
}
|
||||
// else: dst is neither a belt nor a sink -> dead end -> stall.
|
||||
|
||||
if (accept)
|
||||
{
|
||||
if (!isSink) claimed.Add(dst);
|
||||
outMoveDst[moveCount] = dst;
|
||||
outMoveSrcIdx[moveCount] = i;
|
||||
moveCount++;
|
||||
}
|
||||
}
|
||||
|
||||
order.Dispose();
|
||||
claimed.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 251361cf456e888459d473b5fedf7c4a
|
||||
@@ -1,85 +0,0 @@
|
||||
using Unity.Entities;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Pure, deterministic deposit/withdraw/total helpers for a machine's server-only <see cref="MachineInput"/> /
|
||||
/// <see cref="MachineOutput"/> staging buffers — the byte-id, non-replicated twin of <see cref="StorageMath"/>
|
||||
/// (which serves the [GhostField] global <see cref="StorageEntry"/> ledger). No RNG/wall-clock. DynamicBuffer is
|
||||
/// a handle, so mutations apply to the underlying entity buffer. Overloaded per buffer type because the two
|
||||
/// element types are deliberately distinct (a machine can carry both without a singleton-buffer clash). Deposit
|
||||
/// is a no-op for count <= 0 or resource id 0; Withdraw clamps to available and drops a row at zero.
|
||||
/// </summary>
|
||||
public static class MachineSlotMath
|
||||
{
|
||||
// ---- MachineOutput ----
|
||||
public static void Deposit(DynamicBuffer<MachineOutput> buffer, byte resourceId, int count)
|
||||
{
|
||||
if (count <= 0 || resourceId == 0) return;
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
if (buffer[i].ResourceId == resourceId)
|
||||
{
|
||||
var e = buffer[i]; e.Count += count; buffer[i] = e; return;
|
||||
}
|
||||
buffer.Add(new MachineOutput { ResourceId = resourceId, Count = count });
|
||||
}
|
||||
|
||||
public static int Withdraw(DynamicBuffer<MachineOutput> buffer, byte resourceId, int count)
|
||||
{
|
||||
if (count <= 0 || resourceId == 0) return 0;
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
if (buffer[i].ResourceId == resourceId)
|
||||
{
|
||||
var e = buffer[i];
|
||||
int taken = e.Count < count ? e.Count : count;
|
||||
e.Count -= taken;
|
||||
if (e.Count <= 0) buffer.RemoveAt(i); else buffer[i] = e;
|
||||
return taken;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int TotalOf(DynamicBuffer<MachineOutput> buffer, byte resourceId)
|
||||
{
|
||||
int total = 0;
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
if (buffer[i].ResourceId == resourceId) total += buffer[i].Count;
|
||||
return total;
|
||||
}
|
||||
|
||||
// ---- MachineInput ----
|
||||
public static void Deposit(DynamicBuffer<MachineInput> buffer, byte resourceId, int count)
|
||||
{
|
||||
if (count <= 0 || resourceId == 0) return;
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
if (buffer[i].ResourceId == resourceId)
|
||||
{
|
||||
var e = buffer[i]; e.Count += count; buffer[i] = e; return;
|
||||
}
|
||||
buffer.Add(new MachineInput { ResourceId = resourceId, Count = count });
|
||||
}
|
||||
|
||||
public static int Withdraw(DynamicBuffer<MachineInput> buffer, byte resourceId, int count)
|
||||
{
|
||||
if (count <= 0 || resourceId == 0) return 0;
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
if (buffer[i].ResourceId == resourceId)
|
||||
{
|
||||
var e = buffer[i];
|
||||
int taken = e.Count < count ? e.Count : count;
|
||||
e.Count -= taken;
|
||||
if (e.Count <= 0) buffer.RemoveAt(i); else buffer[i] = e;
|
||||
return taken;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int TotalOf(DynamicBuffer<MachineInput> buffer, byte resourceId)
|
||||
{
|
||||
int total = 0;
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
if (buffer[i].ResourceId == resourceId) total += buffer[i].Count;
|
||||
return total;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16b6aef96031f54469f05044a2c18e66
|
||||
@@ -1,51 +0,0 @@
|
||||
using Unity.Mathematics;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Pure, deterministic catch-up + cooldown math shared by the M7 production systems (Harvester/Conveyor/
|
||||
/// Fabricator). No RNG/wall-clock -> server-authoritative. The single GATED catch-up path: a never-processed
|
||||
/// machine (<see cref="NeedsInit"/>) initializes first; a cooling machine yields 0; a due machine yields
|
||||
/// floor(elapsed/period) clamped to [0, maxCatchup]; period is guarded by max(1,...). Cooldown is persisted as
|
||||
/// REMAINING ticks (epoch-independent) so a save survives the server-tick origin reset on a fresh session.
|
||||
/// </summary>
|
||||
public static class ProductionMath
|
||||
{
|
||||
/// <summary>True for a never-processed machine (baked/just-placed) — initialize the baseline before producing.</summary>
|
||||
public static bool NeedsInit(uint lastProcessedTick) => lastProcessedTick == 0u;
|
||||
|
||||
/// <summary>
|
||||
/// Cycles to award THIS process. 0 if cooling (<paramref name="nextTick"/> newer than <paramref name="now"/>)
|
||||
/// or nothing elapsed; otherwise floor(elapsed/period) clamped to [0, <paramref name="maxCatchup"/>].
|
||||
/// <paramref name="nextTick"/>==0 is the inactive sentinel (never read as a future cooling tick). The lower
|
||||
/// bound is 0 (not 1): when genuinely due the NextTick gate guarantees elapsed>=period, so a sub-period
|
||||
/// edge (e.g. a freshly restored remaining==0 machine) floors to 0 rather than minting prematurely.
|
||||
/// <paramref name="period"/> is guarded by max(1,...) so a 0 never divides.
|
||||
/// </summary>
|
||||
public static int CyclesDue(NetworkTick now, uint nextTick, uint lastProcessedTick, int period, int maxCatchup)
|
||||
{
|
||||
int p = math.max(1, period);
|
||||
|
||||
if (nextTick != 0u)
|
||||
{
|
||||
var next = new NetworkTick(nextTick);
|
||||
if (next.IsValid && next.IsNewerThan(now))
|
||||
return 0; // still cooling down
|
||||
}
|
||||
|
||||
int since = now.TicksSince(new NetworkTick(TickUtil.NonZero(lastProcessedTick)));
|
||||
if (since <= 0)
|
||||
return 0;
|
||||
|
||||
return math.clamp(since / p, 0, maxCatchup);
|
||||
}
|
||||
|
||||
/// <summary>Remaining cooldown ticks to PERSIST (epoch-independent): 0 if inactive or already due, else nextTick-now.</summary>
|
||||
public static uint RemainingTicks(uint nextTick, uint nowTick) =>
|
||||
nextTick == 0u ? 0u : (nextTick > nowTick ? nextTick - nowTick : 0u);
|
||||
|
||||
/// <summary>Re-anchor a persisted remaining cooldown to the current tick origin on restore (NonZero-guarded).</summary>
|
||||
public static uint RestoreNextTick(uint nowTick, uint remaining) => TickUtil.NonZero(nowTick + remaining);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d461ab50604ea642b26586bffeed41e
|
||||
@@ -76,4 +76,12 @@ namespace ProjectM.Simulation
|
||||
|
||||
/// <summary>Tag on the baked singleton carrying the <see cref="StructureCatalogEntry"/> buffer (the build cost/prefab table).</summary>
|
||||
public struct StructureCatalog : IComponentData { }
|
||||
|
||||
/// <summary>
|
||||
/// Marks a structure PLACED by a player at runtime (BuildPlaceSystem) or restored from a save — i.e. the
|
||||
/// persistable set, as opposed to anything baked into the subscene. SaveWriteSystem scans only these and
|
||||
/// BaseRestoreSystem re-adds the tag, so save/restore is the single source of truth for player builds.
|
||||
/// Server-only (not replicated). (Re-homed here from the retired automation components — LANTERN purge.)
|
||||
/// </summary>
|
||||
public struct RuntimePlacedTag : IComponentData { }
|
||||
}
|
||||
|
||||
@@ -6,17 +6,16 @@ namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Scans a server world for PLAYER-built structures (<see cref="PlacedStructure"/> + <see cref="RuntimePlacedTag"/>)
|
||||
/// into the flat SaveData v2 arrays — the SINGLE shared scan used by BOTH the autosave (SaveWriteSystem) and the
|
||||
/// into the flat SaveData arrays — the SINGLE shared scan used by BOTH the autosave (SaveWriteSystem) and the
|
||||
/// quit-to-menu save (WorldLauncher), so the two paths can never drift (only RuntimePlacedTag structures are saved;
|
||||
/// anything baked into the subscene is the subscene's source of truth, not the save's). Cooldowns are stored as
|
||||
/// REMAINING ticks (epoch-independent). Managed (List/array) — runs only on a save, never in the hot loop.
|
||||
/// anything baked into the subscene is the subscene's source of truth, not the save's). Managed (List/array) —
|
||||
/// runs only on a save, never in the hot loop.
|
||||
/// </summary>
|
||||
public static class SaveStructureScan
|
||||
{
|
||||
public static void Collect(EntityManager em, uint nowTick, out StructureSave[] structures, out StructureIoRow[] io)
|
||||
{
|
||||
var structs = new List<StructureSave>();
|
||||
var ioRows = new List<StructureIoRow>();
|
||||
|
||||
using var q = em.CreateEntityQuery(
|
||||
ComponentType.ReadOnly<PlacedStructure>(),
|
||||
@@ -27,46 +26,19 @@ namespace ProjectM.Simulation
|
||||
{
|
||||
var e = entities[k];
|
||||
var ps = em.GetComponentData<PlacedStructure>(e);
|
||||
int idx = structs.Count;
|
||||
|
||||
var row = new StructureSave
|
||||
structs.Add(new StructureSave
|
||||
{
|
||||
Type = ps.Type,
|
||||
CellX = ps.Cell.x,
|
||||
CellZ = ps.Cell.y,
|
||||
RemainingTicks = ProductionMath.RemainingTicks(ps.NextTick, nowTick),
|
||||
// EB-1: guarded so automation machines (no Health) don't crash the autosave path (no try/catch).
|
||||
// EB-1: guarded so structures without Health don't crash the autosave path (no try/catch).
|
||||
HP = em.HasComponent<Health>(e) ? em.GetComponentData<Health>(e).Current : 0f,
|
||||
};
|
||||
|
||||
if (em.HasComponent<Conveyor>(e))
|
||||
row.Direction = em.GetComponentData<Conveyor>(e).Direction;
|
||||
|
||||
if (em.HasComponent<ConveyorItem>(e) && em.IsComponentEnabled<ConveyorItem>(e))
|
||||
{
|
||||
var item = em.GetComponentData<ConveyorItem>(e);
|
||||
row.ConveyorResId = item.ResourceId;
|
||||
row.ConveyorCount = item.Count;
|
||||
}
|
||||
|
||||
structs.Add(row);
|
||||
|
||||
if (em.HasBuffer<MachineInput>(e))
|
||||
{
|
||||
var buf = em.GetBuffer<MachineInput>(e, true);
|
||||
for (int i = 0; i < buf.Length; i++)
|
||||
ioRows.Add(new StructureIoRow { StructureIndex = idx, Slot = 0, ResourceId = buf[i].ResourceId, Count = buf[i].Count });
|
||||
}
|
||||
if (em.HasBuffer<MachineOutput>(e))
|
||||
{
|
||||
var buf = em.GetBuffer<MachineOutput>(e, true);
|
||||
for (int i = 0; i < buf.Length; i++)
|
||||
ioRows.Add(new StructureIoRow { StructureIndex = idx, Slot = 1, ResourceId = buf[i].ResourceId, Count = buf[i].Count });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
structures = structs.ToArray();
|
||||
io = ioRows.ToArray();
|
||||
io = System.Array.Empty<StructureIoRow>(); // machine I/O retired with the automation chain (row type dies at save v7)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,11 +56,8 @@ namespace ProjectM.Simulation
|
||||
/// baked telegraph can't drift from the server windup.</summary>
|
||||
public const int ChargerWindupTicks = 30;
|
||||
|
||||
// ---- Production / automation (M7: Harvester/Conveyor/Fabricator) ----
|
||||
// ---- Base defense (EB-2) ----
|
||||
|
||||
/// <summary>Max production cycles a single machine awards in one process (bounds within-session
|
||||
/// catch-up after any skipped ticks; restore re-seats the baseline so this never reflects wall-clock).</summary>
|
||||
public const int MaxProductionCatchup = 600;
|
||||
|
||||
/// <summary>EB-2: Charge (turret munition) consumed per turret shot, withdrawn from the global ledger. A
|
||||
/// turret with 0 Charge SOFT-FAILS (no shot, no cooldown advance). A ledger-fed Fabricator mints Charge from
|
||||
@@ -73,9 +70,8 @@ namespace ProjectM.Simulation
|
||||
|
||||
// ---- Cold start (CycleDirectorSpawnSystem seeds the shared ledger on a NEW game) ----
|
||||
|
||||
/// <summary>DR-042 C6c: Ore deposited into the shared ledger at spawn on a NEW game ONLY (a restored save keeps
|
||||
/// its persisted ledger). Bootstraps the Fabricator(30)->Charge->Turret(10) chain so a turret placed before any
|
||||
/// mining isn't a silent cold deadlock. Ore-only so the 'build a Fabricator to arm turrets' lesson survives.</summary>
|
||||
/// <summary>DR-042 C6c: Ore deposited into the shared ledger at spawn on a NEW game ONLY (a restored save
|
||||
/// keeps its persisted ledger) — seed capital for the build loop.</summary>
|
||||
public const int StartingOre = 90;
|
||||
|
||||
// ---- Expedition run economy (RoomFieldSystem / RunDirectorSystem) ----
|
||||
|
||||
Reference in New Issue
Block a user