On this page
Development
Building and testing #
You need the .NET 10 SDK and a Vintage Story 1.22.x install, with VINTAGE_STORY pointing at
the folder that holds VintagestoryAPI.dll and VintagestoryLib.dll (the .pdb next to the
first is required too, or the engine's logger crashes at boot). Both dlls are compile-time
references only; neither is copied into the mod, which still ships as one file.
export VINTAGE_STORY=/path/to/vintagestory
dotnet build Pulse.slnx -c Release
dotnet test # unit tests, then the Atlas scenarios
dotnet build Pulse/Pulse.csproj -c Release -t:PackageMod # artifacts/pulse_x.x.x.zip
dotnet build Pulse.Otlp/Pulse.Otlp.csproj -c Release -t:PackageMod # artifacts/pulseotlp_x.x.x.zipThe scenarios in Pulse.Scenarios boot a real headless server in-process through
Atlas, load the mod, and scrape it over HTTP for real. The
atlas CLI runs the same assembly without VSTest, which is faster to iterate against:
atlas run Pulse.Scenarios/bin/Release/net10.0/Pulse.Scenarios.dll
atlas run Pulse.Otlp.Scenarios/bin/Release/net10.0/Pulse.Otlp.Scenarios.dllPulse.Otlp.Scenarios is a separate project because it stages both mods, laid out exactly as
their zips are, and the base suite's staging should stay as it is. It stands up a fake collector,
points the mod at it, runs the world, and asserts on the protobuf that arrives. There is one
collector per protocol the config accepts: an HttpListener for http/protobuf, and for grpc a
small HTTP/2 server, since a gRPC client wants its own service path, a length-prefixed message
and a status trailer before it calls an export delivered.
Unit tests in Pulse.Tests cover the aggregator, the exposition writer, the log classifier and
the small classes behind the wave of engine and world metrics: the busy-time average, the ping
aggregates, the entity top-ten with its series retirement rule, and the suspend window. None of
them needs a server. Pulse.Otlp.Tests covers the config translation, which is where the OTLP
mod's only non-obvious logic lives. CI also runs both unit suites on Windows. The scenarios stay
on Linux, since they boot a server build made for it.
Mutation testing runs at two depths. tools/mutation-check.sh applies ninety-one representative
mutations one at a time and requires the suite to fail on every one; CI runs it on every code
change, deterministic and done in about six minutes. .github/workflows/mutation.yml runs
dotnet-stryker incrementally on pull requests into dev touching Pulse/: it mutates only the
files the pull request changed and reports without gating. The full run mutates the whole project
except the files that only run under a live server, scored 94 percent at the 0.2.0 release, fails
below 83, and runs weekly and on workflow_dispatch. Pulse.Otlp's own Stryker lane is
workflow_dispatch only, because Stryker launches the wrong project's test host for it and the
score swings too much between runs to gate a pull request on; it scored 81 percent at 0.2.0 and
fails below 70.
Static analysis runs on SonarCloud
for pushes to dev and pull requests into it. The job waits for the quality gate, so a failing
gate turns the check red instead of sitting unnoticed on SonarCloud's side. The gate judges new
code only: an A rating for reliability, security and maintainability, at least 80 percent
coverage, at most 3 percent duplication, and every security hotspot reviewed. The coverage badge
at the top of this page comes from the same job, which runs the two unit suites and the base
scenarios under coverlet. Five files are left out of that figure: the two ModSystems, the engine
probe, the attribution probe and the live-server half of the attribution metrics. The game's
loader loads the staged dll outside coverlet's instrumentation, so nothing a scenario executes in
them can reach a coverage report. The scenarios are still what tests them.
The documentation site is built by docs/site/build.mjs from the README, the getting-started
guide, the two contrib READMEs, the alert rules, the dashboard JSON and the changelog. It also
reads both modinfo.json, NOTICE and the replies in Pulse/PulseCommands.cs, and it stops when
any of these changes in a way a page depends on: a renamed section, a reworded /pulse reply. So
a pull request that touches no document can still fail the pages workflow, which runs the build
on every pull request and deploys the result from main. The build needs Node 22 or newer and
has two dependencies.
cd docs/site
npm ci
npm test # the build rules and the logic of the client scripts
npm run build # writes docs/site/dist
npm run serve # serves it on http://127.0.0.1:4173 (needs Python 3)Where this is going #
The survey in docs/metrics-feasibility.md lists what is measurable on this engine and what is
not. Save duration is the honest gap: the world-save event fires before any writing happens and
there is no completion signal, so Pulse reports the suspend window instead of inventing a
number. Per-player traffic does not exist anywhere in the engine, not even internally.
Traces and logs over OTLP would reuse most of the exporter mod, but neither has an obvious consumer on a game server yet, so they stay unbuilt until someone asks.
License #
Source: README.md