Developing¶
File structure¶
TimberbornMods/
timberbot/
src/ C# mod (runs inside the game)
TimberbotService.cs Lifecycle, settings, orchestration (7 DI params)
TimberbotEntityRegistry.cs GUID-backed entity lookup + numeric-ID bridge (4 DI params)
TimberbotReadV2.cs All GET read endpoints, tracked refs, and published snapshots
TimberbotWrite.cs All POST write endpoints (22 DI params)
TimberbotPlacement.cs Building placement, path routing, terrain (14 DI params)
TimberbotEvents.cs [OnEvent] publishers → WS broadcaster (5 DI params)
TimberbotDebug.cs Reflection inspector and benchmark (1 DI param)
ITimberbotWriteJob.cs Write job interface for budgeted main-thread execution
TimberbotHttpServer.cs HttpListener, routing, request/response handling
TimberbotJw.cs Fluent zero-alloc JSON writer
TimberbotPure.cs Pure static helpers (no Unity deps, shared with test project)
TimberbotLog.cs File-based error logging, timestamped, thread-safe
TimberbotConfigurator.cs Bindito DI module registration
TimberbotAutoLoad.cs Auto-load a save at main menu via autoload.json or CLI args
TimberbotAutoLoadConfigurator.cs MainMenu context DI registration for auto-load
Timberbot.csproj Build config, game DLL references
manifest.json Mod metadata (version, name, description)
settings.json Persistent settings store (runtime + agent/UI settings, primarily edited in-game)
thumbnail.png Mod thumbnail (rendered in the Mod Manager + README)
test/
Timberbot.Tests.csproj xUnit test project (net8.0, shares source files)
TimberbotJwTests.cs JSON writer tests (primitives, nesting, commas, reuse)
TimberbotPureTests.cs Pure helper tests (parsing, quoting, assertions, normalization)
script/
release.py Build + package + GitHub release script
python/ `tbot` CLI + typed Pydantic client (PyPI: `timberbot`)
src/timberbot/ package source
tests/ pytest suites (unit + httpserver-mocked + integration)
scripts/ codegen / fixture-capture helpers
docs/ Documentation
architecture.md How the mod works (thread model, caching, serialization)
performance.md Measurements, benchmarks, GC pressure, optimization history
developing.md This file (building, testing, contributing)
api-reference.md Endpoint documentation
Building the mod¶
Requires .NET SDK 6+ and Timberborn installed.
This compiles Timberbot.dll and auto-deploys to:
| Platform | Default $(ModDir) |
|---|---|
| Windows / macOS / native Linux | ~/Documents/Timberborn/Mods/Timberbot/ |
| Linux + Proton (auto-detected) | ~/.steam/steam/steamapps/compatdata/1062090/pfx/drive_c/users/steamuser/Documents/Timberborn/Mods/Timberbot/ |
Game DLLs are referenced from:
If your Steam install is elsewhere, override GameManagedDir when building instead of editing the project file:
dotnet build /p:GameManagedDir="D:\Steam\steamapps\common\Timberborn\Timberborn_Data\Managed"
dotnet build /p:ModDir="C:\Users\<you>\Documents\Timberborn\Mods\Timberbot"
On macOS, pass the platform-specific GameManagedDir and ModDir the same way.
For Linux/Proton setups with non-steamuser Wine usernames or custom prefixes, the bundled helper resolves the right ModDir via the same Python logic the CLI uses:
scripts/deploy.sh # auto-detect (honors $TBOT_DOCUMENTS_DIR)
scripts/deploy.sh /custom/mods # explicit override
How the mod works¶
TimberbotConfiguratorregisters all services as singletons in theGamecontext via Bindito DI- On
Load(),TimberbotServicestarts anHttpListeneron port 8085 in a background thread and (whenwsEnabled) a separateTcpListeneron port 8086 for the WebSocket channel - GET requests are handled directly on the background listener thread (reads from
ReadV2published snapshots) - POST requests are queued in a
ConcurrentQueue<PendingRequest>and drained on the main thread UpdateSingleton()runs every frame: drains POST queue, services pending fresh publishes- Game events (
[OnEvent]handlers inTimberbotEvents.cs) and state mutations (TimberbotAgentState.Changed) fan out to the WS broadcaster, which pushesevent/stateframes to every connected client
For full architecture details see architecture.md.
Settings model¶
The in-game Settings modal is the primary configuration surface for Timberbot. For the full key-by-key schema of every config file, see the Configuration Reference.
All settings persist to settings.json, including:
- runtime settings:
httpPort,wsPort,wsEnabled,listenAddress,authToken,debugEndpointEnabled,maxBodyBytes,writeBudgetMs - widget UI settings such as
widgetLeft,widgetTop,actionLoggingEnabled(per-backend model/effort/template now live in~/.config/timberbot/config.toml; the persistedmode/goallive instate.jsonalongsidesettings.jsonand are owned byTimberbotAgentState)
TimberbotService keeps an in-memory settings object and debounces writes back to disk. Editing settings.json directly is supported, but it is the manual/advanced path rather than the default workflow.
Adding a new GET endpoint¶
- Add a
Collect*method toTimberbotReadV2.cs - Add the route to
RouteRequest()inTimberbotHttpServer.cs - If you need new game services, inject them via the
TimberbotReadV2constructor - Add a matching method to
TimberbotClientinpython/src/timberbot/api/client.py
Adding a new POST endpoint¶
- Add an action method to
TimberbotWrite.csorTimberbotPlacement.cs - Add the route to
RouteRequest()inTimberbotHttpServer.cs(POST routes run on main thread) - If you need new game services, inject them via the constructor
- Add a matching typed method to
TimberbotClientinpython/src/timberbot/api/client.py(the operationId inopenapi.yamland the method name must agree — the contract tests will catch drift)
Adding new game DLL references¶
<Reference Include="Timberborn.NewSystem" Publicize="true">
<Private>false</Private>
<HintPath>$(GameManagedDir)\Timberborn.NewSystem.dll</HintPath>
</Reference>
Publicize="true" makes internal types accessible. <Private>false</Private> prevents copying the DLL to output (the game already has it).
CI artifacts (testing a PR locally)¶
Every PR that touches the relevant code paths uploads downloadable artifacts to its workflow runs. Open the PR's Checks tab → click the relevant workflow run → scroll to Artifacts at the bottom.
| Workflow | Artifact name | Contains | Trigger |
|---|---|---|---|
| Python tests | timberbot-py-<pr-number> |
Wheel + sdist for the proposed CLI | Any change under python/ |
| docs | docs-site-<pr-number> |
Fully-built MkDocs site (open index.html locally) |
Any change under docs/ or mkdocs.yml |
To test the proposed Python CLI from a PR:
# download `timberbot-py-<pr>.zip` from the PR's Checks tab, then:
unzip timberbot-py-<pr>.zip -d /tmp/tb
pipx install --force /tmp/tb/timberbot-*.whl
tbot --help
C# mod DLL is not in CI. The mod links against ~80 proprietary Timberborn.*.dll files from your Steam install, so GitHub Actions cannot produce a runnable Timberbot.dll. To test a proposed mod-side change locally, check out the PR branch and rebuild — the post-build target auto-deploys to your Timberborn Mods folder:
gh pr checkout <pr-number> # or: git fetch origin pull/<pr>/head:pr-<pr> && git checkout pr-<pr>
cd timberbot/src
dotnet build # also auto-deploys to ~/Documents/Timberborn/Mods/Timberbot
Testing¶
Unit tests¶
Offline tests for the JSON serializer and pure helper functions. No game required.
141 tests covering TimberbotJw (serialization, comma handling, nesting, reuse) and TimberbotPure (orientation parsing, name cleanup, assertion evaluation, value normalization, shell quoting).
Test suite¶
The legacy timberbot/script/test_*.py harnesses have been migrated into the
pytest tree under python/tests/. Unit tests run without a game; the
integration marker covers the live-game smoke, validation, and performance
suites.
# unit tests only (default; no game required)
python -m pytest python/tests/
# integration tests against a running Timberborn + Timberbot mod
python -m pytest python/tests/integration/ -m integration
# specific integration subsets
python -m pytest python/tests/integration/ -m "integration and write"
python -m pytest python/tests/integration/ -m "integration and slow"
See python/tests/integration/README.md for the runner-level options
(group selection, performance budgets, benchmark iterations).
| Group | Tests | Description |
|---|---|---|
| read | 9 | GET endpoints, projections, map, schema, data accuracy |
| write | 16 | speed, pause, priority, workers, floodgate, recipes, etc. |
| placement | 6 | place/demolish, orientation, find, water, overridable, blockers |
| path | 16 | flat, 1z, 2z (all directions), A* diagonal/obstacle/no-route, sections |
| crops | 6 | crops, tree marking, planting, clear, demolish crop |
| buildings | 6 | detail, inventory, range, recipes, prefab costs, power |
| beavers | 10 | detail, needs, position, district, bots, carrying, durability |
| events | 1 | WS frame fan-out, tbot listen end-to-end |
| cli | 2 | CLI commands, error codes |
| perf | 4 | endpoint latency, building perf, brain perf, v2 parity |
| wipe | 1 | demolish all buildings + clear all crops |
What the tests cover¶
- Smoke: representative coverage of the full
/api/*read surface - Write-to-read: POST change -> first GET sees it -> restore -> first GET sees restoration
- Performance: direct endpoint latency comparisons across the live snapshot path
- Concurrency: simultaneous requests against projection-backed endpoints
- Validation:
test_validation.pycovers 77 tests across 11 groups (read, write, placement, path, crops, buildings, beavers, events, cli, perf, wipe) - Cache invalidation: place path -> count+1, demolish -> count back (EventBus + fresh-on-request snapshots)
- Data accuracy:
validateendpoint compares cached vs live game state per field.validate_allchecks all entities, all fields, 0 mismatches - Burst: 7 sequential calls < 3s total (24ms measured)
- Save-agnostic: discovery phase detects faction, map bounds, existing buildings
- Events (WebSocket):
tbot listenconnects tows://host:wsPort/api/ws, receiveseventframes as in-game state changes
In-game benchmark¶
/api/benchmark (POST, requires debugEndpointEnabled: true in settings.json) runs micro-benchmarks with GC0 tracking. It is queued and stepped under the main-thread write budget, so the request may take multiple frames to complete.
- Collection iteration patterns (foreach vs for-loop, enumerator boxing)
- Game API alloc checks (GetNeeds, Inventories, BreedingPod.Nutrients)
- Lightweight internal helpers like prefab collection
Results include per-test GC0 count, ms/call, and pass/fail. See performance.md for recorded results.
Release¶
This fork ships via GitHub releases only; the Steam Workshop entry "Timberbot API" belongs to the upstream abix-/TimberbornMods project. Users install the fork manually — see Getting Started.
GitHub release¶
This builds a Release DLL, packages a ZIP (Timberbot.dll + manifest.json + thumbnail.png), tags the version, and creates a GitHub release. The tbot CLI ships separately on PyPI as the timberbot package and is not bundled into the mod ZIP.