diff --git a/.gitattributes b/.gitattributes
index ec2855e..fbd75d3 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +1 @@
-* text = lf
\ No newline at end of file
+* text=lf
\ No newline at end of file
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 412e1cc..f374816 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -4,8 +4,7 @@
name: Build and Test
on:
- # TODO: Uncomment this line
- # push:
+ push:
pull_request:
types: [opened, synchronize, reopened]
@@ -24,9 +23,9 @@ jobs:
contents: write
pull-requests: write
outputs:
- assemblySemVer: ${{ steps.version_step.outputs.assemblySemVer }}
- GitVersion_FullSemVer: ${{ steps.version_step.outputs.GitVersion_FullSemVer }}
- semVer: ${{ steps.version_step.outputs.semVer }}
+ assemblySemVer: ${{ steps.setup.outputs.assemblySemVer }}
+ GitVersion_FullSemVer: ${{ steps.setup.outputs.GitVersion_FullSemVer }}
+ semVer: ${{ steps.setup.outputs.semVer }}
steps:
- uses: actions/checkout@v5
@@ -46,6 +45,8 @@ jobs:
- id: tag-commit
uses: ./actions/steps/git/tag-commit
+ with:
+ version: ${{ steps.setup.outputs.GitVersion_FullSemVer }}
unit-test:
needs: build
@@ -69,22 +70,31 @@ jobs:
assembly-semver: ${{ needs.build.outputs.assemblySemVer }}
gitversion-full-semver: ${{ needs.build.outputs.GitVersion_FullSemVer }}
- # benchmark:
- # needs: build
- # runs-on: windows-latest
- # env:
- # CONFIGURATION: Release
- # permissions:
- # contents: read
- # issues: write
-
- # steps:
- # - uses: actions/checkout@v5
- # with:
- # lfs: true
- # fetch-depth: 0
-
- # - uses: ./actions/steps/benchmark
+ benchmark:
+ needs: build
+ runs-on: windows-latest
+ env:
+ CONFIGURATION: Release
+ permissions:
+ contents: read
+ issues: write
+ actions: read
+
+ steps:
+ - uses: actions/checkout@v5
+ with:
+ lfs: true
+ fetch-depth: 0
+ submodules: recursive
+
+ - uses: ./actions/steps/setup
+ - uses: ./actions/steps/benchmark
+ with:
+ configuration: ${{ env.CONFIGURATION }}
+ project: src/GameEngineAdapter.Benchmarks/GameEngineAdapter.Benchmarks.csproj
+ job: short
+ exporters: GitHub
+ filter: "*"
lint:
runs-on: windows-latest
diff --git a/GameEngineAdapter.slnx b/GameEngineAdapter.slnx
index 7600562..27fffad 100644
--- a/GameEngineAdapter.slnx
+++ b/GameEngineAdapter.slnx
@@ -1,6 +1,8 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/actions b/actions
index a1edd1c..5bef35b 160000
--- a/actions
+++ b/actions
@@ -1 +1 @@
-Subproject commit a1edd1cbe8dbf9e4eee70b8c8622d5c3b5556cff
+Subproject commit 5bef35b8e5a94b425ff6cd058ea6195b3b6ad401
diff --git a/docs/plans/issue-1-translator-tests-and-benchmarks.md b/docs/plans/issue-1-translator-tests-and-benchmarks.md
new file mode 100644
index 0000000..e16145e
--- /dev/null
+++ b/docs/plans/issue-1-translator-tests-and-benchmarks.md
@@ -0,0 +1,151 @@
+# Issue #1 follow-up — Translator tests and benchmarks
+
+## Overview
+
+Issue #1 is functionally complete for contract shape (interfaces + DTOs), but two acceptance criteria remain:
+
+1. Translator-style unit tests that verify DTOs are correctly mapped to an engine-facing call surface using fakes.
+2. A benchmark suite that measures DTO translation performance (target: single-digit microseconds on hot paths).
+
+This plan describes how to add those two items without changing the public contract surface in `GameEngineAdapter.Core`.
+
+## Table of contents
+
+- [Issue #1 follow-up — Translator tests and benchmarks](#issue-1-follow-up--translator-tests-and-benchmarks)
+ - [Overview](#overview)
+ - [Table of contents](#table-of-contents)
+ - [Plan issue](#plan-issue)
+ - [Plan status](#plan-status)
+ - [Definition of terms](#definition-of-terms)
+ - [Architectural considerations and constraints](#architectural-considerations-and-constraints)
+ - [Implementation guide](#implementation-guide)
+ - [Plan requirements](#plan-requirements)
+ - [Phase 1 — Translator tests](#phase-1--translator-tests)
+ - [Phase 2 — Benchmarks](#phase-2--benchmarks)
+ - [See also](#see-also)
+ - [References](#references)
+
+## Plan issue
+
+- [#1](https://github.com/JohnLudlow/GameEngineAdapter/issues/1)
+
+## Plan status
+
+Completed
+
+## Definition of terms
+
+| Term | Meaning | Reference |
+| ---- | ------- | --------- |
+| BenchmarkDotNet | .NET microbenchmark framework that runs code repeatedly under controlled conditions to measure throughput/latency. | |
+| Translator test | Unit test that verifies a DTO is translated/mapped into a lower-level call surface correctly (typically using fakes/spies). | |
+
+## Architectural considerations and constraints
+
+- **Do not change contracts**: `JohnLudlow.GameEngineAdapter.Core` is the public contract assembly. Translator tests and benchmarks should not require changes to public interfaces unless strictly necessary.
+- **Keep engine-agnostic**: Translator tests should validate mapping logic without taking a dependency on a real engine SDK.
+- **Avoid benchmark noise**: Benchmarks should run in Release and avoid allocations or I/O unrelated to the measured translation.
+- **CI integration already scaffolded**: There is an existing composite action at `actions/steps/benchmark/action.yml` with TODO placeholders for the benchmark project path, and the workflow job is currently commented out in `.github/workflows/main.yml`.
+
+## Implementation guide
+
+### Plan requirements
+
+- (***Not started***) Translator tests exist for DTO→engine-call mapping
+ - GIVEN a fake engine call surface
+ - WHEN an adapter-facing provider receives DTOs
+ - THEN the provider calls the fake engine API with correctly translated values.
+
+- (***Not started***) Benchmark suite exists for DTO translation
+ - GIVEN a benchmark project
+ - WHEN the benchmark runs in Release
+ - THEN it reports per-operation timing for translation hot paths.
+
+- (***Not started***) CI can run benchmarks (optional but recommended)
+ - GIVEN a PR build
+ - WHEN the benchmark job is enabled
+ - THEN BenchmarkDotNet results are produced as artifacts and published in the build summary.
+
+### Phase 1 — Translator tests
+
+***Not started***
+
+#### Objective
+
+Add unit tests that verify translation from DTOs (`SpriteDrawDto`, `TextDrawDto`, `MeshDrawDto`, `MaterialDto`, `TransformDto`) into an engine-facing API using fakes/spies.
+
+#### Technical details
+
+1. **Create a minimal fake engine call surface** inside the UnitTests project.
+ - Example: `IFakeRenderBackend` with methods like `DrawSprite(string spriteId, TransformDto transform, MaterialDto material, int layer)`.
+ - Implementation: `RecordingFakeRenderBackend` stores each call (method name + args) into a list.
+
+2. **Create a sample translator provider** (test-only) that implements `IRenderProvider` and forwards to the fake backend.
+ - Example type: `TranslatingRenderProvider : IRenderProvider`.
+ - Translation should be intentionally simple and explicit (pass-through of IDs/transform/material/layer). The goal is to validate mapping patterns, not to build a real engine adapter.
+
+3. **Write translator tests** validating:
+ - Ordering: calls arrive in the same order as DTO submissions.
+ - Fidelity: all DTO fields are forwarded without mutation.
+ - Frame boundaries: `BeginFrame`/`EndFrame`/`Present` call sequences can be asserted if the translator provider chooses to forward them.
+
+4. **Keep tests isolated**
+ - Do not reuse `HeadlessRenderProvider` for translator tests; that provider records DTOs but does not exercise a DTO→engine mapping.
+
+#### Phase requirements
+
+- (***Not started***) Render DTO translation is verified
+ - GIVEN a `TranslatingRenderProvider` backed by a recording fake backend
+ - WHEN `SubmitSprite`, `SubmitText`, and `SubmitMesh` are invoked
+ - THEN the fake backend receives equivalent calls with equivalent values.
+
+- (***Not started***) Material and transform objects are forwarded correctly
+ - GIVEN a DTO with non-default `TransformDto` and populated `MaterialDto.Uniforms`
+ - WHEN the DTO is submitted
+ - THEN the backend sees the same values.
+
+### Phase 2 — Benchmarks
+
+***Not started***
+
+#### Objective
+
+Add a BenchmarkDotNet benchmark project that measures DTO translation performance for representative hot paths.
+
+#### Technical details
+
+1. **Create a new benchmark project**
+ - Path: `src/GameEngineAdapter.Benchmarks/GameEngineAdapter.Benchmarks.csproj`
+ - References: `GameEngineAdapter.Core` and (optionally) a small internal translation implementation shared with tests.
+ - Packages: `BenchmarkDotNet`.
+
+2. **Define benchmarks**
+ - Benchmark `TranslatingRenderProvider.SubmitSprite` with a fixed DTO.
+ - Benchmark a loop over N submissions to reduce overhead noise.
+ - Ensure Release configuration and `--filter *` works.
+
+3. **Wire up the existing CI benchmark action (optional but recommended)**
+ - Replace the `` placeholders in `actions/steps/benchmark/action.yml` with the actual benchmark project path.
+ - Uncomment/enable the `benchmark` job in `.github/workflows/main.yml` once the benchmark project exists.
+
+#### Phase requirements
+
+- (***Not started***) Benchmarks run locally
+ - GIVEN the benchmark project
+ - WHEN `dotnet run -c Release --project src/GameEngineAdapter.Benchmarks/...` is executed
+ - THEN BenchmarkDotNet produces a markdown results file under `BenchmarkDotNet.Artifacts/results/`.
+
+- (***Not started***) Benchmarks run in CI (optional)
+ - GIVEN the workflow benchmark job is enabled
+ - WHEN the CI pipeline runs
+ - THEN benchmark results are uploaded and included in the job summary.
+
+## See also
+
+- [Phase 1 — Interface development](./phase-1-interface-development.md)
+
+## References
+
+- BenchmarkDotNet docs:
+- Existing benchmark action scaffold: `actions/steps/benchmark/action.yml`
+- CI workflow scaffold: `.github/workflows/main.yml`
diff --git a/docs/plans/phase-1-interface-development-interfaces.drawio.svg b/docs/plans/phase-1-interface-development-interfaces.drawio.svg
index 4eaa3c0..02f3fa5 100644
--- a/docs/plans/phase-1-interface-development-interfaces.drawio.svg
+++ b/docs/plans/phase-1-interface-development-interfaces.drawio.svg
@@ -1 +1,3471 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/plans/phase-1-interface-development.md b/docs/plans/phase-1-interface-development.md
index d7d75df..4a8264a 100644
--- a/docs/plans/phase-1-interface-development.md
+++ b/docs/plans/phase-1-interface-development.md
@@ -9,32 +9,43 @@ Phase 1 defines the stable adapter contracts and DTO shapes used by all adapters
- [Phase 1 — Interface development](#phase-1--interface-development)
- [Overview](#overview)
- [Table of contents](#table-of-contents)
- - [Feature status](#feature-status)
+ - [Plan issue](#plan-issue)
+ - [Plan status](#plan-status)
- [Definition of terms](#definition-of-terms)
- [Architectural considerations and constraints](#architectural-considerations-and-constraints)
- [Implementation guide](#implementation-guide)
- - [Feature requirements](#feature-requirements)
- - [Technical details](#technical-details)
- - [Phase requirements](#phase-requirements)
- - [API examples (minimal)](#api-examples-minimal)
- - [Class diagram](#class-diagram)
- - [DTO guidelines](#dto-guidelines)
- - [Testing and compatibility](#testing-and-compatibility)
- - [Performance targets](#performance-targets)
+ - [Plan requirements](#plan-requirements)
+ - [Phase 1 — Adapter contracts and DTOs](#phase-1--adapter-contracts-and-dtos)
+ - [Objective](#objective)
+ - [Technical details](#technical-details)
+ - [Phase requirements](#phase-requirements)
+ - [Examples](#examples)
+ - [Class diagram](#class-diagram)
+ - [DTO guidelines](#dto-guidelines)
+ - [Testing and compatibility](#testing-and-compatibility)
+ - [Performance targets](#performance-targets)
+ - [Known issues and design concerns](#known-issues-and-design-concerns)
- [See also](#see-also)
- [References](#references)
-## Feature status
+## Plan issue
-Not started
+- [#2](https://github.com/JohnLudlow/GameEngineAdapter/issues/2)
+
+## Plan status
+
+Complete (interfaces and DTOs implemented)
## Definition of terms
| Term | Meaning | Reference |
| ---- | ------- | --------- |
| Adapter | An implementation binding the engine-agnostic contracts to a specific engine (MonoGame, Stride, Raylib, TUI, Headless). | |
-| DTO | Data Transfer Object — compact, typically struct-based shapes passed across the adapter boundary. | |
| ContractVersion | Semantic version string indicating the adapter contract shape. | |
+| DTO | Data Transfer Object — compact, typically struct-based shapes passed across the adapter boundary. | |
+| EngineConfig | Configuration data supplied when initializing an adapter (engine selection, resource paths, options). | |
+| FrameScope | Disposable scope returned by BeginFrame; disposing finalizes the current frame (RAII pattern). | |
+| Provider | A lightweight, adapter-owned service obtained from IEngineAdapter for a specific concern (rendering, input, UI, assets). | |
## Architectural considerations and constraints
@@ -45,126 +56,397 @@ Not started
## Implementation guide
-### Feature requirements
+### Plan requirements
-- (***Not started***) API definitions committed with XML docs and examples.
+- (***Complete***) API definitions committed with XML docs and examples.
- GIVEN interface PR is opened
- WHEN team reviews and approves
- THEN interfaces are versioned and published for adapter implementations.
-### Technical details
+### Phase 1 — Adapter contracts and DTOs
+
+***Complete***
+
+#### Objective
+
+Define stable adapter contracts and DTO shapes. Produce small, well-documented C# interfaces and compact DTOs that minimize allocation and provide a stable surface for engine adapters.
-- Define `IEngineAdapter` (lifecycle, capability descriptor, provider accessors such as `GetRenderProvider`, `GetInputProvider`).
-- Define provider interfaces (`IRenderProvider`, `IInputProvider`, `IUserInterfaceProvider`, `IAssetProvider`) that expose minimal, adapter-owned runtime surfaces and DTO translation helpers.
+#### Technical details
+
+- Define `IEngineAdapter` (lifecycle, capability descriptor, provider property accessors such as `RenderProvider`, `InputProvider`, `AudioPlayer`).
+- Define provider interfaces (`IRenderProvider`, `IInputProvider`, `IUserInterfaceProvider`, `IAssetProvider`) that expose minimal, adapter-owned runtime surfaces and DTO translation helpers. Providers are exposed as read-only properties on `IEngineAdapter`.
- Providers are intentionally lightweight and adapter-owned: callers obtain a provider from the adapter and submit DTOs or poll events through that provider.
-- Define `IAudioPlayer` (play/stop/volume, audio asset references) and `IAssetProvider`/`IAssetLoader` for asset lifecycle operations.
+- Define `IAudioPlayer` (play/stop/volume, audio asset references), `IAssetProvider` for asset caching and lifecycle management (unload, query, cache eviction), and `IAssetLoader` for loading assets from storage into the asset provider.
- Provide capability descriptor model (`EngineCapabilities`) returned at adapter init and allow specific engine capability variants (e.g. `HeadlessEngineCapabilities`) to derive from the base capabilities.
-### Phase requirements
+#### Type definitions
-- (***Not started***) Adapter lifecycle & capability negotiation
- - GIVEN adapters are present at startup
- - WHEN the game queries capabilities
- - THEN `IEngineAdapter` returns `EngineCapabilities` and exposes `Initialize`/`Shutdown` semantics and diagnostics for mismatches.
+The types listed below were originally referenced by the contracts before the Core project was fully fleshed out. They are now implemented under `src/GameEngineAdapter.Core/` (one file per type) in the `JohnLudlow.GameEngineAdapter.Core` namespace.
-### API examples (minimal)
+The snippets are retained as illustrative examples; prefer the source files as the canonical definitions.
+
+##### EngineConfig
```csharp
-// Example adapter root interface
-public interface IEngineAdapter : IDisposable
+namespace JohnLudlow.GameEngineAdapter.Core;
+
+///
+/// Configuration data for initializing an engine adapter.
+///
+/// Adapter type or engine backend identifier (e.g. "MonoGame", "Stride").
+/// Optional path to engine resources or platform binaries.
+/// Optional key-value configuration entries.
+public readonly record struct EngineConfig(
+ string AdapterName,
+ string? ResourcePath,
+ IReadOnlyDictionary? Options);
+```
+
+##### FrameScope
+
+```csharp
+namespace JohnLudlow.GameEngineAdapter.Core;
+
+///
+/// Disposable scope for a single render frame. Disposing finalizes the frame.
+///
+public readonly record struct FrameScope : IDisposable
{
- EngineCapabilities Capabilities { get; }
- Task InitializeAsync(EngineConfig config, CancellationToken ct = default);
- Task ShutdownAsync(CancellationToken ct = default);
+ /// Finalizes the current frame.
+ public void Dispose() { /* adapter-specific frame end logic */ }
+}
+```
+
+##### SpriteDrawDto
+
+```csharp
+namespace JohnLudlow.GameEngineAdapter.Core;
+
+///
+/// DTO for submitting a sprite draw command.
+///
+/// Asset identifier for the sprite texture.
+/// World-space transform for the sprite.
+/// Material to apply when rendering.
+/// Sort layer for deterministic draw ordering.
+public readonly record struct SpriteDrawDto(
+ string SpriteId,
+ TransformDto Transform,
+ MaterialDto Material,
+ int Layer);
+```
- // Provider accessors - adapters expose provider instances for rendering, input, UI and assets
- IRenderProvider GetRenderProvider();
- IInputProvider GetInputProvider();
- IUserInterfaceProvider GetUserInterfaceProvider();
- IAssetProvider GetAssetProvider();
+##### TextDrawDto
+
+```csharp
+namespace JohnLudlow.GameEngineAdapter.Core;
+
+///
+/// DTO for submitting a text draw command.
+///
+/// Text content to render.
+/// World-space transform for the text.
+/// Asset identifier for the font.
+/// Font size in points.
+/// Material to apply when rendering.
+/// Sort layer for deterministic draw ordering.
+public readonly record struct TextDrawDto(
+ string Text,
+ TransformDto Transform,
+ string FontId,
+ float FontSize,
+ MaterialDto Material,
+ int Layer);
+```
+
+##### MeshDrawDto
+
+```csharp
+namespace JohnLudlow.GameEngineAdapter.Core;
+
+///
+/// DTO for submitting a mesh draw command.
+///
+/// Asset identifier for the mesh.
+/// World-space transform for the mesh.
+/// Material to apply when rendering.
+/// Sort layer for deterministic draw ordering.
+public readonly record struct MeshDrawDto(
+ string MeshId,
+ TransformDto Transform,
+ MaterialDto Material,
+ int Layer);
+```
+
+##### IInputProvider
+
+```csharp
+namespace JohnLudlow.GameEngineAdapter.Core;
+
+///
+/// Adapter-owned provider for polling input state (keyboard, mouse, gamepad).
+///
+public interface IInputProvider
+{
+ /// Returns true if the specified key is currently pressed.
+ bool IsKeyDown(string key);
+
+ /// Returns true if the specified mouse button is currently pressed.
+ bool IsMouseButtonDown(int button);
+
+ /// Returns the current mouse position in screen coordinates.
+ (float X, float Y) GetMousePosition();
}
```
+##### IUserInterfaceProvider
+
```csharp
-// Engine capabilities and camera descriptor examples
-public readonly struct EngineCapabilities
+namespace JohnLudlow.GameEngineAdapter.Core;
+
+///
+/// Adapter-owned provider for user interface operations.
+///
+public interface IUserInterfaceProvider
{
- public readonly bool Supports2D;
- public readonly bool Supports3D;
- public readonly bool SupportsShaders;
- public readonly bool SupportsAudio;
- public readonly bool SupportsRichUI;
- public readonly string ContractVersion; // semantic contract version
- public readonly string[] SupportedTextureFormats;
- public readonly int MaxTextureSize;
- public readonly int MaxAudioChannels;
+ // Members to be defined based on UI requirements.
}
+```
+
+##### IAssetProvider
+
+```csharp
+namespace JohnLudlow.GameEngineAdapter.Core;
-// Example of a capabilities variant used by headless/test adapters
-public readonly struct HeadlessEngineCapabilities
+///
+/// Adapter-owned provider for asset lifecycle management, caching, and querying.
+/// Does not load assets directly — delegates to for I/O.
+///
+public interface IAssetProvider
{
- public readonly EngineCapabilities Base;
- public readonly bool SupportsOffscreenRendering; // offscreen frame encoding / trace capture
- public readonly bool DeterministicTick; // indicates deterministic simulation support
+ /// Returns the asset loader used by this provider.
+ IAssetLoader Loader { get; }
+
+ /// Returns a previously loaded asset by identifier, or null if not cached.
+ object? GetAsset(string assetId);
+
+ /// Unloads a previously loaded asset and removes it from the cache.
+ void UnloadAsset(string assetId);
+
+ /// Returns true if the specified asset is currently loaded and cached.
+ bool IsAssetLoaded(string assetId);
+
+ /// Evicts all cached assets.
+ void ClearCache();
}
+```
-public enum ProjectionType { Orthographic, Perspective }
+##### IAudioPlayer
-public readonly struct CameraDescriptor
+```csharp
+namespace JohnLudlow.GameEngineAdapter.Core;
+
+///
+/// Interface for audio playback (play, stop, volume control).
+///
+public interface IAudioPlayer
{
- public readonly ProjectionType Projection;
- public readonly float FieldOfViewDegrees; // used for perspective
- public readonly float OrthographicSize; // used for orthographic
- public readonly float AspectRatio;
- public readonly float NearPlane;
- public readonly float FarPlane;
- // Camera transform is supplied separately via TransformDto when submitting world-space primitives
+ /// Plays the specified audio asset.
+ void StartPlayback(string audioAssetId, bool loopPlayback = false);
+
+ /// Stops playback of the specified audio asset.
+ void StopPlayback(string audioAssetId);
+
+ /// Sets the volume for the specified audio asset (0.0–1.0).
+ void SetVolume(string audioAssetId, float volume);
}
```
+##### IAssetLoader
+
+```csharp
+namespace JohnLudlow.GameEngineAdapter.Core;
+
+///
+/// Interface for loading assets from storage (disk, network, embedded resources).
+/// Separate from which handles caching and lifecycle.
+///
+public interface IAssetLoader
+{
+ /// Asynchronously loads an asset by identifier.
+ Task