Loading…
gathering yarn balls…
Cat Game Research
Frontier
Dev-log transmissions from the expedition

The Dromed Trap/Trigger System: Thief II's Logic Wiring in CatGame

How Looking Glass Studios' Dark Engine trap/trigger architecture from Thief II inspired a data-driven ECS puzzle system in CatGame — with link graphs, Act/React stimuli, and designer composition patterns.

In 2000, Looking Glass Studios shipped Thief II: The Metal Age, built on the Dark Engine — an object system where every entity is an integer ID with typed properties, directed links, and a message protocol. The trap/trigger system, documented in a technical design document by Marc LeBlanc, is one of the most elegant data-driven puzzle architectures ever shipped in a game engine. This post traces its adaptation into CatGame's ECS.

"Everything a designer needs is already present in the core object model; the TrapTrig hierarchy and the ControlDevice protocol are merely the conventional vocabulary that evolved on top of it." — Marc LeBlanc, Dromed TDD

The Dark Engine Object Model

The Dark Engine's core is deceptively simple. Every game entity is an object: a unique integer ID with a minimal footprint. Behavior is assembled from three primitives:

Links
First-class directed edges with a named "flavor" (ControlDevice, Contains, Target, ScriptParams). Bidirectionally queryable. Optionally carry data.
Scripts
Object Script Modules (OsMs) — native code that receives messages. Attached via the S→Scripts property.
Messages
TurnOn / TurnOff are the primary trap protocol. Others exist for physics, frob, damage. Simple, reliable, composable.

The trap system lives under the fnord → TrapTrig branch of the object hierarchy. fnord objects are invisible logic nodes — zero-cost abstract nodes that designers litter throughout a level without performance fear. This is the key insight: logic is cheap, rendering is expensive.

CatGame's ECS Translation

CatGame uses a C++20 ECS architecture with EnTT. The mapping from Dark Engine concepts to ECS is direct:

Dark EngineCatGame ECSStatus
Object (integer ID)entt::entity✅ Done
Properties (typed data bags)ECS components✅ Done
Links (directed, flavored)LinkComponent + LinkFlavor✅ Done
ControlDevice flavorLinkFlavor::ControlDevice✅ Done
TurnOn / TurnOff messagesControlDeviceSignalEvent✅ Done
TrapTrig (invisible logic node)Receiver entities + ControlDeviceReceiverComponent✅ Done
RequireAll / RequireAnyrequire_all field✅ Done
Inverterinvert field✅ Done
Signal depth limitkMaxSignalDepth = 8✅ Done
Cycle detectionvisited vector✅ Done
TOML level definitions[[links]] + [entities.name]✅ Done

The Link Graph: Wiring the World

In Dromed, designers wire the world by creating ControlDevice links between objects. A button links to a door. A pressure plate links to an emitter trap. A relay trap links to multiple targets with ordered, delayed signals. The link graph is the puzzle logic.

ButtonRelayTrapDoorLightTurnOnorder=0order=1, delay=0.5s
Figure 1: A button → relay → door+light link graph with ordered, delayed dispatch

CatGame's LinkSystem stores links as entities with a LinkComponent holding from, to, flavor, and a LinkData struct with signal, order, delay, one_shot, and consume_source fields. The ControlDeviceSystem dispatches signals along links, respecting ordering and delay, with a priority queue keyed by (fire_time, order, link_entity).

Trap-Control Flags (Phase B)

Dromed receivers support per-receiver flags that control signal filtering. CatGame implements all five:

FlagBehaviorStatus
OnceFire only once, then ignore further inputs✅ Implemented
NoOffIgnore TurnOff signals (latch on)✅ Implemented
OffFilterOnly react to TurnOff, not TurnOn✅ Implemented
RevertAuto-revert to false after revert_delay seconds✅ Implemented
DirectionGoForward / GoReverse signal direction❌ Not yet

Specialized Trap Archetypes (Phases C–D)

Dromed's TrapTrig hierarchy includes specialized archetypes that CatGame implements as ECS components:

RelayTrap
Pure message relay. Forwards TurnOn/TurnOff along ControlDevice links. Supports Once/NoOff flags. The workhorse of complex logic chains. ✅ Implemented
DestroyTrap
Destroys (or slays) linked objects on TurnOn. One-shot cleanup, collapsing floors. ✅ Implemented
FlipFlopTrap
Toggles state on each TurnOn, emits TurnOn/TurnOff alternately. Two-state toggle from a single input. ✅ Implemented
EmitterTrap
Spawns projectile entities on rising edge. Copies emitter transform. Halt flag for one-shot. ✅ Implemented

Act/React: The Stimulus System (Phase E)

The Act/React system is Dromed's continuous-signal layer, orthogonal to the discrete TurnOn/TurnOff protocol. An object can have Sources (stimulus + propagator + intensity) and Receptrons (stimulus listener + threshold + effect). When a source's stimulus reaches a receptron at sufficient intensity, the receptron fires its effect.

StimulusSourcefire, intensity=5radius=5.0ReceptronDestroy, thresh=1ReceptronDamage, 50/sfire stimfire stim→ entity destroyed→ health -= 50*dt
Figure 2: A fire stimulus source propagates via radius to two receptrons with different effects

Propagators

Four propagator types control how stimuli travel from source to receptron:

PropagatorMechanismStatus
ContactStimulus fires on collision/overlap between source and target✅ Implemented
RadiusSpherical propagation; intensity may attenuate with distance. Uses SpatialHashGrid for broad-phase culling.✅ Implemented
FlowStimulus flows along ControlDevice links from source to directly linked targets✅ Implemented
ScriptDirect application to all matching receptrons regardless of spatial proximity✅ Implemented

Receptron Effects

EffectBehaviorStatus
DamageApplies damage_amount * dt to HealthComponent. Sets is_dead at 0 health.✅ Implemented
DestroyRemoves the entity from the registry✅ Implemented
SendMessageDispatches a ControlDeviceSignalEvent with the configured message✅ Implemented
AddMetapropertyAdds a metaproperty to the entity (behavior modification)❌ Not yet

The StimTrap Bridge

The StimTrapComponent bridges the discrete ControlDevice protocol and the continuous Act/React system. When a receiver with a StimTrap gets TurnOn, it activates a StimulusSourceComponent with the configured stimulus type and intensity. On TurnOff, it either deactivates the source or emits a brief negative-intensity pulse if negative_intensity > 0.

ButtonStimTrapreceiverStimulusSourcefire, radius=5ReceptronDestroyTurnOnactivatesfire stim
Figure 3: The StimTrap bridge — discrete TurnOn activates a continuous stimulus source

Designer Composition Patterns (Phase F)

The TDD describes six standard composition patterns. CatGame implements and tests all of them in a fixture level (trap_fixture.toml) with 9 integration tests:

Button→Door

Pattern 1: Basic ControlDevice link. The simplest composition.

PressurePlate→EmitterTrap→Projectile

Pattern 2: Physics volume triggers projectile spawn.

Button A↘RelayTrap (Once)→Target A (order=0)
Button B↗→Target B (order=1, delay=0.5s)

Pattern 3: Complex logic with ordered, delayed relay dispatch.

Trigger→DestroyTrap (Once)→Debris × N

Pattern 4: One-shot collapsing floor.

Lever→FlipFlopTrap→Gate (toggle)

Pattern 5: Two-state toggle from a single input.

Button→StimTrap→StimulusSource→Receptron (Destroy)

Pattern 6: Discrete signal → continuous stimulus → effect.

TOML Level Definition

CatGame levels are TOML files. The trap/trigger system is fully data-driven — designers wire logic by editing TOML, not C++:

# Button → Door [entities.button_01] archetype = "billboard"position = [-8.0, 0.0, 0.0] visible = false [entities.door_01] archetype = "billboard" [entities.door_01.control_device_receiver] require_all = falseinvert = false# StimTrap → Receptron bridge [entities.stim_trap_01.stim_trap] stimulus_type = "fire"intensity = 5.0 radius = 5.0 [entities.victim_05.receptron] stimulus_type = "fire"effect = "Destroy"threshold = 1.0 [[links]] from = "button_01"to = "door_01"flavor = "ControlDevice" [links.data] signal = "TurnOn"order = 0 delay = 0.0

Parity with the TDD

The Dromed TDD (by Marc LeBlanc) defines the full trap/trigger architecture. Here's where CatGame stands against it:

TDD FeatureCatGame Status
Object = integer ID✅ EnTT entity
Properties (typed data bags)✅ ECS components
Links (directed, flavored, data-carrying)✅ LinkComponent
ControlDevice link flavor✅ LinkFlavor::ControlDevice
TurnOn / TurnOff message protocol✅ ControlDeviceSignalEvent
Trap-control flags (Once, NoOff, OffFilter, Revert)✅ 4 of 5 (Direction pending)
RelayTrap✅ RelayTrapComponent
DestroyTrap✅ DestroyTrapComponent
FlipFlopTrap✅ FlipFlopTrapComponent
EmitterTrap (projectile spawn)✅ EmitterTrapComponent + ProjectileComponent
Act/React Sources✅ StimulusSourceComponent
Act/React Receptrons✅ ReceptronComponent
StimTrap bridge✅ StimTrapComponent
Propagators (Contact, Radius, Flow, Script)✅ All 4 implemented
Receptron effects (Damage, Destroy, SendMessage)✅ 3 of 4 (AddMetaproperty pending)
Link ordering + delay✅ LinkData::order + delay
Signal depth limit + cycle detection✅ kMaxSignalDepth=8 + visited vector
Designer composition patterns✅ 7 patterns tested in trap_fixture.toml
Direction (GoForward / GoReverse)❌ Not yet
AddMetaproperty receptron effect❌ Not yet
Physics-volume triggers (BoundsTrigger)❌ Not yet (physics disabled in PnC)
RequireOne / N-of-M gating❌ Not yet
RandomRelay (random destination)❌ Not yet
QuestVar traps❌ Not yet
TeleportTrap❌ Not yet
TimedStimulator / TrapTimer❌ Not yet
Metaproperty add/remove (A-links / R-links)❌ Not yet

Test Coverage

Link Unit Tests
33 test cases, 166 assertions. Covers receiver flags, relay traps, destroy traps, flip-flop, emitter+projectile, stim trap bridge.
Stimulus Unit Tests
12 test cases, 42 assertions. Covers all 4 propagators, threshold, cooldown, damage+health, destroy effect.
Trap Fixture Integration
9 test cases, 54 assertions. All 7 composition patterns + NoOff + OffFilter flags.
PnC Integration
19 test cases, 219 assertions. Cross-level state, inventory, puzzle guards, interaction rollback.

Related Pages

Glossary

ControlDevice
The primary link flavor in Dromed. When a source object fires TurnOn/TurnOff, the signal travels along all ControlDevice links from that object to their targets.
Fnord
The Dromed category for invisible logic objects. TrapTrig, RelayTrap, and other logic nodes live under fnord in the object hierarchy. They have no renderable model and minimal overhead.
Receptron
A listener for a specific stimulus type. When the stimulus reaches the receptron at sufficient intensity (above threshold), the receptron fires its configured effect.
Stimulus Source
A continuous signal emitter. Defined by stimulus type, propagator (Contact/Radius/Flow/Script), intensity, radius, and lifecycle.
Trap-Control Flags
Per-receiver flags that filter incoming signals: Once (fire once only), NoOff (ignore TurnOff), OffFilter (only react to TurnOff), Revert (auto-revert after delay), Direction (signal direction control).
StimTrap
A bridge component that converts discrete TurnOn/TurnOff signals into continuous stimulus source activation/deactivation.
Caveats & Limitations
  • Thief II screenshots are copyrighted by Looking Glass Studios / Eidos Interactive. All diagrams in this post are original SVG illustrations created by the CatGame team, not game assets.
  • The Dromed TDD is an internal Looking Glass Studios document by Marc LeBlanc. This post summarizes its architecture and describes CatGame's independent implementation. No proprietary code or assets are reproduced.
  • Dark Engine vs Dark Engine (game): "Dark Engine" refers to Looking Glass Studios' game engine (Thief: The Dark Project, Thief II, System Shock 2). It is unrelated to "dark energy" in physics or any same-named concept in other domains.
  • Implementation status: Features marked ✅ are implemented and tested. Features marked ❌ are roadmap items not yet shipped. See the parity table above for the full breakdown.
  • Act/React intensity attenuation: The TDD describes quadratic and biquadratic falloff for Radius propagation. CatGame currently uses a simple distance check without attenuation. Falloff curves are a roadmap item.
  • SpatialHashGrid: The broad-phase grid uses a default cell size of 16.0 units. This is configurable but not yet exposed in TOML.

References: Dromesday Book — Sources & Receptron Basics · Thief Editors' Guild — S&R Guide · TTLG Forums — S&R System Info

← All transmissions