RimBridgeServer should become a reliable automation layer that lets an AI develop, validate, and debug RimWorld mods against the real game, not a partial simulation. The bridge needs to support unit, UX, and integration testing while keeping the implementation surface intentionally selective: only build features that are broadly useful, stable, and worth maintaining.
The immediate design goal is to turn the current proof-of-useful-tools into a platform with:
- low-latency execution for common operations
- explicit async handling for long-running or frame-bound work
- clean separation between bridge contracts, game adapters, and optional extensions
- strong discoverability for capabilities and events
- a TDD workflow that can scale with the project
As of 2026-03-19, the repository has moved beyond the first vertical slice. It now has:
- a working GABP host and internal capability registry
- bridge diagnostics, journaling, and event/log surfaces
- core and live-smoke test projects
- lifecycle, debug-action, mod configuration, mod settings, architect, selection, notification, screenshot, and UI workbench capabilities
- a growing scripting layer (
run_scriptand the Lua front-end) built on the shared registry
That progress is useful, but the architectural risks this document was written to address still matter:
- the tool surface is monolithic and mixes transport, orchestration, game access, UI state, and serialization
- async behavior is mostly implicit, with ad hoc waiting in specific tools like screenshots
- there is still substantial coupling between the host assembly and first-party capability modules
- capability discovery now exists internally, and the tool surface is now annotation-driven, but longer-form design docs like this one are still hand-maintained and can drift
- third-party extension discovery now exists through shared annotations plus a one-sweep reflected mod scan, but some optional mod integrations still rely on reflection where no compile-time dependency is desired
RimBridgeServer already references Krafs.Rimworld.Ref and swaps the normal game assembly for Assembly-CSharp_publicised.dll during build. This must become a hard rule:
- direct RimWorld access should use the publicized API first
- reflection should be treated as an exception path, not the default
- reflection is only acceptable for third-party mod adapters where there is no stable compile-time dependency
The current project already follows the build pattern used in Achtung2. That is the correct baseline and should stay.
All reads and writes that touch RimWorld state, map state, selection, windows, designators, debug menus, screenshots, save/load, or input must flow through a single execution abstraction that understands:
- main-thread affinity
- frame-bounded work
- long events
- synchronous fast paths
- async wait conditions
We should preserve existing tool names while the internal architecture changes. External automation should not break just because internals are cleaned up.
Full coverage is only realistic if the bulk of new logic lives outside the game-specific adapter layer. The architecture therefore needs a thin RimWorld-facing shell and thick pure logic modules around:
- capability discovery
- request normalization
- script planning
- result envelopes
- operation journaling
- event correlation
- retry and wait policies
Using the decompiler against the publicized RimWorld reference, the following seams are already available and should anchor the architecture:
Verse.GameDataSaveLoader.SaveGameandLoadGameVerse.LongEventHandler.QueueLongEvent,ExecuteWhenFinished, andAnyEventNowOrWaitingVerse.ScreenshotTaker.TakeNonSteamShotandQueueSilentScreenshotVerse.Messages.MessageVerse.LetterStack.ReceiveLetterVerse.PlayLog.AddVerse.Log.Notify_MessageReceivedThreadedInternalVerse.DebugWindowsOpener.ToggleDebugActionsMenuLudeonTK.Dialog_Debug.TrySetupNodeGraphandGetNodeVerse.Command.ProcessInputVerse.Designator.ProcessInputVerse.DesignatorManager.SelectandDeselectRimWorld.MapInterface.HandleMapClicksandHandleLowPriorityInputVerse.LoadedModManager.ReadModSettingsandWriteModSettings
These seams are enough to implement most of the requested core feature set without inventing large amounts of game logic ourselves.
The bridge should not be designed as a growing bag of GABP methods. Internally it should expose a capability registry where each capability declares:
- a stable id
- category
- description
- argument schema
- result schema
- execution kind
- whether sync execution is supported
- whether it emits events
GABP tools then become one transport projection of that internal registry.
If RimBridgeServer supports extension packages for other mods, the same principle should govern our own features. That means:
- core RimBridgeServer features register through the same provider contract as external extensions
- optional first-party feature groups should live in separate packages or projects when their scope justifies it
- the host should not special-case first-party capabilities beyond bootstrapping trusted default providers
- every capability package, whether shipped by us or another mod, should be discoverable through the same registry
This prevents the architecture from drifting into two incompatible systems: one for built-in features and one for extensions.
Explicit async instead of hidden waiting
Every operation should clearly declare whether it is:
- immediate
- frame-bound
- long-event-bound
- background observed
The caller can then choose:
immediateto fail fast if the game is not readywaitto block until completion or timeoutqueueto receive an operation id and poll or subscribe for completion
Human-friendly resolution like pawn name matching is useful, but the internal API should prefer stable handles once something is resolved:
- map id
- thing id
- pawn id
- window id
- menu id
- operation id
- screenshot id
This reduces ambiguity and repeated lookup cost.
Everything not inherently tied to RimWorld runtime objects should live in shared, testable libraries.
This is an aspirational split, not the current repository layout. The repo still uses a flatter Source/ structure plus focused shared libraries, but this is the direction a larger refactor could move toward:
Source/
RimBridgeServer.Host/ // net472 RimWorld entry assembly and GABP host
RimBridgeServer.Contracts/ // schemas, ids, result envelopes, script AST
RimBridgeServer.Core/ // registry, execution policies, journaling, script engine
RimBridgeServer.Game/ // shared RimWorld adapter helpers
RimBridgeServer.Capabilities.Core.Diagnostics/
RimBridgeServer.Capabilities.Core.Lifecycle/
RimBridgeServer.Capabilities.Core.Selection/
RimBridgeServer.Capabilities.Core.View/
RimBridgeServer.Capabilities.Core.DebugActions/
RimBridgeServer.Capabilities.Optional.Pawns/
RimBridgeServer.Capabilities.Optional.UI/
RimBridgeServer.Extensions.Abstractions/ // provider contract used by all packages
RimBridgeServer.Extensions.Example/ // example third-party adapter
Tests/
RimBridgeServer.Contracts.Tests/
RimBridgeServer.Core.Tests/
RimBridgeServer.Game.Integration/
RimBridgeServer.E2E/
docs/
architecture.md
lua-frontend-design.md
This can be introduced incrementally. There is no need for a single risky big bang move. The important part is that feature packages, including first-party ones, plug into the same registry and execution model.
Responsible for:
- bootstrapping the mod
- starting and stopping the GABP server
- exposing legacy tool aliases
- exposing internal capability discovery
- managing subscriptions for events and operation updates
Suggested types:
BridgeHostToolFacadeCapabilityRegistryLegacyToolMapper
Responsible for:
- game-thread dispatch
- safe synchronous execution
- wait conditions
- long-event coordination
- timeout and cancellation handling
- operation journaling
Suggested types:
IGameThreadDispatcherGameThreadDispatcherIOperationRunnerOperationRunnerOperationJournalWaitConditionExecutionModeOperationStatus
Capabilities should be grouped by domain, with each domain owning request normalization and response shaping for its own area.
Each domain should preferably ship as a provider package that registers one or more capabilities into the shared registry. For first-party code, that means the host composes a set of provider packages rather than directly owning all behavior.
Core domains:
- diagnostics and logging
- game lifecycle and state
- pause and time control
- save and load
- screenshot and view targeting
- input and cursor control
- selection
- settings and mod configuration
- debug actions
- scripting and batch execution
Optional domains:
- pawn state and commands
- faction state
- context menus
- widget row and gizmo access
- inspect pane
- designators
Extension domains:
- capabilities registered by other mods
- adapters for known mods
Responsible for turning game activity into structured events.
Core event sources should include:
- bridge operation lifecycle
- long event start and completion
- warnings and errors from
Verse.Log - message feed from
Verse.Messages - letter feed from
Verse.LetterStack - play log additions
- selection changes
- map changes
- active window changes
- screenshot completion
Suggested types:
IEventSourceEventBusEventEnvelopeObservationSnapshotStateProbe
Responsible for executing batches of capability calls with low overhead, consistent waiting semantics, and a detailed report.
Suggested types:
ScriptDefinitionScriptStepScriptExecutionContextScriptRunnerScriptReportStepReport
The internal contract model should be established early and then reused everywhere.
Each capability descriptor should contain:
IdCategorySummaryArgumentsSchemaResultSchemaExecutionKindSupportsImmediateSupportsWaitSupportsQueueEmitsEventsSourcesuch ascore,optional, orextension
Every operation should return a consistent envelope:
{
"success": true,
"operationId": "op_123",
"status": "completed",
"startedAtUtc": "2026-03-16T12:00:00Z",
"completedAtUtc": "2026-03-16T12:00:00Z",
"durationMs": 12,
"result": {},
"warnings": [],
"events": []
}Even immediate operations should have an operationId so logs, events, and reports can correlate cleanly.
Introduce small, reusable reference types:
MapRefPawnRefThingRefWindowRefMenuRefCellRefScreenRectRef
Avoid ad hoc sleeping loops inside capabilities. Centralize wait behavior around named conditions such as:
game.idlelong_event.nonewindow.openwindow.closedselection.matchesscreenshot.existslog.contains
These belong in RimBridgeServer itself because they are broadly useful across mod development.
- process and game running status
- active program state
- current map and loaded game summary
- Player.log tail and structured in-game log stream
- warnings and errors subscription
- pause and unpause
- speed control
- explicit
wait_until_idle wait_frameswait_for_long_event
- list saves
- save game
- load game
- quick snapshot save for test fixtures
- fixture restore helpers
- camera state
- jump and frame
- screenshot capture
- clipped screenshot capture
- semantic screenshot targeting
- screenshot metadata including map, selection, and camera context
- mouse position
- mouse click
- keyboard input
- selection read and mutate
- click by screen rect or semantic target
- input must work even when RimWorld is not the foreground application, which rules out a foreground-only desktop automation design
- RimWorld prefs
- mod settings persistence through
LoadedModManager - safe discovery of loaded mod settings surfaces
- discover debug action tree
- resolve action by path
- execute action directly or through a UI-visible mode when needed
- support pinning and toggles where exposed by
DebugActionNode
- run a structured batch
- choose sync or async execution mode per step
- capture detailed report and intermediate artifacts
These are useful, but should sit behind separate modules so they can evolve independently.
- pawn state and commands
- faction state
- context menu inspection and execution
- widget row and bottom-left gizmo access
- inspect pane extraction
- designator discovery and application
The extension model should allow external mods to register additional capabilities with descriptors that look identical to core ones.
Debug action access should not devolve into custom tool-per-action code. The correct design is:
- use the internal debug node graph as the discovery surface
- expose nodes by stable path
- let callers query children before execution
- support direct execution where the node semantics allow it
- preserve a UI-backed fallback path for actions that require actual dialog interaction
This is one of the biggest opportunities to reduce waiting time because it avoids implementing one-off wrappers for individual debug items.
The input stack should support two modes.
The preferred mode for automation:
- resolve a semantic target such as a selected pawn, menu option, designator, or gizmo
- execute through the underlying command object when possible
- return structured evidence of what was targeted
Required for cases where the UI itself is under test:
- screen coordinate targeting
- rect clipping
- mouse move and click
- key press simulation
- screenshot before and after action
Physical mode should still be implemented inside RimWorld's process or window event path wherever possible. Foreground-dependent OS desktop input is not a sufficient design because automated test runs may keep RimWorld in the background.
This split matters because functional automation and UX automation are different jobs. We should not pay the cost of physical UI simulation when a direct command path is available.
The event system should be treated as a first-class API, not a future add-on.
Each event should include:
sequencetimestampUtccategorytypesourceoperationIdwhen relevantpayload
Core categories:
bridgeoperationgamelong_eventlogmessageletterselectionwindowcapability
For real-time debugging, the log pipeline should combine:
- tailing
Player.log - patched
Verse.Log.Notify_MessageReceivedThreadedInternal - structured warnings and errors raised by bridge code itself
Do not start with a custom textual parser. That is unnecessary risk. The low-risk first version should be a structured JSON script format that every client can generate easily.
Suggested first shape:
{
"name": "load-fixture-and-run-debug-action",
"defaults": {
"mode": "wait",
"timeoutMs": 10000
},
"steps": [
{ "call": "rimworld.load_game", "args": { "saveName": "fixture_a" } },
{ "wait": { "condition": "game.idle", "timeoutMs": 60000 } },
{ "call": "rimworld.debug_actions.execute", "args": { "path": "Pawns/..." } },
{ "call": "rimworld.take_screenshot", "args": { "fileName": "after_action" } }
]
}The important property is not syntax. The important property is that every step can target any registered capability, including extension capabilities, and produces a uniform report.
Later, a human-friendly DSL can be layered on top if it is still worth it.
The current concrete recommendation for that next layer is documented in docs/lua-frontend-design.md: use Lua syntax via MoonSharp, but keep the existing script runner as the shared execution backend instead of inventing a second direct automation runtime.
The bridge now uses annotation-based discovery for third-party tools instead of a second manual registration model.
Current model:
- third-party mods reference
RimBridgeServer.Annotations - RimBridgeServer delays GAB startup until
LoadedModManager.InitializeModshas completed - once all mods are initialized, the host scans each loaded mod assembly exactly once for annotated public tool methods
- discovered tools are registered once into both the capability registry and the live GAB tool surface
- each mod is isolated by
try/catch, so one failing scan does not block other mods
Supported authoring shapes:
- public static methods on any loaded mod assembly type
- public instance methods on a loaded
Verse.Modhandle type - public instance methods on a public parameterless tool class
Design constraints:
- keep the shared package annotation-only so participating mods do not need a heavy runtime dependency
- keep discovery one-shot and startup-bound rather than re-scanning during live execution
- continue to prefer publicized RimWorld APIs for actual game access; reflection is for extension discovery and optional third-party adapters, not routine game logic
Target full coverage by pushing almost all branching logic into Contracts and Core, then keeping Game adapters thin and validated with deterministic in-game scenarios.
Scope:
- capability registry
- request validation
- result envelopes
- wait condition evaluation
- operation journal
- script planning and report generation
- event correlation
- sync vs queue policy logic
These should aim for near-100 percent line and branch coverage.
Scope:
- legacy tool alias mapping
- capability descriptor completeness
- schema compatibility
- serialization stability
Scope:
- real save and load
- pause and time control
- selection behavior
- screenshot capture
- debug action discovery and execution
- log and message capture
- designator and command invocation
These should run against stable fixture saves and quick-test colonies.
Scope:
- launch RimWorld
- connect through GABP
- execute scripts end to end
- verify screenshots, logs, and operation reports
Every async-capable feature should be exercised across:
- immediate success
- queued completion
- long-event overlap
- timeout
- cancellation
- missing target after map or state change
- event correlation correctness
Each incremental step should follow the same discipline:
- add or update tests first
- implement the smallest coherent vertical slice
- run focused tests locally
- run a build of the mod
- if the step touches game integration, run the relevant GABS-driven smoke case
- update the relevant design or usage docs when the public surface changes
- commit
- push
The rule is to keep the tree in a releasable state after every step.
Deliverables:
- architecture document
- progress log
- explicit constraints around publicized RimWorld usage
Exit criteria:
- agreed target structure exists in-repo
Deliverables:
ContractsprojectExtensions.Abstractionsproject with the provider contract used by both first-party and third-party packages- operation envelope types
- capability descriptor types
- shared ids and references
- adapter layer that keeps current tools working
Tests:
- serialization
- validation
- compatibility
Deliverables:
GameThreadDispatcherOperationRunner- timeout and wait condition support
- operation journal
Tests:
- main-thread dispatch behavior with fakes
- timeout behavior
- queued vs immediate execution
Deliverables:
- current features moved out of
RimBridgeTools - first-party provider packages for the existing feature groups
- legacy tool aliases preserved
- new registry-backed dispatch
Tests:
- capability discovery
- legacy alias contract tests
- integration smoke for existing feature set
Deliverables:
- event bus
- structured in-game log capture
- Player.log tail reader
- long-event observation
Tests:
- event envelopes
- log capture filtering
- long-event state transitions
Deliverables:
- consolidated lifecycle service
- wait helpers
- faster sync control paths for common flows
Tests:
- save/load lifecycle
- pause and speed behavior
- wait conditions around long events
Deliverables:
- screen and map target references
- clipped screenshot support
- semantic target resolution
- input service abstraction
Tests:
- unit tests for target resolution
- integration tests for screenshot and selection flows
- UX smoke tests with fixture screenshots
Deliverables:
- debug action discovery
- debug action path execution
- reportable debug-action results
Tests:
- path resolution
- discovery stability
- integration execution cases
Deliverables:
- gizmo access
- context menu improvements
- designator discovery
- god-mode designator selection and application
- inspect pane and widget row extraction
Tests:
- per-adapter integration tests
- designator execution cases in god mode
- UI regression scenarios around structure placement
Deliverables:
- JSON script format
- script execution engine
- step-level report
- mixed sync and async steps
Tests:
- report correctness
- rollback and failure reporting semantics
- end-to-end script execution
Notes:
- v1 should stay deliberately small: ordered capability calls, explicit per-step reports, and no separate DSL runtime
- every registered capability, including future extension capabilities, should be scriptable automatically through the shared registry rather than a second plugin model
- the next increment after the first runnable slice should add controlled step-output references so scripts can create something in one step and consume its id in a later step
Deliverables:
- extension abstraction package
- annotation package and startup-bound discovery lifecycle
- extension discovery endpoint
- first sample extension
Tests:
- discovery
- namespacing
- script access through extension capabilities
Deliverables:
- fixture management
- canned repro scripts
- coverage gates for
ContractsandCore - nightly end-to-end runs
Tests:
- complete automated smoke matrix
- prefer internal direct execution over UI simulation when the goal is functionality testing
- prefer UI simulation when the goal is UX validation
- prefer JSON scripts over a custom DSL in v1
- prefer annotation-based one-sweep extension discovery after all mods initialize over ad hoc per-tool reflection
- prefer stable ids over repeated fuzzy name lookup
- prefer publicized RimWorld APIs over reflection
- prefer reflection only in isolated third-party adapters
The next concrete slice is no longer fixed in this document. The current scripting track is functional through JSON, inline Lua, and file-backed Lua fixtures, so the near-term choice is between broadening reusable Lua examples/reference around planning patterns or switching tracks to the structured pawn-event journal backlog. If we switch tracks, the lowest-risk event slice is still job_changed, draft_changed, and mental_state_changed with cursor-based pull as the correctness path and host-level push as an optional acceleration path.