Skip to content

[mud] Add a master event loop + state machine (foundation for a JS MUD) #4

Description

@trey-darley

Why this exists

This 404 page is becoming a small interactive world.

If we keep adding features (Chaos Mode, Core Wars, Fox/Poem, hidden triggers, trapdoors, etc.) with ad-hoc DOM toggles, we will create JS mud — in the bad way.

Instead: let’s build JS mud in the good way:
a tiny Multi-User Domain architecture (even if it’s niche for now).

This issue is the foundation: a single source of truth for state + a master event loop that renders the page.

Current bug (symptom)

Modes can conflict.

Example:

  • Start Core Wars
  • Disable GIF Chaos Mode
  • The Core Wars stop button can disappear (because the button is hidden when chaos is off)
  • Result: Core Wars keeps running but the UI can’t stop it

This is a sign we need an explicit state model.

Goal

Introduce a small, explicit state machine + master event loop that:

  • Tracks page state in one place
  • Drives all UI from state (render is the only thing that touches DOM)
  • Prevents impossible states (or self-heals them)
  • Creates a clean path toward a MUD-like command loop (next issues)

Think: “game loop for a haunted 404 page.”

Constraints

  • Static only
  • No external dependencies
  • Must remain readable for students (small functions, comments, clear names)
  • No frameworks

Proposed direction (suggested, not mandatory)

1) Central state

Create a single state object.

Example:

const state = {
  // feature toggles / modes
  chaosOn: false,
  coreWarsOn: false,
  poemVisible: false,

  // MUD scaffolding (not all used immediately)
  room: "404_VOID",
  inventory: [],
  flags: {},
  log: []
};

2) Event queue + dispatcher

All inputs become events.

Examples:

  • Button clicks
  • Hash triggers
  • Timers / animation ticks

Suggested:

const queue = [];
function dispatch(action) { queue.push(action); }

3) Update loop (single authority)

A reducer-like update(state, action) returns a new state.

function update(state, action) {
  switch (action.type) {
    case "TOGGLE_CHAOS": ...
    case "COREWARS_START": ...
    case "COREWARS_STOP": ...
    case "TOGGLE_POEM": ...
    default: return state;
  }
}

4) Render loop (only place that touches DOM)

render(state):

  • Applies body classes (chaos-mode, corewars-on)
  • Updates button text + visibility
  • Sets aria attributes
  • Ensures controls are never unreachable

5) Invariants (non-negotiable rules)

  • If state.coreWarsOn === true, the user must always have a visible UI control to stop it.
  • UI must never hide the only control needed to exit a mode.
  • DOM is derived from state (not the other way around).

Acceptance criteria

  • Current bug is fixed: you can always stop Core Wars, even if Chaos Mode is off
  • All button handlers call dispatch() and do not directly toggle DOM
  • A single render(state) function updates:
    • body classes
    • button labels and visibility
    • aria-hidden and related accessibility toggles
  • The architecture makes it easy to add a future COMMAND_INPUT action (for the MUD loop)

Notes (for students)

This is not about “overengineering.” This is about making a tiny world coherent as it grows.

If you do this right, future features become easy:

  • add a terminal input
  • parse commands
  • update state
  • re-render
  • ship weirdness safely

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions