Entropy Limiters: The Effects Boundary Pattern (Why We Need It)
Context
As GridPlacement/GridBuilding evolves toward Rimworld-scale complexity, the biggest long-term risk is not a single bug—it’s architectural entropy:
- Small “quick fixes” accumulate as ad-hoc engine glue.
- Side-effects (spawn, free, audio, UI, commits) spread across unrelated systems.
- Tests become large, flaky, and expensive (because the behavior is no longer isolated).
An entropy limiter is a deliberate boundary that concentrates complexity so it cannot spread.
Definition: “Entropy limiter”
An entropy limiter is a boundary that:
- Forces all side-effects through one narrow interface.
- Is easy to unit test (headless, deterministic).
- Makes it obvious when a developer is about to introduce a “just do it here” engine mutation.
It limits entropy by making the easy path also the correct path.
The Effects Boundary
What it is
The Effects Boundary is the single component that translates domain/orchestrator outputs into typed requests for engine work.
GDScript 5.1:
GBEffectApplier- Consumes:
GBOrchestratorOutput(containseffects: Array[GBOrchestratorEffect]) - Emits: typed signals (
preview_ensure_requested,indicator_show_requested,placement_commit_requested, …)
- Consumes:
C# 6.0:
GPEffectApplier- Consumes:
GPOrchestratorOutput(containsIReadOnlyList<GPEffect>) - Emits: typed events (
PreviewEnsureRequested,IndicatorShowRequested,PlacementCommitRequested, …)
- Consumes:
What it is NOT
- It does not spawn/free nodes.
- It does not mutate UI.
- It does not call services/orchestrators.
- It does not validate domain correctness.
It is a mapping layer only.
Why we need it (Rimworld-scale justification)
1) Prevent glue sprawl (the #1 testability killer)
Without an effects boundary, the following tends to happen:
- placement adapter plays a sound “just here”
- manipulation system spawns a preview “just here”
- UI node commits placement “just here”
Each individual change feels harmless, but collectively:
- the engine-glue is everywhere
- bugs become cross-system
- fixes require integration scenes
- tests become slow and brittle
With an effects boundary:
- orchestrators remain pure coordination
- adapters remain thin
- all engine work is requested in one place
2) Make behavior testable at the cheapest layer
A good Rimworld-like architecture relies on:
- lots of unit tests
- few integration tests
Effects boundaries enable unit tests like:
- “Given effects A,B,C → emit signals/events X,Y,Z in order”
This can run:
- without scenes
- without Godot runtime
- deterministically
3) Make “new side-effect types” explicit and reviewable
When a new side-effect is needed (new UI, new audio, new VFX), it becomes:
- a new
EffectTypeenum member - a new typed signal/event
- a unit test update
That creates a high-signal code review diff and prevents accidental coupling.
4) Reduce regressions during refactors
Rimworld-scale code evolves constantly.
When engine work is centralized:
- refactors of UI/presentation don’t require rewriting domain tests
- refactors of domain orchestration don’t break scenes
Practical rules
- Rule 1: Orchestrators output requested effects only (models + effect list).
- Rule 2: Only the Effects Boundary converts effects → engine requests.
- Rule 3: Presentation systems subscribe to signals/events and do the scene work.
- Rule 4: Add/maintain unit tests for effect→event mapping (order preserved).
Links
- GDScript:
res://addons/grid_building/core/services/gb_effect_applier.gd - GDScript models:
res://addons/grid_building/systems/orchestrator/models/orchestrator_effect.gdres://addons/grid_building/systems/orchestrator/models/orchestrator_output.gd
- C#:
GridPlacement.Core.Services.Presentation.GPEffectApplierGridPlacement.Core.Services.Presentation.GPEffect/GPEffectType/GPOrchestratorOutput