← All projects

Permit Office

Public beta

You're the permit clerk, not the mayor: shape a city through paperwork, inspections, and consequences that ripple outward.

GameArcGISArcPyPython

At a glance

  • Outcome: A playable 12-week city-management game whose engine is ArcGIS Pro itself. Feature classes hold the state, map selections are the input, and geoprocessing rules carry a run from seed to final audit.
  • Status: Public beta, v0.95, built across 19 working days in May and June 2026. District repaint is verified live in ArcGIS Pro; cold-start resume, the feature rings, legacy saves and final balance aren’t.
  • Role: Solo. Design, rules engine, ArcGIS adapter, tests and docs.
  • Stack & libraries: Python with arcpy and Tkinter, ArcGIS Pro 3.3+, and a file geodatabase.
  • Source: Public on GitHub under MIT, with architecture docs, ADRs and a changelog.
  • Validation: 314 tests over the rules and the adapter shims; the recorded v0.95 run was 313 passed and 1 skipped, the skip being a file-size guardrail I waived during the redraw work. One test plays a fixed seed through all 12 weeks outside ArcGIS Pro and asserts a final pass.
  • Limitations: Needs ArcGIS Pro to play. Thirty-three live smoke checks are written and unrun, and tuning the 12 weeks toward a fair result is open design work, not polish.

Overview

You play the permit clerk, not the mayor. You shape the city through paperwork, inspections, approvals, denials and reports.

Each week gives you two action points against a four-case docket, and inspecting a file costs the same point as issuing the permit. So you can’t read everything you sign. Denying an ordinary permit is free; deferring a mandatory follow-up isn’t. Whatever you leave alone at the end of the week resolves without you. It comes back next week, or its window closes for good, or it quietly feeds pressure into the districts it named, or that pressure turns into an enforcement case or an incident.

The scarce resource is attention, and not deciding is a decision the city acts on.

Approvals spawn real features on the map, from businesses and housing to transit and hazards, and their effects ripple through a district and into its neighbors. After twelve weeks a final audit grades you. Every game is generated from a seed, so a new seed means a new city and a new docket, and an old seed replays the same one.

The point wasn’t a novelty map demo. It was to find out whether GIS data structures could carry a real game loop: state on the map, input through selections, and rules that resolve spatial consequences.

One noun carries the whole game

Everything that crosses the desk is a docket item. A permit application, a civic incident, an enforcement order, a maintenance follow-up and a step in a multi-week project are all the same record, stamped from one of two dozen hand-written templates that carry the effects, costs, risk, stakeholders and what happens if you ignore it.

Every action resolves through one function and comes back as one result: which districts changed, by how much, which features to update, which stakeholders moved. That result is the seam between the two halves of the program. The rules never mention ArcGIS, and the adapter never needs the rules. It reads the result to work out which map layers are now stale and repaints only those.

Adding a new kind of civic event means adding a template, not a code path.

How it’s built

The geodatabase is both the city and the save file. Districts, features, docket rows, projects and the action log all live there, and the dashboard keeps only session bookkeeping and rebuilds the rest from those rows on every reload. There’s no second save file that can drift away from the map, and an interrupted session resumes from the same rows.

The rules layer never imports ArcPy. Districts, dockets, decisions, turn advancement and audit scoring are plain Python, and every arcpy and Tkinter call lives in the adapter. Balance and rules work happen without an ArcGIS license or a running session, which is the whole answer to developing against a heavyweight licensed host.

ArcPy does the spatial work: selecting target districts, generating proposed geometry, computing spillover, activating approved features and writing results back. What those results are is decided by the rules layer.

The redraw problem, and getting it wrong first

ArcGIS will redraw a layer from its cached renderer without re-reading the data behind it, and districts are colored by attributes that change every decision.

So my first optimization measured as safe, shipped, and left the board frozen on the snapshot from the start of the game. The commit that skipped the redraw is reverted in the history, and the decision record that promoted it is annotated as having mis-measured the problem, against my own earlier conclusion.

Removing and re-adding the district layers was correct but cost four or five seconds every time. The current path keeps three display slots and rebuilds a hidden one from the geodatabase, symbolizes it, and only then swaps it into view, leaving the previous one standing if the rebuild fails.

Faster variants got benchmarked and rejected. One ran in about a second and painted the whole board red and gray after the game closed. Visual correctness is the gate, not the benchmark, and 42 of the project’s 155 commits are redraw and performance work, which is a fair measure of how much of this project that problem actually was.

This started while I was reusing simulation logic from Evocatio, and the docket-and-rulings control scheme I worked out here is what later gave that project its interface.