Files
Project-M/Assets/_Project/Tests/EditMode/AutomationSaveRoundTripTests.cs
kronic f3f65bccbf 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>
2026-06-06 15:05:15 -07:00

72 lines
3.3 KiB
C#

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);
}
}
}