Zaldaryon.Pharos.Templates 0.8.0

dotnet new install Zaldaryon.Pharos.Templates@0.8.0
                    
This package contains a .NET Template Package you can call from the shell/command line.

<p align="center"> <img src="docs/pharos-logo.svg" alt="Pharos logo" width="120" height="120" /> </p>

Pharos

In-process headless test harness for Vintage Story client and server systems. Pharos boots a real Vintage Story client or server inside your test process, drives it frame by frame or tick by tick, and exposes rendering and world-state inspection APIs to xUnit test code.

What It Does

Pharos runs Vintage Story inside your test process. The client loads chunks, meshes geometry, and issues OpenGL draw calls exactly like the normal client, but with no visible window and no audio. The server runs a full simulation loop with a real player registry and savegame stack, but in an isolated temporary sandbox. Your test code controls the frame and tick loops, positions the camera, inspects rendering state, places blocks, and asserts on results.

Use cases:

  • Rendering regression tests for client-side mods
  • Server-side integration tests without a running game instance
  • Performance benchmarks that measure actual draw calls and allocations
  • Visual diff tests comparing framebuffer output across versions
  • Automated smoke tests for mod compatibility
  • Lockstep client-server end-to-end scenario testing

Requirements

  • Vintage Story 1.22.x (1.22.7 recommended)
  • .NET 10 SDK
  • OpenGL 4.3+ for full inspection features (3.3 for basic headless)
  • Windows or Linux (macOS not supported)

See the compatibility matrix for version details.

Installation

Client scenario testing

Add the Pharos XUnit metapackage to your test project:

dotnet add package Zaldaryon.Pharos.XUnit

This brings in the core library, xUnit integration, and xUnit itself.

Server scenario testing

The server APIs are part of the same package. No extra package is needed.

Migrating from Pixnop.Atlas

If you have existing Atlas test suites, install the compatibility shim for zero-code-change migration:

dotnet add package Zaldaryon.Pharos.AtlasCompat

Or use the CLI migration tool:

pharos migrate-atlas ./your-test-project

See Migrating from Atlas for the full guide.

Set the VINTAGE_STORY environment variable to your game installation:

# Windows (PowerShell)
$env:VINTAGE_STORY = "C:\Program Files\Vintage Story"

# Linux
export VINTAGE_STORY=~/.local/share/vintagestory

Quick Start: Client Scenarios

Create a test class that inherits from ClientScenarioBase and mark test methods with [ClientScenario]:

using Zaldaryon.Pharos.XUnit;
using Xunit;

public class ChunkRenderingTests : ClientScenarioBase
{
    [ClientScenario]
    public void ClientLoadsChunksAroundPlayer()
    {
        Player!.SetPosition(0, 100, 0);
        Player.LookAt(32, 64, 32);
        FrameController!.Frames(60);
        Assert.True(Client!.WorldMap.LoadedChunks > 0);
    }
}

Quick Start: Server Scenarios

Inherit from ServerScenarioBase and use [ServerScenario]:

using Zaldaryon.Pharos.XUnit;
using Xunit;

[ServerWorld(seed: 12345, playStyle: "creativebuilding", worldType: "superflat")]
public class InventoryTests : ServerScenarioBase
{
    [ServerScenario]
    public async Task GivingItemToPlayer_UpdatesInventory()
    {
        var player = await CreateTestPlayerAsync("TestPlayer");
        player.GiveItem("game:sword-iron");
        Assert.True(player.HasItem("game:sword-iron"));
    }
}

Quick Start: Client-Server Scenarios

Combine both in one locked-step test with [ClientServerScenario]:

using Zaldaryon.Pharos.XUnit;
using Xunit;

public class BlockInteractionTests : ClientServerScenarioBase
{
    [ClientServerScenario]
    public async Task BreakingBlock_UpdatesBothSides()
    {
        var pos = new BlockPos(10, 64, 10);
        Server.Api.World.BlockAccessor.SetBlock(1, pos);
        await Session.StepUntilAsync(() => ClientServerAssert.BlockSynced(Session, pos));
        ClientServerAssert.ClientServerBlockSynced(Session, pos);
    }
}

Quick Start: Smoke-Test a Mod

Check that a mod loads, joins and plays without errors, with no test project:

dotnet tool install -g Zaldaryon.Pharos.Cli
export VINTAGE_STORY=/path/to/vintagestory
xvfb-run -a pharos smoke --mod bin/Release/mymod.zip --strict

Or, in a test project, inherit the ready-made scenario:

[ServerMods("../../../../MyMod/bin/Release/mymod.zip")]
[StrictBoot]
public class Smoke : ModSmokeTest;

See Smoke Testing a Mod.

To run a live suite faster, pharos run runs its test classes in parallel worker processes and merges their results into one TRX:

pharos run tests/MyMod.Tests --filter "Category=Live" --parallel 2 --trx results.trx

See Parallel Runs.

To compare a change against a baseline and see what each metric did, pharos compare runs the same tests on both sides, alternating and repeating them:

pharos compare tests/MyMod.Tests --baseline origin/main --repeat 5 --budgets pharos.compare.json

See Comparing Before and After.

To build a test world, stamp in a WorldEdit schematic, or boot each class from a saved world that pharos fixture made from a builder test:

await this.PlaceSchematicAsync("Fixtures/furnace-room.json", new BlockPos(512, 4, 512));
pharos fixture tests/MyMod.Tests --scenario VillageBuilder.Build --out tests/MyMod.Tests/Fixtures/village.vcdbs

See Test Worlds.

To see where the server's tick time goes, and who the traffic is for, measure a stretch of ticks:

ServerProfile profile = await Server.Profile.MeasureAsync(ticks: 600);
Assert.True(profile.Mods["mymod"].MeanMsPerTick < 0.5);
Assert.True(profile.BlockEntities["BlockEntityQuern"].P95MsPerTick < 1);
profile.Publish(); // feed the numbers into result.json and pharos compare

See Server Profiling.

To see what worldgen costs, what a save writes, and how much of the world is loaded:

WorldgenProfile worldgen = await Server.Worldgen.MeasureAsync(64, 64, 65, 65);
SaveProfile save = await Server.Saves.MeasureAsync();
WorldStats stats = Server.WorldStats;

Assert.True(worldgen.MeanMsPerColumn < 5);
Assert.True(save.TotalBytes < 100_000_000);
Assert.True(stats.LoadedChunks > 0);

See World Metrics.

Quick Start: A Real Client Joining a Server

Boot the client in engine mode to run the vanilla client startup. The client joins the server, goes through character creation, receives chunks and renders the world through the game's own pipeline. It needs no auth server and no outbound network access:

using var server = EmbeddedServerHost.Boot(new ServerWorldOptions { WorldType = "superflat" });
using var client = HeadlessClientBootstrap.Boot(new HeadlessClientOptions { BootMode = ClientBootMode.Engine });
using var session = client.ConnectLoopback(server, "Pilot");

Assert.True(session.WaitForPlayerJoined(TimeSpan.FromSeconds(120)));
FramebufferSnapshot frame = client.CaptureFrame();

See Engine Mode for what runs and how the offline join works.

Run tests with:

dotnet test -c Release

On Linux without a display:

./scripts/run-headless-linux.sh

Packages

Package Description
Zaldaryon.Pharos Core headless client bootstrap, frame stepper, and inspection APIs
Zaldaryon.Pharos.Bridge In-client mod exposing inspection hooks to scenario code
Zaldaryon.Pharos.XUnit xUnit integration: [ClientScenario], [ServerScenario], [ClientServerScenario]
Zaldaryon.Pharos.AtlasCompat Drop-in compatibility shim for Atlas.Api and Atlas.XUnit
Zaldaryon.Pharos.Cli The pharos command: pharos smoke, pharos run --parallel, benchmarks, Atlas migration. Uses your own game install

Install Zaldaryon.Pharos.XUnit for client and server testing. Install Zaldaryon.Pharos.AtlasCompat to migrate existing Atlas test suites without code changes.

Platform Support

Platform Method Notes
Windows Hidden GLFW window with FBO Works on any GPU
Linux Mesa llvmpipe under Xvfb Works in CI without a GPU
macOS Not supported GLFW cannot create headless OpenGL contexts

Both Windows and Linux produce identical rendering behavior and report OpenGL 4.5 core profile.

Inspection APIs

Client rendering

Class Purpose
GlCommandProxy Count draw calls, buffer allocations, and VAO operations
CullingInspector Query which chunks are visible, frustum-culled, or occlusion-culled
MemoryInspector Track mesh pool hit/miss rates and type allocations
ShaderInspector Intercept uniform uploads and detect redundant state changes
FramebufferSnapshot Capture pixels for visual regression comparisons
IndirectDrawInspector Verify GPU indirect draw dispatch and command buffer contents

Server and network

Class Purpose
EmbeddedServerHost Native embedded server with tick control and sandbox isolation
ServerSandbox Isolated temp directory, mod staging, SQLite-safe cleanup
WorldSnapshot In-memory world state capture and rollback
PacketRecorder, RecordedSession Record client and server packets with their frames, and replay them into a client or a server
Host.StateHash Hash the server's blocks, entities and players after each tick, to find where two runs diverge
PacketTracer Assert that specific packet IDs cross the loopback boundary
NetworkDegradationSimulator Inject latency, packet drop, and jitter
Server.ModNetwork, Server.Visibility Record per-player mod messages and inspect actual chunk/entity tracking
PlayerLayout, Server.SameTick Join player crowds and queue reproducible same-tick bot actions

Assertions

Class Purpose
PharosAssert Client-side rendering assertions (draw collapse, chunk visibility, UV, GL leaks)
ClientServerAssert End-to-end block sync, position sync, inventory sync assertions

See the inspection API reference for method signatures and examples.

Documentation

  • Writing Client Scenarios: [ClientScenario], [ClientTheory], [PharosMods], and isolation modes
  • Writing Server Scenarios: [ServerScenario], ServerScenarioBase, IServerTestPlayer, and world helpers
  • Engine Mode: Booting a client that joins a real server offline, with character creation and the vanilla render pipeline
  • Smoke Testing a Mod: pharos smoke and ModSmokeTest, in a terminal or in CI
  • Doctor and Init: pharos doctor, which checks the machine, and pharos init, which writes a first test project
  • Game-state snapshots: Verify converters, block regions, runtime-value scrubbing and game-version baselines for xUnit v2 and v3
  • Templates and the Reusable Workflow: dotnet new pharos-mod-tests, dotnet new pharos-ci and mod-ci.yml, which runs a mod's tests on Linux and Windows
  • Boot Diagnostics: The warnings a client or server logs while booting, [StrictBoot] and [AllowBootDiagnostic]
  • Parallel Runs: pharos run --parallel, which runs test classes in worker processes and merges their results
  • Run Reports: pharos report, which turns a run into an HTML page, a Markdown summary and timings
  • Test Worlds: Schematics, saved worlds with [ServerWorld(SaveFile = ...)], and pharos fixture
  • Failure Artifacts and the Watchdog: The screenshot, logs, traffic and run details a failing scenario saves, and the timeout that stops a hung one
  • Metrics: Named per-test metrics, automatic frame/tick/memory/GL/network/world collectors, and the result.json schema
  • Comparing Before and After: pharos compare, which runs the same tests against a baseline and reports each metric's delta, verdict and budget
  • Server Profiling: Server.Profile.MeasureAsync, which breaks a measured stretch of ticks down by system, mod, block entity, entity, behavior, AI task and player traffic
  • World Metrics: Server.Worldgen, Server.Saves and Server.WorldStats, which attribute generation by pass and handler, split a save by category and mod data key, and count the loaded world
  • Mod Loading: Server.Mods and Client.Mods, which report mod states, systems, run order, JSON patch outcomes, compatibility assets and asset origins
  • Content Sweep: [ContentSweep] and ContentSweep.RunAsync, which place, break, round-trip and spawn each code from a mod
  • Commands: Server.Commands, which reads a mod's command tree, runs its parsers without its handler, and records what the chat handlers did
  • Held Items: the held-item hooks, the mining times and the durability, and the holds a client or a bot makes
  • Containers and Item Flow: the inventories of the chests, the crates and the ground, the totals of a region, and what the hoppers moved
  • Bags, Wearables and Liquids: the contents of a bag, the gear a player wears, and the litres a container holds
  • Spoilage and Temperature: the transitions of a stack, the rates they run at, and the temperature of an item
  • Projectiles: how a shot flies, what it hits and what happened to its stack
  • Block outcomes: the drops of a block, an explosion and the hooks a block calls
  • Block ticks and snow: ticking a block on demand, and the snow that lies on it
  • Animals: what an animal breeds, how it grows and what a hen box holds
  • Traders and villagers: what a trader offers, what a trade did and what a villager says
  • Temporal stability: the stability of a place, storms, rifts, rift wards and temporal gears
  • Multiplayer: player crowds, server mod channels, per-player visibility and same-tick inventory contention
  • Server administration: the whitelist, the bans, the kicks and the data the server keeps of its players
  • Configuration: [WorldConfig], [ServerConfig] and the configuration matrices, which set a scenario's configuration before the server boots and put it back after a test
  • CI and Cloud Environments: Running the suite in CI and cloud environments, offline or online auth, and a dedicated server in Docker
  • Client-Server Testing: [ClientServerScenario], lockstep stepping, and packet assertions
  • HUD inspection: composed stat bars, hotbar, block/entity info, interaction help, messages, boss bars and custom HUD layouts
  • Inspection API Reference: Detailed API for all inspector classes
  • Compatibility Matrix: Supported VS versions, OpenGL requirements, and platform notes
  • Migrating from Atlas: Drop-in shim and step-by-step migration guide
  • Roadmap: Development history and phase completion status
  • Contributing: How to contribute to Pharos

Status

v0.3.0 complete. All 36 issues across milestones M1 through M14 are merged. Packages are available on NuGet. See the roadmap for the full history.

License

MIT

Support

If Pharos helps your mod development, consider supporting via Ko-fi.

  • net10.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.8.0 60 10/26/2026