Grid Placement

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 (contains effects: Array[GBOrchestratorEffect])
    • Emits: typed signals (preview_ensure_requested, indicator_show_requested, placement_commit_requested, …)
  • C# 6.0: GPEffectApplier

    • Consumes: GPOrchestratorOutput (contains IReadOnlyList<GPEffect>)
    • Emits: typed events (PreviewEnsureRequested, IndicatorShowRequested, PlacementCommitRequested, …)

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 EffectType enum 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).

  • GDScript: res://addons/grid_building/core/services/gb_effect_applier.gd
  • GDScript models:
    • res://addons/grid_building/systems/orchestrator/models/orchestrator_effect.gd
    • res://addons/grid_building/systems/orchestrator/models/orchestrator_output.gd
  • C#:
    • GridPlacement.Core.Services.Presentation.GPEffectApplier
    • GridPlacement.Core.Services.Presentation.GPEffect / GPEffectType / GPOrchestratorOutput