Punball · systems design
A stick figure on graph paper, drawn like a bored teenager did it in the back of a maths exercise book. Underneath: an energy scheduler, a Dijkstra flow field, and a table of trade-offs that will not let you have anything for free.
§ 1
The art is deliberately naive. Biro on graph paper, circles for heads, four lines for limbs. It should look like something you could draw yourself in a lesson you were not paying attention in.
That is the disguise. The design goal is to hide a nuanced, difficult, strategic game under the guise of a stick figure dungeon — so a player who dismisses it on sight and then dies on floor three has to go back and work out why.
§ 2
The first version moved everything in lockstep: you stepped, then every monster stepped. That is the shallowest possible model of time, and it makes speed meaningless — a fast monster and a slow one arrive together.
So time is now an energy scheduler. Every actor banks energy at its own rate. When an actor's bank reaches the cost of an action, it acts and spends it.
The scheduler
energy += speed · act when energy ≥ 100 · then energy -= 100
The player's speed is 10, so a plain action is ten micro-ticks of the world clock. A cave rat at speed 15 acts three times while you act twice. A shambling corpse at speed 5 acts once while you act twice.
Monster speeds in the table run from 5 to 16. That spread is the entire reason positioning matters: you cannot outrun everything, so you have to choose what to outrun.
Standing still is a decision, not a pause. After a grace period the dungeon takes a step without you — monsters close, and whatever was hunting you gets nearer.
| Constant | Value | What it does |
|---|---|---|
| Action cost | 100 | The price of one action, for everybody |
| Player speed | 10 | Ten micro-ticks per action; the yardstick |
| Idle grace | 650 ms | How long you may think before the floor moves |
| Idle step | 850 ms | How fast it keeps moving once it starts |
| Sim radius | 20 | Cells around you that actually think, for cost |
§ 3
Six abilities is the tabletop convention, and two of them are always dead weight in a game this size. Four, each of which changes something you can feel within a floor:
Force and carry
Speed and precision
Endurance
Perception and lore
The modifier
mod(score) = floor((score − 10) / 2)
Straight from the d20 model. Ten is average and every two points is one step. Keeping the familiar curve means anyone who has played a tabletop RPG already knows how the numbers behave.
| Class | Might | Finesse | Grit | Sight | HP | Opening kit |
|---|---|---|---|---|---|---|
| Fighter | 15 | 11 | 14 | 9 | 24 | Shortsword, leather |
| Archer | 10 | 14 | 11 | 14 | 22 | Shortbow, cloth |
| Wizard | 8 | 12 | 11 | 18 | 21 | Cracked staff, cloth |
The wizard's Might of 8 is a negative modifier. That is intentional: a wizard who swings a staff is doing the wrong thing, and the sheet says so before the player finds out the hard way.
§ 4
Finesse buys speed. Speed buys actions. Actions buy everything else. Left unchecked, that is a runaway: stack Finesse, act twice per monster turn, win.
Half rate, and only half
speed = 10 + mod(finesse) × 0.5
A Finesse modifier of +4 is worth +2 speed, not +4. So the archer acts about 20% more often than the fighter rather than 40% more often.
This single coefficient is what keeps three classes inside the same game. Set it to 1.0 and Finesse becomes the only ability worth having.
§ 5
39 items, and not one of them is a straight upgrade. Plate armour is the clearest case: it is the best protection in the game and it makes you slower, which in a game where time is currency means it can get you killed.
| Item | Gives | Takes |
|---|---|---|
| Plate armour | Heavy protection | Speed, and a Might floor to wear it at all |
| Tower shield | Best block in the game | Your off hand — no bow, no two-hander |
| Greatsword | Biggest melee damage | Speed, and the same off hand |
| Lantern | Sight far past your natural range | Announces you to everything with ears |
| Cursed ring | A genuinely strong bonus | Cannot be removed once worn |
| Heavy pack | Carry more loot | Slower, and worse at dodging |
The build gate is checkNoFreeLunch(). It reads the gear table, finds any item whose modifiers are all non-negative, and fails. Adding a tempting item to this game means first deciding what it costs.
§ 6
Fog of war is absolute. You see rooms you have opened and corridors you have walked. Not the monsters in the next room, not its doors, not its shape — nothing outside what you have actually revealed.
That is what makes Sight an ability rather than a display setting, and it is what makes a door a decision. Opening one is committing to whatever is behind it.
A trap you can see is a puzzle. A trap you cannot see is a tax. The interesting version is a trap that some characters see and others do not.
Detection
visible if passiveSight(abilities) ≥ trap.detectDc and within sightRange
Six trap kinds, each with its own difficulty. The wizard at Sight 18 walks the floor reading it. The fighter at Sight 9 finds traps with their feet.
This is not a handicap, it is the class identity — and § 8 shows it turning up in the measurements.
§ 7
On a real hand-drawn dungeon map a door is a gap in a wall. It sits on the line between two squares, not inside a square of its own.
So doors are edges. Each has a canonical key — v:x,y for a vertical edge, h:x,y for a horizontal one — and blocking movement means blocking an edge rather than occupying a cell. A shut door costs no floor space, which is why corridors can meet rooms cleanly.
| Rule | Why |
|---|---|
| A shut door stops anything that cannot work a handle | Closing a door behind you is a real tactic, not decoration |
| Ghosts pass through regardless | One monster class that ignores your best tactic keeps it from being the answer to everything |
| Monsters do not open doors | Otherwise the tactic never works and the door is a speed bump |
| Monsters take trap damage | The floor is not on anyone's side. Leading a chase over a trap you spotted is a play |
§ 8
Three classes are only three classes if they all reach roughly the same depth. Otherwise there is one real class and two traps for new players.
So the game is played headlessly, 2,000 runs per class, by a policy that is deliberately unskilled — it does not use terrain, does not shut doors, does not retreat. Whatever survives that is the baseline the design provides, not the ceiling a good player reaches.
3.47×
Before
One class reached three and a half times the depth of another. That is not a difficulty curve, it is a wrong answer and two decoys.
1.22×
After
Archer 19.2, Fighter 16.8, Wizard 15.8 average floors. Close enough that the spread is play, not sheet.
| Class | Avg depth | Damage taken from traps |
|---|---|---|
| Archer | 19.2 | 9% |
| Fighter | 16.8 | 24% |
| Wizard | 15.8 | 0% |
That third column is the design working. Nothing tells the fighter they are bad at spotting traps — Sight 9 does, and a quarter of all damage they take is the floor itself. The wizard's zero is the same fact from the other end.
§ 9
The first scoring model gave points for picking things up. That rewards walking into every room and touching everything, which is the opposite of a strategy game — it rewards thoroughness, and thoroughness is just time.
Score is now paid for actions that carried risk.
A player optimising for score and a player optimising for survival now want mostly the same things — which is the only honest arrangement, because the alternative is a leaderboard that teaches bad habits.
The other cabinets on this arcade are getting generated pixel art. This one never will, and that is a design decision rather than a backlog item.
The identity of this game is ink on a page. A rendered sprite pasted onto graph paper does not read as a nicer monster — it reads as a sticker on someone's homework. The naive drawing is load-bearing: it sets the expectation that the systems then violate.