Act I · Taking up the trade
From the ledger’s first page
1
The ledger opens
/login/ — the wanted-poster auth screen
- On the screen
- The front door is a handbill tacked to lamplit planks: name, word, and a
brass-ringed ledger line. Typed values render in a struck-into-the-book
monospace.
- In the picture
- Schultz travels on paperwork — bills of sale, warrants, handbills. Nothing
in his world happens until the right paper says so, and neither does anything
here.
- Under the hood
- Django’s own
contrib.auth views and AuthenticationForm,
restyled but never rewritten — field names, validators, and error slots are the
framework’s. A wrong password answers in character (“That name and word
don’t match our ledger.”) with Django’s literal message beneath in
fine print, because an inactive-account refusal must never be dressed up as a
typo.
2
A name goes in the book
/signup/ — registration
- On the screen
- Signing up mints the character: $75 in the purse, rank Freedman, standing on
Daughtrey’s spawn tile.
- In the picture
- The film opens with a man being freed and handed a trade. That is literally this
form.
- Under the hood
- A
CreateView whose form_valid runs in
transaction.atomic: the User and the Hunter are born together or not at
all. Placement coordinates come off the seeded Location row — moving the spawn
is a map edit, not a code change.
3
Daughtrey, Texas
/game/ — first spawn, canvas world, server-rendered HUD
- On the screen
- A hand-pixeled town — mud ruts in the road, clapboard walls, doors glowing
lamplight. Arrow keys walk tile to tile; the HUD above is not part of the canvas at
all.
- In the picture
- Daughtrey is the film’s first town — where Schultz demonstrates how the
paperwork of the trade beats the men with guns.
- Under the hood
- The whole world is rows: territories, locations, and trigger tiles are models,
seeded by data migrations and compiled from ASCII map art by
manage.py buildmaps — which flood-fills every map from its spawn
and refuses to write a world where any door is unreachable. The client gets tiles and
coordinates only; every meaning stays server-side.
4
The doctor makes his pitch
A DIALOGUE trigger tile, stepped on
- On the screen
- Stand beside the grey-bowlered figure and he speaks — the mentor pointing you
at the marshal’s office.
- In the picture
- Schultz’s recruitment speech — the bounty trade explained by the man who
lives on it.
- Under the hood
- The client posts only
(x, y). The server looks that coordinate up
in TriggerTile against the location it believes you occupy, and
answers with explicit keys — a payload spread was rejected in review because a
key named action could impersonate a travel instruction.
Act II · The trade
Paper, powder, and the price of a head
5
The wanted board
/board/ — swapped into the panel by the marshal’s-office door
- On the screen
- Texas paper only — the wall in front of you, richest reward first, each poster
carrying its sketch and its licence: DEAD OR ALIVE or ALIVE ONLY, printed in words on
the paper itself.
- In the picture
- The Smitty Bacall handbill, read aloud in the film with its per-head arithmetic, is
this screen’s blueprint — down to the gang roster, which the game corrected
from the actual script (Gerald Nash rides with Michaels and Koons).
- Under the hood
Bounty.objects.open() is a custom QuerySet where expiry is a WHERE
clause: a lapsed handbill vanishes with nobody writing a row. The poster is a template
inclusion tag — the board and the claim endpoint render the same partial by
calling the same function, so they cannot drift. Query cost is pinned by a test that
renders one poster and eight for the same budget.
6
Taking the paper down
The claim, the instant after the click
- On the screen
- One click and the head is yours — the poster restamps itself YOURS, and every
other poster’s button withdraws: one hunt at a time.
- In the picture
- A handbill claimed is a contract. Two hunters cannot collect on one head, which in
the film is settled with pistols and here is settled by the database.
- Under the hood
- The project’s crown jewel:
claim() locks the row with
select_for_update, re-checks under the lock, and is backstopped by partial
unique constraints in Postgres. The test spins two real threads at one bounty and
proves exactly one wins — and a mutation check proved that removing the lock
makes it fail 15 times out of 15.
7
The general store
/store/ — the shelf, with gates showing
- On the screen
- Five items, honest labels: what you cannot afford says “Beyond your
purse”, and the Sharps rifle sits greyed until word of you travels —
reputation-gated, visible but unbuyable.
- In the picture
- Outfitting for the winter hunts — the montage where the trade starts paying
for its own tools.
- Under the hood
- The form’s queryset is the gate — an item you may not buy is not
in the form at all. The friendly refusal lives in
clean_item; the
guarantee lives in a conditional UPDATE … WHERE cash >= price
whose row count decides. Thirteen mutations were run against this form’s tests;
all thirteen were caught.
8
$75 becomes $25
The purchase response — shelf restocked, purse redrawn
- On the screen
- The Colt joins your kit, its price struck through, and the purse in the top bar
changes without the page moving.
- In the picture
- Money on the counter. The film counts every dollar out loud; so does the HUD.
- Under the hood
- An htmx out-of-band swap re-renders the HUD partial inside the purchase response
— after
refresh_from_db(), because middleware snapshots the hunter
before the view runs and an unrefreshed include would show pre-purchase money. A review
caught that exact trap before any panel shipped it.
9
The saloon knows everything
/saloon/ — rumors, standings, and your own ledger
- On the screen
- The barkeep’s talk is local and earned — some lines only surface once your
reputation warrants them. On the wall: THE FASTEST GUNS, and your own career
figures.
- In the picture
- Saloons are where the film moves its information — who is wanted, who is
dangerous, what the winter pays ($1,500–$3,000 a gang, per the script, and per the
barkeep here).
- Under the hood
- The leaderboard is one SQL statement: a filtered aggregate ranked by a
RANK() window function — chosen over DENSE_RANK()
because a saloon counts heads, so two hunters tied at $300 are both 1st and the next man
is honestly 3rd. Career stats are a single aggregate() over the fight
ledger.
Act III · The hunt
Out where the paper points
10
The Texas trail
Through the town gate — the wilderness between towns
- On the screen
- The road west: churned mud, animated water, ambush ground that looks like ordinary
trail — because it is. The trail warns you quietly, which is to say not at
all.
- In the picture
- The riding. Half the film is two men crossing country toward a name on a piece of
paper.
- Under the hood
- Doors are database rows whose payloads the browser never sees; walking is free and
client-side, but travel happens only when the server answers a trigger POST by moving
the row. A review found the one crack — an in-flight position save racing a door
— and the fix now provably drains every save before any trigger fires.
11
The standoff
/hunt/showdown/ — the ENCOUNTER tile, with your paper matching this territory
- On the screen
- He speaks first — the same man says the same line every time you meet him
— then a tension delay, then DRAW. Click fast and the roll gets up to +3.
- In the picture
- Every standoff in the film is a beat of stillness, then speed. The taunts are the
script’s own villain lines, cleaned to the letter of the reference’s content
rules.
- Under the hood
- The stopwatch lives in the session: both ends of the interval are server-observed, and
the number the browser reports is read by nothing — a test time-travels ten seconds
and posts
elapsed_ms=0 to prove the client’s own clock buys zero.
Dice, gear, reputation, and reaction are snapshotted onto the fight row so every verdict
re-checks from its own arithmetic, forever.
12
“$200, paid on proof”
The win — a 16 against a 12, purse and repute move together
- On the screen
- HE IS DOWN. The reward lands, reputation follows, and the HUD redraws out of band
— the one panel that moves money is the one panel that refreshes the bar.
- In the picture
- Schultz’s Daughtrey line — “In other words, marshal, you owe me
two hundred dollars” — is the entire economy of this game in one
sentence.
- Under the hood
- Collection is one transaction: a conditional UPDATE whose row count is the permission
slip to pay, cash moved by
F() expressions, and the project’s single
custom signal — bounty_collected — carrying reputation across
the app boundary. Sent inside the transaction on purpose: fame is part of the
payout, so a failing receiver rolls the money back too.
13
Catching lead
The other verdict — and what re-entering while wounded looks like
- On the screen
- Lose the roll and you caught lead: two minutes lying low, with a countdown that is a
number, not a promise.
- In the picture
- People get shot in this story. The ones who live wait it out.
- Under the hood
hurt_until is never cleared — the wound heals by the clock passing
it, the same lazy-time trick as bounty expiry: no job, no worker, just a comparison. The
gate is enforced in the engine under the claim lock (raising a distinct
HunterHurt), because a review proved two simultaneous DRAWs could otherwise
both roll off one arming.
14
The Marshal’s Office is the Django admin
/marshal/ — a second, playable AdminSite; /admin/ stays stock for ops
- On the screen
- Staff play the marshal: the bounty ledger with filters, and actions that post outlaws
to the board, double rewards on live paper, and re-hang lapsed handbills.
- In the picture
- The office Schultz walks into with a corpse and walks out of with a receipt. His ledger
of handbills is, structurally, an admin changelist.
- Under the hood
- A second
AdminSite instance, skinned through a one-line template fork
proven safe under the production cached loader — the stock /admin/
renders byte-identical with the fork in place. The actions obey the same database
constraints as the game: posting a man already wanted is refused politely, per row, by
the constraint’s own name.
Act IV · Candyland
The house at the end of the paper trail
15
Turned away at the gate
/finale/gate/ — reputation 400 and $500, or no entry
- On the screen
- Ride the whole map — Daughtrey, the trail, El Paso, Greenville — and the
gate still reads your ledger back to you: not enough name, not enough money, come back
when you are somebody.
- In the picture
- Candyland cannot be raided; it has to be bought into, with a cover story
expensive enough to be believed.
- Under the hood
- A view gate on real columns — and one more condition a plan can’t foresee:
you must not be mid-hunt, because the finale’s own paper rides the same
one-active-claim constraint as everything else.
16
Stephen’s questions
Stage one — the cover story, $100 per slip
- On the screen
- Three questions. A wrong answer costs $100 of cover story and starts you over; go broke
and the gate closes behind you.
- In the picture
- Stephen is the film’s real security system — the one nobody budgeted for.
His suspicion scene is this stage.
- Under the hood
- Every stage transition is a guarded UPDATE (
filter(finale_stage=N)), so a
re-POST or a second tab cannot skip a beat; the toll is the same race-safe
conditional-UPDATE shape as the store, so it cannot overdraw.
17
The parlor, then the house
Stages two and three — $0 handbills; this was never about the money
- On the screen
- Two stands, worth nothing on paper — Butch Pooch in the parlor, then Stephen in
the house, greeting you with his own gloat turned back on him.
- In the picture
- “You had my curiosity. Now you got my attention.” opens the parlor;
“that’s when you knew your goose was cooked” opens the last
door.
- Under the hood
- The finale reuses the whole encounter engine unchanged, hanging its paper OPEN-and-claimed
inside one transaction so no other query ever sees it on a board. The single-occupancy
parlor is not code being careful — it is
one_active_posting_per_outlaw
doing its job: a second hunter at this stage is told the parlor is occupied, by the
constraint, translated by name.
18
Free at last
The epilogue — career ledger closed, standings crowned
- On the screen
- The closing page of the ledger: eight hunts, the money, the fame — and the
standings board with your name where it now belongs.
- In the picture
- The mansion, the ending, the horse taking its bow.
- Under the hood
- Freedom and its +300 fame land in a single UPDATE — a review caught that two
statements left a crash window where a hunter could be free but unfamous, unrecoverably.
The career figures are one
aggregate(); the crown is the same window function
that ranks the saloon wall.
The reckoning
How the picture maps to the framework
The README carries the full thirteen-row table; this is the short ledger.
| The picture | The game | The framework |
| Schultz’s ledger of handbills | The Marshal’s Office | A second, reskinned AdminSite |
| Claiming a head first | Bounty.claim() | select_for_update + partial unique constraints |
| Telegrams between towns | manage.py telegraph | One idempotent management command — no Celery |
| Paper going stale | Bounty expiry, wound recovery | Time as a WHERE clause — lazy reads, one clock |
| Counting the reward out loud | Every dollar that moves | F() expressions and conditional UPDATEs |
| Word travels | Reputation on collection | The one custom signal, sent inside the transaction |
| The handbill itself | {% wanted_poster %} | An inclusion tag both board and claim render through |
| Saloon talk | Earned rumors | Reputation-gated querysets |
| The fastest gun | THE FASTEST GUNS board | RANK() window function, one statement |
| Candyland’s one parlor | Single-occupancy finale stands | one_active_posting_per_outlaw, translated by name |
The proof
This walkthrough was also the test
The demo run doubled as a verification pass: every beat above was played live, with the
browser console and the server log watched at each step.
- Played clean
- Zero 500s and zero tracebacks across the full run; the only console entry all session
was a missing favicon. All eight fight rows re-check from their own snapshots:
roll + sum(snapshot) ≥ target equals the recorded verdict, every
time.
- Suite behind it
- 370 tests: the two-thread claim race, a 13-mutation store kill-matrix, an 840-action
seeded economy walk where the books must balance after every step, query-count budgets,
and a Playwright smoke that signs up and claims through the real browser.
One bug found — and fixed — during this demo
The playthrough surfaced a stranded handbill: deleting a hunter cascades their claim away
but left the bounty CLAIMED forever — and the very constraint that keeps the boards
honest then barred that outlaw from ever being posted again. Big John Brittle was sitting in
exactly that state.
The fix went to the game’s reaper: the telegraph now re-hangs claimed paper that has
no living claim, before its expiry pass, so a lapsed orphan retires in the same run. The
first attempt used .exclude(claims__resolved_at=None) — which a failing
test caught compiling to a LEFT JOIN that excluded the zero-claim rows it existed to
catch. The committed version names real claims by primary key. Running it against the live
database re-hung Big John on the spot:
telegraph: 1 re-hung, 0 expired, 0 posted.
Take up the trade
Everything above is playable now — the same $75, the same first town, the same
paper on the wall. Give a name and a word and the ledger opens.
Take up the trade
Already in the book? Open the ledger