Two-phase startup

The Orchestrator had a race condition in its startup sequence. It discovers child components by querying the NodeRegistry, but some components hadn’t registered yet when the Orchestrator ran its discovery pass. The registry was empty because child nodes initialize after their parent in the Instance tree.

This is a subtle Roblox ordering issue. Instance.ChildAdded fires when a child is parented, but a ModuleScript that creates a node doesn’t register that node until its init() runs. If the parent’s init() runs first (which it does, because the boot system processes top-down), the parent sees zero children.

The fix was two-phase startup: Phase 1, all nodes register themselves with the NodeRegistry. Phase 2, orchestrators run discovery. A barrier signal coordinates the transition — no orchestrator queries the registry until every node has registered.

This pattern will come back again on Day 22 when we hit the same issue at the region level with terrain and layout generation.

Battery component

Beam weapons need power management. The new Battery component provides:

  • Charge capacity and current level
  • Configurable drain rate while firing
  • Recharge rate with a delay after firing stops (no instant regen)
  • Signals for empty, full, and configurable threshold events

We designed Battery as a standalone component deliberately. It’s not weapon-specific — shields, abilities, sprint meters, anything that uses a “pool of energy that drains and refills” can use the same Battery. The weapon system consumes it, but doesn’t own it.

Hierarchical orchestration

The big architectural win: orchestrators can contain orchestrators. A SwivelLauncher is an Orchestrator that owns a Swivel, a Targeter, and a Launcher. A TurretArray could be an Orchestrator that owns multiple SwivelLaunchers. Each level manages its own children and exposes aggregated signals upward.

This is where the closure-based privacy from yesterday pays off. A parent orchestrator interacts with its children only through their public API. It can’t accidentally reach into a child’s internal state. The boundary is enforced, not just encouraged.

The composition pattern also solves a configuration problem. In a flat component list, configuring “turret #3’s left swivel’s rotation speed” requires some ugly path-based lookup. With hierarchical orchestration, you configure the SwivelLauncher, and it configures its own children. Each level only knows about its direct children.

Capped the day by building the shooting gallery demo back from the shelved version. Pop-up targets appear at random positions, the turret system acquires and engages them, and the Targeter’s filtering system handles “don’t re-target something that’s already been hit.” It ties the whole Targeter → Swivel → Launcher → Battery pipeline together in one visual, testable scenario.