After a few days off for New Year’s (and a documentation cleanup on Dec 31 — copyright boilerplate in every file, consolidated session logs), we jumped back into gameplay.

RunModes

The game needed more than one way to play. RunModes is a subsystem that switches between different gameplay configurations — tutorial, practice, timed challenge, etc. Each mode can adjust which assets are active, what the scoring rules are, and how the UI behaves.

The implementation is a state machine. Each mode is a named state with enter/exit hooks:

RunModes.define("tutorial", {
    onEnter = function() enableTutorialPrompts(); disableTimer() end,
    onExit = function() disableTutorialPrompts(); enableTimer() end,
})

Switching modes calls onExit for the current mode and onEnter for the new one. Assets query the current mode to decide their behavior. The Orchestrator skips the countdown timer in tutorial mode. The Dispenser shows hint text.

This also introduced a Tutorial subsystem for guided first-play experiences. New players get step-by-step prompts instead of being dropped into a live round. The tutorial is itself a RunMode — it’s not a separate code path, just a mode that happens to show extra UI.

The HUD visibility bug

Our fancy GUI layout system from Day 6 bit us. The layout manager reparents UI frames to organize them into layers — moving a frame from one ScreenGui to another to control z-ordering. That reparenting was breaking visibility. Frames that should be visible were hidden because Roblox resets certain properties when an Instance changes parents.

Specifically, ScreenGui.DisplayOrder determines layering, so our layout system moves frames between ScreenGuis with different display orders. But reparenting a Frame triggers Roblox’s internal layout recalculation, and frames that had been manually set to Visible = true were getting reset by UIListLayout reflow.

The fix was preserving and restoring visibility state across reparenting operations. But it’s a good reminder: the more your framework does automatically, the more edge cases it creates. Roblox’s Instance tree has a lot of implicit behavior tied to parent-child relationships.

Other work

  • Standby mode — Dispensers and TimedEvaluators now hide themselves when they’re not in the active play area, instead of sitting there looking interactive but doing nothing
  • Camper model — Added a new NPC model for the campfire scene
  • Added a proper .gitignore (yes, we were a week in before adding one — every Roblox project’s dirty secret)