Files
TheriapolisV3/Theriapolis.Tests/Dungeons/Phase7ConstantsTests.cs
T
Christopher Wiebe b451f83174 Initial commit: Theriapolis baseline at port/godot branch point
Captures the pre-Godot-port state of the codebase. This is the rollback
anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md).
All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:40:51 -07:00

76 lines
3.0 KiB
C#

using Theriapolis.Core;
using Xunit;
namespace Theriapolis.Tests.Dungeons;
/// <summary>
/// Phase 7 M0 — schema integrity tests for the Phase 7 constants. These
/// guard against silent regressions in:
/// - <see cref="C.SAVE_SCHEMA_VERSION"/> (must == 8 at Phase 7 ship)
/// - The 4 new RNG sub-streams (must be unique vs every existing stream)
/// - Dungeon size bands (must be a coherent ladder)
/// - Movement-cost multipliers (must be ≥ 1.0; squeezing must dominate)
/// </summary>
public sealed class Phase7ConstantsTests
{
[Fact]
public void SaveSchemaVersion_IsEight()
{
Assert.Equal(8, C.SAVE_SCHEMA_VERSION);
}
[Fact]
public void DungeonRngSubStreams_AreDistinctFromAllExistingStreams()
{
// Collect every named ulong RNG sub-stream by reflection. Each
// must be unique — a collision means two independent streams share
// a seed, breaking the dice contract.
var fields = typeof(C).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static)
.Where(f => f.IsLiteral && f.FieldType == typeof(ulong))
.ToArray();
var seen = new Dictionary<ulong, string>();
foreach (var f in fields)
{
ulong value = (ulong)f.GetRawConstantValue()!;
if (seen.TryGetValue(value, out var prior))
Assert.Fail($"RNG sub-stream collision: {f.Name} == {prior} ({value:X})");
seen[value] = f.Name;
}
// Belt-and-braces: assert the four Phase 7 streams exist.
Assert.Contains(C.RNG_DUNGEON_LAYOUT, seen.Keys);
Assert.Contains(C.RNG_ROOM_PICK, seen.Keys);
Assert.Contains(C.RNG_DUNGEON_POPULATE, seen.Keys);
Assert.Contains(C.RNG_DUNGEON_LOOT, seen.Keys);
}
[Fact]
public void DungeonSizeBands_FormCoherentLadder()
{
Assert.True(C.DUNGEON_SMALL_ROOMS_MIN <= C.DUNGEON_SMALL_ROOMS_MAX);
Assert.True(C.DUNGEON_MED_ROOMS_MIN <= C.DUNGEON_MED_ROOMS_MAX);
Assert.True(C.DUNGEON_LARGE_ROOMS_MIN <= C.DUNGEON_LARGE_ROOMS_MAX);
// Ladders don't overlap — a small dungeon's max < medium's min.
Assert.True(C.DUNGEON_SMALL_ROOMS_MAX < C.DUNGEON_MED_ROOMS_MIN);
Assert.True(C.DUNGEON_MED_ROOMS_MAX < C.DUNGEON_LARGE_ROOMS_MIN);
}
[Fact]
public void MovementCostMultipliers_AreOrdered()
{
Assert.True(C.MOVE_COST_MISMATCH_LIGHT >= 1.0f,
"Mismatch must never give a speed bonus.");
Assert.True(C.MOVE_COST_MISMATCH_LIGHT < C.MOVE_COST_MISMATCH_MED);
Assert.True(C.MOVE_COST_MISMATCH_MED < C.MOVE_COST_MISMATCH_HEAVY);
}
[Fact]
public void LockAndTrapDcs_AreOrdered()
{
Assert.True(C.LOCK_DC_TRIVIAL < C.LOCK_DC_EASY);
Assert.True(C.LOCK_DC_EASY < C.LOCK_DC_MEDIUM);
Assert.True(C.LOCK_DC_MEDIUM < C.LOCK_DC_HARD);
Assert.True(C.TRAP_DC_TRIVIAL < C.TRAP_DC_EASY);
Assert.True(C.TRAP_DC_EASY < C.TRAP_DC_MEDIUM);
}
}