Abstract
Problem: RPGs that rely heavily on scripts to manage game state produce fragile, bug-prone systems β dead NPCs that keep talking, guards that detect invisible players, quests that break in unexpected ways.
Approach: Tim Cain argues for encoding game rules as general-purpose code that flows through centralized choke points, reserving scripts only for rare, surgical exceptions that the rules genuinely cannot cover.
Findings: This approach naturally handles edge cases (stealth, invisibility, unconscious NPCs, alternate entry paths), makes changes propagate globally from a single edit, and produces emergent quest solutions β as demonstrated by Fallout's Rescue Tandi quest and Arcanum's quest state system.
Key insight: If you code your mechanics as general rules, most edge cases resolve themselves for free. Scripts should be the exception, not the architecture.
The Core Principle
The title says it all: code the rules, script the exceptions. Game mechanics β stealth detection, lock picking, item pickup, kill-on-sight flags β should be implemented as general-purpose code that runs through centralized systems. Scripts should only be used surgically for the rare situation that general rules genuinely cannot cover.
Tim frames this as the single most important architectural decision in RPG development. Games that get this wrong produce the bugs players know and mock: dead guards who rotate to say "Stop, thief!", NPCs who keep talking after being killed, quests that break when players find creative solutions.
The Burglary Example
Tim walks through a simple RPG scenario β the player is given a quest to break into a house and steal an item β to illustrate what should and shouldn't be scripted.
What to code (general rules)
- Stealth system β whether NPCs detect the player, based on visibility, distance, facing direction, light levels
- Lock picking β a general mechanic for all locked objects
- Kill-on-sight flags on guards β if they see the player, they attack (with a bark like "You're not supposed to be here")
- Quest item flags on the target item, ideally with a pointer back to the quest it belongs to
What NOT to script
- Individual scripts on every guard, window, door, or lit area
- Scripts on the item itself checking "Am I being picked up by the player?"
- Scripts on the chest checking "Has the player accessed me?"
- Scripts on the room that activate every frame tick watching for the player to pick up the item ("Did he pick it up yet? Did he pick it up yet?" β Tim calls this "incredibly wasteful")
The Choke Point Pattern
Tim advocates for designing code with choke points β single locations through which all relevant actions must flow. For example, there should be one place in the code where item pickup happens. This is where you check: Can the item be picked up? Is inventory full? Is it too heavy? And crucially: Is it a quest item?
When the player picks up a quest item through any means, the quest automatically advances from "find the item" to "return to quest giver." You can add safeguards β preventing accidental drops or sales β and when design requirements change during development (QA says "stop asking, just disallow it"), you change it in one spot.
With scripts, every individual script would need to check for these conditions manually. Some will do it differently. Some will do it wrong.
Rules for When You Must Script
Tim acknowledges that even the best rule systems occasionally need scripts β boss creatures, special artifact-level items, truly unique situations. His rules for scripts:
Put the script as close as possible to the action it tests
Don't put a "player broke in" script on the door β what if the player climbs through a window, or comes through a roof hatch? Tim shares a story from The Outer Worlds: an area had an electric fence the player couldn't jump over, but later a level designer added some crates nearby. Speedrunners discovered they could jump on the crates and over the fence. A script on the gate would have completely missed this valid entry method.
Don't use area triggers as proxies for NPC perception
If you want a script for "NPC sees the player," put it on the NPC's perception, not on an area trigger. The guard might be on a walk path and nowhere near the trigger zone. The guard might be facing away. The player might be invisible or stealthed. The NPC might be unconscious or dead. An area trigger ignores all of this.
Have script conditions call code functions
If a script tests "can this NPC see the player?", that check should call into the code's perception system β not reimaglement distance and angle calculations in the script itself. Two reasons:
- Performance β code is faster than interpreted scripts
- Consistency β when you change what "seeing the player" means (maybe invisibility becomes 100% effective, maybe stealth rules change), you change it in one place and every script that calls
canSeePlayergets the update automatically
Fallout and Arcanum: Proof It Works
Fallout's emergent quest solutions
Tim credits Fallout's success partly to this philosophy. The Rescue Tandi quest had numerous solutions β most of which emerged naturally from coded mechanics rather than being individually scripted by designers. The quest designer didn't have to think about putting those solutions in; the systems produced them.
Arcanum's code-maintained quest states
Arcanum maintained quest and story states (mentioned, achieved, completed, botched) in code. Scripts could adjust these states, but all access and conditional testing went through code. This prevented bizarre edge cases:
- Players turning down already-completed quests β instead, dialogue could check "I see you already have the item I was going to ask for"
- Completed quests being retroactively botched β if you rescued the princess and returned her to the castle, and she later died there, your already-completed quest shouldn't be botched. "The princess is no longer your concern." Code prevented this; scripts would have been a nightmare.
Summary
The rule is simple and Tim states it plainly: code the rules, script the exceptions. Build your game mechanics as general, centralized code systems. Let those systems handle the vast majority of game states and edge cases automatically. Reserve scripts for the truly exceptional situations that the rules can't cover β and when you do script, keep the script as close to the tested action as possible, and have it call back into code for its conditions. The result is a more robust, more emergent, and far less buggy RPG.
References
- Tim Cain. YouTube video. https://www.youtube.com/watch?v=ljnaL7N5qtw