Skip to content

Rune actors and placement

The actors designers actually drop in the level. Every one of these has a Blueprint subclass in Content/Blueprints/ for visuals; the C++ classes own the logic.

ARune

The physical floating tablet. Players carry it, drop it, place it.

Public state:

  • RuneData (URuneDataAsset*) — what this rune is.
  • bIsBeingCarried, bIsInSlot — runtime flags.
  • bIsNewlyDiscovered — first-encounter flag, drives discovery VFX.
  • RuneMesh — the visible mesh component.
  • FloatAmplitude (default 20), FloatSpeed (default 2) — the bob animation when sitting in a slot.

Public functions:

  • InitializeRune(URuneDataAsset*) — assigns data and updates the mesh.
  • AttachToPlayer(Player, CarryOffset) / DetachFromPlayer() — used by URuneCarryComponent.
  • PlayDiscoveryVFX() — fires the discovery particles, swaps to whichever VFX template is set (Cascade DiscoveryVFXTemplate or Niagara DiscoveryNiagaraVFX).

Designers usually do not place ARune directly. They place a slot with bSpawnWithInitialRune set, or rely on the memory palace / combine slot to spawn runes. The only legitimate manual placement is a loose pickup (e.g. a rune sitting on a table).

ARuneSlot

Base slot. Implements IInteractableInterface so the player's interaction system picks it up automatically.

Components:

  • SlabMesh — the visible base.
  • RuneAttachPoint (USceneComponent) — where the rune hovers.
  • DefinitionUIComponent (UWidgetComponent) — world-space widget showing the rune word/definition.
  • DebugRuneWordText (UTextRenderComponent) — visible only in debug mode (G key).
  • SlotRingEffect (UNiagaraComponent) — the ring VFX that fires when the slot participates in a valid sentence.

State:

  • CurrentRune — the rune currently in the slot.
  • bIsHovering — true when the player is aiming at the slot with a rune.
  • OriginalMaterial — captured at BeginPlay so highlight/hover can revert.

Auth fields:

  • bIsMemoryPalaceSlot + PalaceRuneData — legacy palace mode. Prefer AMemoryPalaceSlot.
  • bSpawnWithInitialRune + InitialRuneData — spawn a rune at BeginPlay.
  • RuneHeightOffset (default 50).
  • HighlightMaterial / HoverMaterial — visual feedback.
  • DefinitionWidgetClass — what to show in the widget component.
  • DefaultSlotPlacementSound — fallback when the rune's own PlaceSound is null.

Events:

  • OnSlotChanged — broadcasts whenever a rune is added or removed. ARuneCalculator binds this.

API:

  • PlaceRune(ARune*) / TakeRune() — virtual, subclasses override.
  • IsEmpty() — virtual.
  • HighlightSlot(bool) / SetHoverState(bool) — visual feedback.
  • ShowDefinitionUI() / HideDefinitionUI().
  • ActivateSlotRing() / DeactivateSlotRing().
  • SetDebugTextVisible(bool) — virtual; overridden by subclasses that want different debug text behavior.

ShouldShowDefinition(Player) is a private helper that gates the definition widget on whether the player has actually discovered the rune. Showcase mode and debug mode bypass this.

ATestSlot (subclass of ARuneSlot)

Showcase slot for demo levels. Bound to a single rune via DedicatedRune. Spawns its rune at BeginPlay; the player can borrow it but it auto-returns when interacted with elsewhere.

Adds:

  • DedicatedRune — what this slot is for.
  • SlotSpotlight — on when rune present, off when borrowed.
  • SpawnDedicatedRune(), ReturnRune(Rune), UpdateSpotlight(bool).

SetDebugTextVisible overrides to use DedicatedRune instead of CurrentRune, so the word stays visible even when borrowed.

ACombineSlot (subclass of ARuneSlot)

Stack of runes that auto-merges via UMergeRecipeDataAsset.

Adds:

  • RuneStack (TArray<ARune*>) — current stack contents.
  • RecipeData — the merge recipe data asset.
  • InitialRunes — runes pre-loaded at BeginPlay.

API:

  • PlaceRune adds to the stack instead of replacing.
  • TakeRune pops the top of the stack.
  • CheckAndMergeRecipes() — called automatically after every place; iterates RecipeData->Recipes, fires the first matching one.
  • HasRunesForRecipe(Required, OutMatching) — set-membership match, order doesn't matter.

IsEmpty overrides to check the array.

AMemoryPalaceSlot (subclass of ARuneSlot)

Permanent home for one specific rune in the memory palace.

Adds:

  • DedicatedRune — the rune that lives here.
  • DefinitionText (UTextRenderComponent) — always-visible 3D text with the definition.
  • SlotSpotlight — on/off based on rune presence.

Differs from ARuneSlot in that PlaceRune rejects manual placement (palace slots aren't generic drop targets); the player borrows and the carry component routes the rune back here on interaction with another slot.

ARuneCalculator

The puzzle station. Owns slots, watches them, validates sentences, fires effects.

Components:

  • CalculatorRoot (USceneComponent) — root.
  • RuneableRangeSphere (USphereComponent) — editor-only visualization of the detection radius. Scale this in the viewport to set the play area.

Major auth fields:

  • Layout: bUseGridLayout, NumSlots (2–3 for linear), GridWidth/GridHeight (1–10), EmptySlotPositions, SlotSpacing.
  • Slots: Slots (runtime), ManualSlots (designer-assigned), bAutoCreateSlots, SlotClass.
  • Range: bUseGlobalEffects, RingExpansionDuration, bLockPlayerDuringActivation, RingActorClass.
  • Showcase: ShowcaseMode (Disabled / PauseAtTarget / LeadingSphere), bShowcaseSingleTarget, ShowcaseBlendTime, ShowcaseHoldDuration, ShowcaseCameraPitch, ShowcaseCameraDistance, ShowcaseGroupingRadius.
  • Slot VFX: SlotActivationMode (Sequential/Simultaneous), SequentialSlotDelay.

Runtime state:

  • LastModifiedTime — used by URulePriorityManager for conflict resolution.
  • CurrentSentence (FSentence) — last valid sentence.
  • bIsActive — flag when an effect is currently applied.

The calculator binds OnSlotChanged from each of its slots and runs ValidateAndExecute on change. In grid mode, CheckHorizontalLines and CheckVerticalLines scan for valid sentences; 3-word sentences consume their slots first to prevent double-counting.

Range mode uses BeginRangeActivation to spawn an AExpandingRingActor and bind OnRingTargetReached / OnRingExpansionFinished. As the ring reaches each target, EffectExecutor::ApplyEffectToSingleTarget fires for that target. Player input can be locked for the duration via LockPlayerInput / UnlockPlayerInput.

Showcase logic precomputes waypoints (PreComputeShowcaseTargets) and feeds them to AEffectShowcaseCamera plus the camera subsystem's cinematic stack.

AExpandingRingActor

Short-lived actor spawned by the calculator. Scales a mesh outward and fires per-target events as a sphere check sweeps the radius.

Public:

  • StartExpansion(MaxRadius, Duration) — kicks off scaling.
  • AbortExpansion() — stop and destroy.
  • SetWaypoints(Distances, PauseDuration) — pause at specific radii (showcase modes use this).
  • ResumeFromWaypoint() — manual resume after a waypoint pause.

Delegates:

  • OnTargetReached(ARuneableObject*) — fired when the sphere check picks up a new target.
  • OnExpansionComplete() — full expansion done.
  • OnWaypointReached(int32 Index) — paused at a waypoint.

The mesh's bounding box is auto-measured on BeginPlay (MeshBaseRadius, MaxMeshScale) so the visual fills the detection radius regardless of mesh shape. Subclass in Blueprint and swap the mesh — no other config needed.

AlreadyAffected (a TSet) prevents re-triggering on the same target as the radius grows.

ARuneableObject

The world target. Anything a sentence can affect derives from this (or subclasses it — AClockDevice is : public ARuneableObject).

State:

  • InitialNounData — designer-set starting identity.
  • CurrentNounData — runtime; changes when IS fires.
  • NounToMeshMap — per-instance mesh override map.
  • bIsDisabled — true when a verb (e.g. Hidden) has temporarily turned the object off.
  • RuneSpeedScale (default 1.0) — Slow/Accelerate accumulate here.
  • RuneDirectionScale (default 1.0) — Reverse flips this to -1.

Components:

  • ObjectMeshUStaticMeshComponent driven by the noun's NounMesh (or a NounToMeshMap override).
  • Inherent components from CurrentNounData->InherentEffectComponents are spun up at BeginPlay and torn down on noun changes.

API:

  • SetNoun(NewNoun) — the only transformation entry point. Updates mesh, materials, physics, inherent components, and registry. Location/rotation/scale are preserved.
  • ResetToDefault() — back to InitialNounData.
  • EnableObject() — re-enable a bIsDisabled object.

Internal helpers (protected):

  • ApplyNounMesh, ApplyNounMaterials, ApplyNounPhysics (which respects bNounSimulatesPhysics), ApplyInherentComponents, RemoveInherentComponents.
  • bOriginalSimulatePhysics/bOriginalGravityEnabled/bPhysicsStateStored capture the actor's pre-noun state so a revert is clean.

The Registry's BeginPlay hook lives in ARuneableObject. EndPlay unregisters.

Source files

  • Source/Rephrased_Demo/Rune.h
  • Source/Rephrased_Demo/RuneSlot.h
  • Source/Rephrased_Demo/TestSlot.h
  • Source/Rephrased_Demo/CombineSlot.h
  • Source/Rephrased_Demo/MemoryPalaceSlot.h
  • Source/Rephrased_Demo/RuneCalculator.h
  • Source/Rephrased_Demo/ExpandingRingActor.h
  • Source/Rephrased_Demo/RuneableObject.h

Known constraints

  • The bIsMemoryPalaceSlot legacy flag still exists on ARuneSlot. New work should use AMemoryPalaceSlot. The flag is kept so older levels keep compiling — flagged for removal at Gate 4.
  • ChildActorComponent-based slot resolution is detected by name (Slot_0, Slot_1, ...). It works but is fragile. ManualSlots is the supported path.
  • The expanding ring's sphere check is per-tick during expansion — fine for puzzle scenes with bounded actor counts but not free at scale. See VFX for tuning.

Change history

  • 2026-05-08 — Gabriel Li — Added baseline change history section for weekly wiki maintenance.