NYC Fiscal Simulator
A story of building a mayoral budget game for the NYPL hack — what inspired it, what we learned, how it's built, and the challenges along the way.
What inspired this project
Most people experience city government as a mystery. Taxes go up, subways break, schools get funded or don't, and the reasons live in a multi-thousand-page budget document that nobody reads. Every policy sparks criticism. The New York City budget is real money with real consequences: roughly $100 billion a year, dictating every aspect of our lives.
We wanted to make those complexities something you could feel, not just something you could read about. Our game puts you in the mayor’s chair, where every decision has consequences, and every solution comes with a cost. Players get to experience the challenges of governing New York City firsthand, while learning about civic issues, thinking critically about policy, and understanding how their decisions can shape the future of the city.
What we learned
We learned how to effectively integrate LLMs into our workflow. Throughout the process, we experimented with different models and approaches to understand what worked best for our team. At the same time, we learned to balance AI-generated suggestions with our own ideas and judgment, using AI to support our creative process rather than replace it.
How we built it
Stack: React 19 + TypeScript + Vite + Zustand + Tailwind 4, with plain Node smoke tests run through jiti.
Features built over the hack:
- A six-year term with a hand-authored crisis arc and randomized mid-year events.
- Capital projects with build times, costs, quality, and construction tracks that respond to departmental funding.
- Ongoing programs (schools, hospitals, transit, water, housing, safety), recurring investments with a capital outlay up front and operating costs every year after.
- A resident preference analysis panel that ranks what each borough wants, with star badges on recommended projects.
- Year-end recaps written as a city that talks back: policy quotes, program praise, and a nagging civic data analyst when you ignore the recommendations.
Challenges we faced
The determinism wall. The first version drew projects with rng.pickN. That produced fine-looking runs but made it impossible to reason about why a run felt bad. We rewrote pool selection as weighted sampling over preference scores, seeded by the same RNG. Now every "why does the Bronx get nothing good?" question was answerable, and we could balance against fixed seeds.
Balance feels like whack-a-mole. Program economics had to be cheap enough to be tempting but expensive enough to matter; approval deltas had to sting without feeling punishing. We tuned PROGRAM_MAX = 3 per borough and the per-type operating costs until going all-in on programs was viable but not dominant, then verified it with a smoke test that builds programs in every borough and checks the recap reflects it.
Where it lands
If there's one thing we want a player to walk away with, it's that the budget isn't a spreadsheet. It's a set of promises. Every program you build is a promise to someone who'll notice if you stop funding it. Every project you skip is a promise broken in a neighborhood you can actually see on the map. That's the whole idea.
Built with React, TypeScript, and a lot of seeded randomness.
Built With
- react
- tailwind
- typescript
- vite
- zustand
Log in or sign up for Devpost to join the conversation.