Case study · 2026
Verticopolis
A browser-native homage to SimTower (1994), with a portable simulation engine.
- Version
- 2.33.0
- Rule-sets
- Classic (1994 rules) or Modern, fixed per tower
- Tower bounds
- 100 floors up, 10 basement levels
- Engine
- TypeScript by default; Rust/WebAssembly port available as an opt-in
- Runs on
- Any modern browser; installable as a PWA, saves before updating

What it is
Build a vertical city floor by floor. Wire it with elevators, attract offices, condos and hotel guests, set the rents, and climb the star ratings toward TOWER. Classic mode follows the 1994 rules; Modern adds rentals, amenities and household variants. Every sprite is drawn in code.
Classic’s departures from the original are documented, and Modern’s variants include two-to-five-person condos. Built with TypeScript, Excalibur.js, lit-html, Tone.js, Rust, WebAssembly, Vite PWA.
Gameplay and the tower simulation
Tenants are routed one by one, with an aggregate congestion model underneath, and ride elevators dispatched with a SCAN algorithm, so a badly placed shaft shows up as a lobby full of waiting people. Stars unlock at 300, 1,000, 5,000 and 10,000 people; TOWER also needs a Wedding Hall, a metro and a passed VIP inspection.

Evolution
The first commit, in February 2026, held three SimTower prototypes built from the same basic prompt by agents from ChatGPT, Copilot and Gemini. None of them was particularly good, and the project was rebuilt from scratch in June.
![A dark, mostly empty grid with a build menu of room types: an early prototype from February 2026.]()
Rebuilt from sourceFeb 2026 · One of three first prototypes, the ChatGPT agent’s attempt ![A pixel-art tower under the Tower Tycoon name, with a tools panel.]()
Actual screenshotJun 29, 2026 · Rebuilt from scratch as Tower TycoonView this version ![A smaller pixel-art tower on the first Excalibur.js build.]()
Actual screenshotJun 30, 2026 · First Excalibur.js buildView this version ![A 40-floor tower in the Verticopolis interface.]()
Actual screenshotJul 29, 2026 · 2.0: renamed Verticopolis, Modern rule-setView this version
Status timeline
- First prototypesShipped
The Copilot attempt was later converted to TypeScript.
- Rebuilt from scratchShipped
A clean-room rebuild on Excalibur.js, renamed Verticopolis on July 1.
- 2.0 and real 1994 savesShipped
Import and export of the original game’s .TDT save files; 2.0.0 shipped on July 29.
- Fixed-step simulation and conformanceShipped
A deterministic fixed-step simulation, a conformance suite for both engines, and the Rust port held to it.
- WebAssembly opt-in and benchmarkShipped
The Rust engine runs in the browser behind an opt-in, with the TypeScript engine as the default and fallback, plus a paired TypeScript vs WebAssembly benchmark.
- Rust-only 3.0 cutoverIn progress
The cutover plan and engine catalog are merged. The cutover gate, the read model and the command layer are still open.
Architecture
The game is a TypeScript host on Excalibur.js. The simulation sits behind an engine boundary, so it does not depend on any one frontend: the renderer, interface, audio and saves talk to it through commands and read its state back.
Diagram
From TypeScript to Rust
The simulation has been ported to Rust and compiled to WebAssembly. Today it is an opt-in: the TypeScript engine is the default, and the Rust engine runs when a player adds ?engine=wasm to the address. The plan is a Rust-only 3.0, gated on a written cutover plan; until that gate passes, both engines ship and must agree.
Determinism and conformance testing
Two engines can only coexist if they produce the same tower. The simulation runs on a fixed time step, so it gives the same result at any frame rate. A conformance suite pins state, crowd and event hashes for a library of scenarios and runs them against both engines, and a nightly fuzzer feeds both the same random command streams and compares their digests. The port includes a Mulberry32 random generator rebuilt on wrapping 32-bit integers, so both engines draw the same numbers.
Engineering challenges
Saves a 1994 game can open
Verticopolis imports and exports the original game’s .TDT save files, and round-trips them against the real game: population, elevators, lobbies and views have to survive the trip in both directions.
Keeping two engines honest
The conformance suite earned its keep early: it caught three places where the Rust port had drifted from the TypeScript engine.
The cost of hosting an engine
Once the game hosts it, the Rust engine is slower than the TypeScript one on every test tower, including the two where it wins on its own. Performance, below, has the numbers and where the time goes.
How it was built
AI coding agents wrote most of the implementation. I made the product and architecture calls and set the rules and review bar they work under.
Tools: Claude Code, the GitHub Copilot coding agent and Codex reviews, working inside the BMAD method, an open-source workflow for planning and reviewing work with AI agents.
Workflow, with a public example for each step
- DecideMe
Product and design calls, written down with the options that were rejected.
Example: A decision log - SpecifyMe + agent
Specs, requirements and stories written before code.
Example: A spec-first change - PlanMe + agent
Larger changes get a written plan with stages and exit criteria.
Example: The 3.0 cutover plan - DelegateMe
Bounded tasks go to Claude Code or GitHub Copilot sessions, each claiming an issue first.
Example: The claim-an-issue rule - ImplementAgent
Agents write the code for each task, following the repository’s written rules.
Example: Commit history - VerifyCI
Typecheck, lint, tests with coverage floors, conformance and screenshot drift.
Example: Conformance caught three Rust drifts - ReviewAgents + me
Adversarial review skills plus Copilot and Codex reviews, repeated until a pass finds nothing new.
Example: A change reviewed in ten rounds - TriageMe
Each finding is fixed now or deferred to the backlog with a tracking issue.
Example: A deferred finding - Approve and mergeMe
Pull requests merge under my account. Agents may arm auto-merge only after review sign-off and green CI.
Example: The merge rules - MeasureAgent + me
Claims about speed are measured with reproducible, paired benchmarks before they are made.
Example: The TypeScript vs WebAssembly benchmark - Refine the rulesMe
When something goes wrong, it becomes a written rule for the next session.
Example: A rule added after an incident
Performance
Snapshot. Measured Oct 14, 2026 on app 2.30.0 (commit 01c6153): headless Chromium 141 in one 4-CPU container, medians of 10 paired runs over 24 in-game hours (720 steps). This is a test machine; players’ hardware will differ. The large-tower bare result is inconclusive; paired 95% intervals are in the source document. Live figures: docs/performance/ts-vs-wasm.md.
The Rust engine does not make the shipped game faster today. On its own, the bare engine ran the small and medium towers in about half the TypeScript time. Hosted inside the game the way it is wired today, the same work took about 3 to 5.5 times as long at normal CPU speed, and up to 8 times under a 4x CPU throttle.
The benchmark write-up puts roughly a fifth of that time in the engine’s step, a fifth in reading its exports, and at most three fifths in host JavaScript.
Measured
| Tower | Bare engine | As hosted today |
|---|---|---|
| Small (729 people) | 0.55× | 4.02× |
| Medium (3,240 people) | 0.54× | 3.23× |
| Large (17,322 people) | 1.08× | 5.45× |
On the large tower at normal CPU speed, the game averaged 6.83 frames per second on the TypeScript engine and 4.47 on the hosted WebAssembly engine; the slowest 1% of frames dropped from 4.62 to 1.94 fps.
Testing and quality gates
Every change goes through typecheck, lint, unit tests and a production build. Two engines that must agree need more than that, so these run too:
- Conformance scenariosPinned state, crowd and event hashes, run against both engines
- Nightly differential fuzzingRandom command streams; TypeScript and Rust digests must match
- Engine independence testNo file, network, console or clock access inside the simulation
- Coverage floors93–94% of the TypeScript (86% of branches) and 87% of the Rust
- Screenshot drift gateThe screenshot gallery is rendered in a pinned Playwright container
Status
Playable today at verticopolis.com (version 2.33.0), in the browser or installed as an app.
Last updated .
Contribute
I’m looking for contributors to Verticopolis. The code is open source, and the contributor guide covers setup, the quality gates and how changes get reviewed.
Receipts
The public pull requests and documents behind this page.
- Fixed-step simulation: the same result at any frame ratePR #847
- Conformance suite for both enginesPR #854
- The Rust engine port, held to that suitePR #857
- Scenario library and nightly TypeScript vs Rust fuzzerPR #861
- WebAssembly engine behind an opt-inPR #877
- TypeScript vs WebAssembly benchmark write-upDoc
- Round-tripping 1994 .TDT saves against the real gamePR #741
- The 3.0 cutover planPR #947
- The open cutover gateIssue #945




