M7 Automation: deterministic Harvester to Conveyor to Fabricator chains
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>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
using NUnit.Framework;
|
||||
using ProjectM.Simulation;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ProjectM.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests the SaveData v2 structure-persistence schema (M7): a player-built structure set + the flat machine-I/O
|
||||
/// table round-trip through JsonUtility with fields intact; the REMAINING-ticks cooldown survives an epoch reset
|
||||
/// (RemainingTicks/RestoreNextTick); and a save JSON lacking the v2 arrays deserializes without throwing (degrades
|
||||
/// to no structures). The deeper instantiate-from-catalog restore is covered by the Play-mode validation pass.
|
||||
/// </summary>
|
||||
public class AutomationSaveRoundTripTests
|
||||
{
|
||||
[Test]
|
||||
public void StructuresAndIo_RoundTrip_PreservesFields()
|
||||
{
|
||||
var data = new SaveData
|
||||
{
|
||||
GoalCharge = 3,
|
||||
GoalTarget = 10,
|
||||
Structures = new[]
|
||||
{
|
||||
new StructureSave { Type = StructureType.Harvester, CellX = 4, CellZ = -2, RemainingTicks = 12 },
|
||||
new StructureSave { Type = StructureType.Conveyor, CellX = 5, CellZ = -2, Direction = 2, RemainingTicks = 3, ConveyorResId = ResourceId.Ore, ConveyorCount = 1 },
|
||||
new StructureSave { Type = StructureType.Fabricator, CellX = 6, CellZ = -2, RemainingTicks = 40 },
|
||||
},
|
||||
StructureIo = new[]
|
||||
{
|
||||
new StructureIoRow { StructureIndex = 0, Slot = 1, ResourceId = ResourceId.Ore, Count = 7 },
|
||||
new StructureIoRow { StructureIndex = 2, Slot = 0, ResourceId = ResourceId.Ore, Count = 5 },
|
||||
},
|
||||
};
|
||||
|
||||
var back = JsonUtility.FromJson<SaveData>(JsonUtility.ToJson(data));
|
||||
|
||||
Assert.AreEqual(SaveData.CurrentVersion, back.Version);
|
||||
Assert.AreEqual(3, back.Structures.Length);
|
||||
Assert.AreEqual(StructureType.Conveyor, back.Structures[1].Type);
|
||||
Assert.AreEqual(2, back.Structures[1].Direction);
|
||||
Assert.AreEqual(ResourceId.Ore, back.Structures[1].ConveyorResId);
|
||||
Assert.AreEqual(1, back.Structures[1].ConveyorCount);
|
||||
Assert.AreEqual(12u, back.Structures[0].RemainingTicks);
|
||||
Assert.AreEqual(2, back.StructureIo.Length);
|
||||
Assert.AreEqual(2, back.StructureIo[1].StructureIndex);
|
||||
Assert.AreEqual(0, back.StructureIo[1].Slot);
|
||||
Assert.AreEqual(5, back.StructureIo[1].Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RemainingTicks_RestoreNextTick_PreservesCooldownGap_AcrossEpochs()
|
||||
{
|
||||
uint saveNow = 5000u, savedNext = 5037u;
|
||||
uint remaining = ProductionMath.RemainingTicks(savedNext, saveNow);
|
||||
Assert.AreEqual(37u, remaining);
|
||||
|
||||
uint restoreNow = 11u;
|
||||
uint restoredNext = ProductionMath.RestoreNextTick(restoreNow, remaining);
|
||||
Assert.AreEqual(48u, restoredNext);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Save_Lacking_V2_Arrays_DeserializesWithoutThrowing()
|
||||
{
|
||||
SaveData back = null;
|
||||
Assert.DoesNotThrow(() => back = JsonUtility.FromJson<SaveData>("{\"Version\":2,\"GoalCharge\":1,\"GoalTarget\":10}"));
|
||||
Assert.IsNotNull(back);
|
||||
Assert.IsTrue(back.Structures == null || back.Structures.Length == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user