Puzzle narrative devices¶
Standalone puzzle actors that hook into the rune system but carry their own state machines and scripted outcomes. The pattern: subclass ARuneableObject, expose a child ARuneSlot, watch for specific verb components, drive a beat-by-beat sequence.
Two devices ship today: the Clock and the Star Observatory. They form a paired puzzle — the clock's second hand spawning triggers the observatory's vortex.
AClockDevice (: public ARuneableObject)¶
Three-state machine driven by which verb component is currently attached to the device.
States (EClockState):
Dormant— no verb effect active.SecondHandActive—URotateComponentdetected.FullyRunning—UPassingComponentdetected.
Pivots (scene components, all visible):
SecondHandPivot(rotates fastest)MinuteHandPivotHourHandPivot
Speed knobs (designer-tunable): SecondHandSpeed (default 6), MinuteHandSpeed (default 1), HourHandSpeed (default 0.2). Degrees per second around Z.
Auth:
CoreSlot(ARuneSlot*,EditInstanceOnly) — the slot designers point the device at. Refinement reads the rune from this slot.TimeRuneData— the rune the device produces on completion.RuneClassToSpawn— class for the spawned rune (typicallyARune).RefinementDelay(default 5) — seconds betweenFullyRunningentry and the Time rune appearing.
Events (BlueprintAssignable):
OnSecondHandStarted— fired onDormant→SecondHandActive.OnClockActivated— fired onSecondHandActive→FullyRunning.OnTimeRuneReady— fired afterRefinementDelayonceFullyRunning.
Behavior summary (driven by Tick):
- Reads the active components on itself each frame.
- Detect Rotate → enter
SecondHandActive, broadcastOnSecondHandStarted. The second hand pivot starts rotating atSecondHandSpeed.PickNextStutterTimeschedules subtle stutter pauses for character;bStutterActive,StutterElapsed,StutterCooldown,StutterBaseRotationdrive the hold-and-twitch beat. - Detect Passing → promote to
FullyRunning, broadcastOnClockActivated. Minute and hour hands engage. ScheduleRefinementTimerHandlefor the refinement delay. - On timer fire:
OnRefinementCompleteconsumes the rune inCoreSlot, spawnsRuneClassToSpawninitialized withTimeRuneData, broadcastsOnTimeRuneReady.
The device is a ARuneableObject, so the rune system applies effects to it normally — Rotate and Passing get attached as component instances, the device just observes.
AStarObservatory¶
Coupled to a AClockDevice. Watches for OnSecondHandStarted and runs a vortex animation that spawns a Passing rune at the end.
Auth:
LinkedClock(AClockDevice*,EditInstanceOnly) — the clock to listen to.VortexDuration(default 10) — how long the buildup takes.PassingRuneData— the rune to spawn.RuneClassToSpawn— class for the spawn.PassingSpawnPoint(USceneComponent) — designer-placed location for the spawned rune.
State (BlueprintReadOnly):
VortexProgress— 0..1.bVortexActive,bVortexComplete.
Events (BlueprintAssignable):
OnVortexStarted,OnVortexComplete,OnPassingSpawned.
Flow:
BeginPlaybindsOnClockSecondHandStartedtoLinkedClock->OnSecondHandStarted.- When the clock's second hand activates,
OnClockSecondHandStartedflipsbVortexActive = true. TickadvancesVortexProgresstoward 1 overVortexDurationseconds.- At completion,
CompleteVortexfiresOnVortexComplete, thenSpawnPassingRuneinstantiatesRuneClassToSpawnatPassingSpawnPoint, firesOnPassingSpawned. - Designer hooks the events to VFX, audio, gating logic.
The runeable-device pattern¶
When you want a puzzle object that also takes verb effects:
- Subclass
ARuneableObject(notAActor). - Add your visual components as scene component children.
- Optionally expose an
EditInstanceOnlychildARuneSlot*if the device needs to consume a rune. - Watch for verb components on yourself in
Tick(or via the components'OnEffectAppliedevents if you control the components). - Run your state machine and broadcast events.
The Clock is the worked example. Designers configure the slot, the speeds, and the refinement target rune; the C++ owns the state transitions.
Source files¶
Source/Rephrased_Demo/ClockDevice.hSource/Rephrased_Demo/StarObservatory.hSource/Rephrased_Demo/RuneableObject.h(parent class)
Known constraints¶
- Clock ↔ observatory init order: The observatory binds to
LinkedClock->OnSecondHandStartedinBeginPlay. AssignLinkedClockon the level instance in the Details panel — not at runtime — so the bind completes before the clock can emitOnSecondHandStartedduring the same session. - The clock polls components in
Tickrather than subscribing to attach/detach events. Cheap for one device but does not scale to many. If we add more devices, factor a "watch for component class" helper intoARuneableObject. RefinementDelayis per-instance but the stutter cadence is hardcoded inPickNextStutterTime. Designers wanting to tune the pacing for new devices will need a knob.- Observatory→Clock coupling is one-way via delegate. The clock does not know the observatory exists. New consumers of
OnSecondHandStartedplug in the same way without changes.
Change history¶
- 2026-05-23 — Gabriel Li — Clock ↔ observatory BeginPlay / LinkedClock wiring note.