Skip to content

Integration test suite for AI, combat, and map generation scenarios (no plan yet) #16

@JohnLudlow

Description

@JohnLudlow

Overview

No integration tests currently exist for the headless adapter. The acceptance criterion in #5 requires that the adapter pass integration scenarios for AI, combat, and map generation, with deterministic seeds producing stable, assertable results in CI.

Since the adapter is a general-purpose engine abstraction, "AI/combat/map generation" integration tests are not tied to any specific game algorithm. Instead they verify that the adapter faithfully supports the patterns those domains require:

Domain What is being tested
AI / NPC logic Deterministic decision-making loop that reads input state and submits render commands each tick
Combat Audio event sequence (weapon sounds, hit effects) interleaved with sprite submissions; asserted in order
Map generation Large batch of mesh/sprite draw submissions across many steps; stable between runs with the same seed

All three scenarios use DeterministicEngineRunner with a fixed seed and TestAdapter for call recording. These scenarios depend on #14 (recording decorators) and #15 (SimulationTime + tick callback) being complete first.

Plan issue

Plan status

Not started

Definition of terms

Term Meaning
Integration test A test that exercises multiple components (adapter, runner, providers) working together end-to-end, as opposed to a unit test that isolates a single class.
Deterministic scenario A simulation run where the output is fully determined by the seed and the scripted inputs, with no external or random variation.
Scripted input Pre-defined key or mouse button presses injected via HeadlessInputProvider.ScriptKeyDown / ScriptKeyUp.

Architectural considerations and constraints

  • Dependencies: These tests require Complete TestAdapter provider call recording (docs/plans/phase-2-completion.md Phase 1) #14 (TestAdapter recording decorators) and Advance DeterministicEngineRunner — SimulationTime, tick callback, configurable camera (docs/plans/phase-2-completion.md Phase 2) #15 (DeterministicEngineRunner tick callback and SimulationTime) to be complete before the tests can be written or pass.
  • No native dependencies: All scenarios must run headless. No GPU, audio hardware, or platform SDK calls.
  • Determinism: Each scenario is run twice with the same seed. Both runs must produce identical RecordedCalls sequences.
  • Test project location: src/GameEngineAdapter.UnitTests/ (existing test project; integration tests live alongside unit tests, separated by file name convention *IntegrationTests.cs).
  • CI-clean: Tests must pass in the GitHub Actions CI pipeline without any additional setup steps.
  • DTO construction: SpriteDrawDto and MeshDrawDto each require (string Id, TransformDto Transform, MaterialDto Material, int Layer). Position is expressed via TransformDto (X, Y, Z, RotationX, RotationY, RotationZ, ScaleX, ScaleY, ScaleZ).

Implementation guide

Plan requirements

  • (Not started) AI scenario produces stable, assertable render output

    • GIVEN a DeterministicEngineRunner with seed 42 and a 16 ms timestep
    • WHEN an NPC decision loop runs for 60 steps via the tick callback
    • THEN the recorded sprite submissions are identical between two runs with the same seed
  • (Not started) Combat scenario audio event sequence is deterministic

    • GIVEN a TestAdapter with seed 1337
    • WHEN a combat event script runs for 10 steps
    • THEN RecordedCalls contains the expected audio and render entries in the correct order
  • (Not started) Map generation scenario produces stable mesh output

    • GIVEN a DeterministicEngineRunner with seed 0 and 100 steps
    • WHEN mesh draw commands are submitted during each tick
    • THEN the total recorded mesh count and content are identical between two runs
  • (Not started) All integration scenarios run in CI without native dependencies

    • GIVEN the GitHub Actions workflow
    • WHEN dotnet test runs the integration test project
    • THEN all scenarios pass with no platform-specific setup

Phase 1 — AI / NPC decision-loop scenario

Not started

Objective

Write AiIntegrationTests.cs. The scenario simulates an NPC that reads a scripted key state each tick, uses the seeded RNG to produce a deterministic position, and submits a sprite at that position. Run with seed 42 for 60 steps, collect RecordedSprites, then run again with the same seed and assert both collections are equal.

Phase requirements

  • (Not started) NPC sprite positions are identical between runs with the same seed
    • GIVEN two DeterministicEngineRunner instances, both with seed 42, 60 steps, 16 ms timestep
    • WHEN both are run with an equivalent NPC tick callback
    • THEN the recorded sprite positions match element-by-element

Examples

// src/GameEngineAdapter.UnitTests/AiIntegrationTests.cs

public sealed class AiIntegrationTests
{
    private static readonly MaterialDto DefaultMaterial =
        new("default", new Dictionary<string, object>(), []);

    private static IReadOnlyList<SpriteDrawDto> RunNpcScenario(int seed)
    {
        var config = new EngineConfig("Headless", null, null);
        var adapter = new HeadlessAdapter(config);
        var runner = new DeterministicEngineRunner(adapter, seed, TimeSpan.FromMilliseconds(16));
        var render = (HeadlessRenderProvider)adapter.RenderProvider;

        runner.Run(60, ctx =>
        {
            var x = ctx.Rng.NextSingle() * 100f;
            var y = ctx.Rng.NextSingle() * 100f;
            var transform = new TransformDto(x, y, 0f, 0f, 0f, 0f, 1f, 1f, 1f);
            adapter.RenderProvider.SubmitSprite(new SpriteDrawDto("npc", transform, DefaultMaterial, 0));
        });

        return render.RecordedSprites;
    }

    [Fact]
    public void NpcScenario_IsDeterministic()
    {
        var run1 = RunNpcScenario(42);
        var run2 = RunNpcScenario(42);
        Assert.Equal(run1, run2);
    }
}

Phase 2 — Combat audio/render sequence scenario

Not started

Objective

Write CombatIntegrationTests.cs. Script 10 combat steps: every step submits a sprite (character); on even steps also play a weapon sound. Use TestAdapter to assert the combined RecordedCalls sequence is stable and in the correct order (render → audio alternating as expected).

Phase requirements

  • (Not started) Combat call sequence is deterministic and ordered
    • GIVEN a TestAdapter and a 10-step combat script
    • WHEN run with seed 1337
    • THEN RecordedCalls contains alternating render and audio entries in the expected order

Phase 3 — Map generation batch scenario

Not started

Objective

Write MapGenerationIntegrationTests.cs. Simulate 100 steps; each step submits 10 mesh draw commands whose geometry is derived from the seeded RNG. Assert that the total mesh count is 1000 and that all mesh entries are identical between two runs with seed 0.

Phase requirements

  • (Not started) Map generation produces 1000 mesh entries

    • GIVEN a DeterministicEngineRunner with seed 0 and 100 steps
    • WHEN 10 meshes are submitted per tick
    • THEN render.RecordedMeshes.Count == 1000
  • (Not started) Map generation output is identical between runs

    • GIVEN two runs with the same seed
    • WHEN mesh entries are compared element-by-element
    • THEN all entries are equal

Testing and compatibility

  • All integration tests run within the existing GameEngineAdapter.UnitTests project — no new test project needed.
  • File naming convention: *IntegrationTests.cs to distinguish from unit tests.
  • No test requires network access, native libraries, or platform-specific tools.
  • Each scenario is self-contained: constructs its own adapter and runner; does not share state between test methods.
  • DTO construction uses TransformDto(X, Y, Z, RX, RY, RZ, SX, SY, SZ) and MaterialDto(ShaderId, Uniforms, TextureSlots) — see Core DTOs for full signatures.

See also

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-libItems related to reusable librariesarea-technicalsystemsInternal systems to do with game developmentenhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions