Software Engineering
State Machines & Architecture
Notes on explicit state, ownership, priorities, transitions, and keeping interactive software predictable.
What a state machine gives me
A state machine makes allowed behavior explicit.
Instead of scattered booleans such as:
is_sleeping
is_dragging
is_emoting
is_walking
I can reason about named states and transitions.
Example:
IDLE
→ PICKUP
→ DRAGGED
→ PUT_DOWN
→ IDLE
Every temporary state needs an exit
A temporary state is dangerous if there is no deterministic path out of it.
For every new state, I should ask:
- how does it start?
- what can interrupt it?
- how does it finish?
- what happens on rapid user input?
- what safe state does it return to?
This is especially important for animation-heavy programs.
Priorities prevent systems from fighting
When several behaviors want control, define priority explicitly.
A useful general rule:
direct user interaction
> required transitions
> active movement
> ambient behavior
> idle
Without priority, timers and animations compete unpredictably.
One owner per responsibility
Architecture is easier to reason about when one system owns each concern.
Examples:
- state machine owns behavior state
- animation controller owns playback
- sprite loader owns asset loading
- renderer owns drawing
- configuration owns persisted values
- input layer translates events into behavior requests
Source of truth
When the same fact is represented in multiple places, they can drift.
Examples:
- frame filenames hardcoded in code and also in a manifest
- version numbers repeated in several files
- state stored in both animation and behavior systems
Prefer one canonical source and derive the rest.
Manifest-driven design
For assets, a manifest can act as the source of truth for:
- animation name
- frame order
- timing
- looping
- paths
Code then asks for an animation by name rather than composing file paths everywhere.
Interruptions are part of the design
Real users do not wait politely for animations.
They may:
- click twice
- grab during an emote
- release during pickup
- right-click immediately after dropping
A robust state machine treats those as normal inputs, not edge cases to ignore.
Architecture should preserve product identity
A technically clever change is not automatically a good change.
If a rewrite makes the program less predictable, introduces duplicate systems, or breaks the core interaction model, it is worse even if the code looks more sophisticated.
Main lesson
Good architecture is not about having more layers. It is about making ownership, state, transitions, and recovery obvious enough that future changes stay safe.
Layered state: domain, behavior, and presentation
A newer lesson is that one state machine should not be forced to represent every kind of state in the program.
A long-running feature can have its own domain state while the creature temporarily changes behavior or presentation.
Example:
FocusSession: ACTIVE
Behavior: COMPUTER
Presentation: writing loop
user drags
FocusSession: ACTIVE
Behavior: DRAGGED
Presentation: drag
drag ends
FocusSession: ACTIVE
Behavior: COMPUTER
Presentation: writing loop
The session remains authoritative while presentation is temporarily interrupted.
This makes the question more precise than “what state is the app in?”
I should ask:
- what durable/domain state exists?
- who owns behavioral state?
- who currently owns presentation?
- which layer is allowed to interrupt which?
- what should resume afterward?
The important architectural rule is not “everything must be one enum.” It is “each kind of state must have one clear owner, and transitions between layers must be explicit.”