Underground 2 Revived
Play Need for Speed: Underground 2 (2004) in Chrome and Firefox with WebGPU graphics, Web Audio, up to 120 fps widescreen, controller support, free roam and Online races with friends. Fan project, not
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A from-scratch, browser-native homage to the classic SimTower (1994): a
vertical metropolis you build floor by floor. Wire it with elevators, attract
tenants, set the rents, keep everyone happy, and climb the star ratings all the
way to a coveted TOWER.
Written in TypeScript on the Excalibur.js
game engine (camera, scene, culling, collision and the render loop), with a
procedural WebAudio soundtrack that changes depending on which part of the
tower you're looking at. No external art assets: every sprite is drawn in code.README 전문 보기 ↗Play it: verticopolis.com
A from-scratch, browser-native homage to the classic SimTower (1994): a vertical metropolis you build floor by floor. Wire it with elevators, attract tenants, set the rents, keep everyone happy, and climb the star ratings all the way to a coveted TOWER.
Written in TypeScript on the Excalibur.js game engine (camera, scene, culling, collision and the render loop). The simulation has two implementations that are held to the same conformance tests: the TypeScript one and a Rust one compiled to WebAssembly (see Simulation engines). A procedural WebAudio soundtrack changes depending on which part of the tower you're looking at. No external art assets: every sprite is drawn in code.
npm install
npm run dev # open the printed localhost URL
Other scripts:
npm run build # production build to dist/
npm run preview # serve the production build
npm run screenshots # build + headless-capture screenshots into docs/screenshots
Contributing? See CONTRIBUTING.md for the dev workflow:
the quality gates (typecheck, lint, test, build), the two test tiers, and
the enforced coverage floors.
The production build (npm run build) is an installable Progressive Web App.
Open the served build in Chrome, Edge, or Safari and use Install app / Add
to Home Screen to run Verticopolis in its own window, offline.
It's built on vite-plugin-pwa (Workbox
under the hood), so there's no hand-rolled service worker. A few details worth
knowing:
gallery/preview tooling pages are excluded from its scope and
precache.src/public/ and are generated from an in-code SVG (no
external art), matching the game's "every sprite drawn in code" ethos. Regen
with npm run icons.Floor tiles, then place rooms on top of them.
The ground floor and every 15th floor want a Lobby.Elevator or Stairs chain back to the
ground lobby. Unreachable tenants get unhappy and leave.Housekeeping), and shops,
restaurants and cinemas earn from foot traffic.Wedding Hall on floor 100 and
pass the VIP inspection to become a TOWER.| Action | How |
|---|---|
| Pan | Drag with the Inspect tool, middle/right mouse, or hold Space |
| Zoom | Mouse wheel |
| Build a room | Pick it from the left palette, click on a floor |
| Build/paint floors | Pick Floor/Lobby, click-drag |
| Build an elevator/stairs | Pick it, drag vertically to set the span |
| Edit a facility | Inspect tool, click a room or shaft → edit panel |
| Bulldoze | Bulldoze tool, click (or drag) |
| Game speed | Top-right buttons, or number keys 0–3 |
| Undo / Redo | Ctrl+Z / Ctrl+Shift+Z (⌘ on Mac), or the ↩ ↪ top-bar buttons |
localStorage, multiple save slots, plus
tower-file (.vctower) export/import.For players. The game autosaves to your browser's localStorage and
restores that slot on the next launch. You also get 3 named manual slots so
you can keep several towers, and tower-file export/import for backups or
sharing a tower with someone else. Export downloads your tower as a compact,
compressed .vctower file (a fraction of the size of the old JSON
exports), and Import loads one back through a file picker. Saves are managed
from the in-game saves panel; clearing your browser storage erases them.
The game can also import and export the original game's .TDT save files,
so a 1994 tower can be opened here and a Classic tower built here can be written
out as a .TDT (Modern towers are refused, since SimTower can only load Classic
ones). The format is documented in docs/canon/tdt-format.md.
How it works. A save is a snapshot of the headless simulation, not the
renderer. Simulation.serialize() writes the deterministic source of truth into a plain
SerializedGame object: money, star rating, the game clock, the RNG seed,
every unit and transport, the event system (fires, seasonal events) and
excavation history. SaveGame (src/storage/SaveGame.ts) stores that object
in localStorage. On load, Simulation.deserialize() rebuilds the
model and the Excalibur scene is regenerated from it.
Why not Excalibur's
Serializer? Excalibur ships a serializer, but it targets the render layer: Actors, Components, the scene graph. In this project those are derived view state, rebuilt from the simulation whenever the tower changes; they hold none of the authoritative game state. Serializing them would mean two save paths and a format coupled to the renderer, so the save system deliberately serializes the headlessSimulationinstead.
Saves are treated as untrusted input. deserialize() coerces non-finite
numbers, drops units/transports with an unrecognized kind, and clamps car counts
and shaft heights, so a hand-edited, corrupt, or foreign file degrades gracefully
rather than crashing the game. A version field plus a migrateSave() seam
gates loading: a save from a newer build loads best-effort instead of being
discarded, and any future format change has a single place to add an upgrade
step. The serialize → deserialize → serialize round-trip is covered by the
Vitest suite.
How the clone maps to the original's mechanics. Items marked ✅ are implemented and covered by the Vitest suite and/or the captured screenshots.
| Original mechanic | Status |
|---|---|
| Build floors, then rooms on top; ground + sky lobbies | ✅ |
| Lobby floors are transit-only (no rooms) | ✅ |
| ~100 floors up, basements below, continuous numbering (B1 = floor 0) | ✅ |
| Offices (quarterly rent) | ✅ |
| Condos / apartments (one-time sale, ~2×–2.5× cost, owner buy-back on loss) | ✅ |
| Hotel single / double / suite (nightly, need housekeeping) | ✅ |
| Fast food, restaurant, shop (foot-traffic income, business hours) | ✅ |
| Multi-floor cinema, party hall | ✅ |
| Parking, security, medical, housekeeping, recycling | ✅ |
| Whole-floor basement metro that brings visitors & eases the commute | ✅ |
| Cathedral → religion-agnostic Wedding Hall on floor 100 | ✅ |
| Stairs, escalators, standard / service / express elevators | ✅ |
| Adjustable elevator cars + per-floor (express) stop config | ✅ |
| Demand-driven elevator cars (SCAN dispatch toward waiting floors) | ✅ |
| Star ratings 1–5 → TOWER, population thresholds + facility gates | ✅ |
| VIP inspection for the final TOWER rating | ✅ |
| Elevator overcrowding stresses tenants; the unhappy leave | ✅ |
| Rush-hour demand (morning/lunch/evening) vs. quiet nights | ✅ |
| Stressed crowds turn red when transport is overwhelmed | ✅ |
| Construction time with scaffolding; destruction/sell refunds | ✅ |
| Fire that spreads unless security/medical contain it | ✅ |
| Bomb threats (defused by security) on prestige towers | ✅ |
| Buried treasure found while excavating basements | ✅ |
| Living tower: walking people, riding cars, the metro train, day/night | ✅ |
| Save/load, multiple slots, .vctower export/import | ✅ |
| Per-person stress/routing simulation |
Desktop vs. mobile: the desktop layout mirrors the original's dollhouse
cross-section; mobile gets a responsive portrait layout with an icon toolbar and
touch pan/pinch (see docs/screenshots/09-mobile.png).
src/
engine/ # pure simulation, no DOM
types.ts shared types
facilities.ts facility catalog, costs, star thresholds, grid constants
Clock.ts game time (days, weekdays, day phases, quarters)
rng.ts deterministic PRNG (mulberry32)
Tower.ts spatial model: two-layer grid, placement rules, reachability
Simulation.ts economy, population, satisfaction, ratings, events, save
render/ # presentation
excalibur/TowerEngine.ts Excalibur.js scene shell; delegates to friend-modules below
excalibur/towerScene.ts scene construction, graphics baking, sky, lifecycle
excalibur/towerReconcile.ts retained-actor reconciliation (rooms, structure, facade)
excalibur/towerCrowd.ts cars, train, garbage/garage traffic, ambient walkers
excalibur/towerInputCamera.ts pointer input, picking, camera, coords, audio focus
excalibur/towerOverlay.ts 2D overlay/stats painters + decorative event visuals
sprites.ts procedural per-facility drawing (drawn into Excalibur canvases)
pixelSprites.ts dollhouse room interiors + walking/seated people
ui/ # DOM controls: palette, status bar, editor panel, modals, toasts
UI.ts shell class; delegates to friend-modules below
templates/ lit-html template bodies; uiDialogs.ts dialog controllers
uiPanels.ts editor/inspector panels; uiStatus.ts status+log pump; uiPalette.ts palette build
audio/Audio.ts # location-based procedural soundtrack + SFX
# ToneAudioEngine (orchestrator) + toneScenes (data/math) + toneVoices (synthesis)
storage/SaveGame.ts # localStorage + .vctower export/import
main.ts # GameApp: tool semantics, sim tick, glue (input/camera via Excalibur)
gallery.ts # standalone sprite-catalog page (docs/screenshots)
tests/ # Vitest unit tests for the engine
The engine is deliberately DOM-free and deterministic so it can be unit
tested in isolation (npm test). Rendering, the camera, panning, zooming and
pointer input all run on Excalibur.js:
TowerEngine owns the game loop and scene, drawing each facility, transport and
merged structural "run" as an Excalibur actor whose graphic reuses our pixel-art
sprite code. main.ts only supplies tool semantics through the engine's
controller hooks and advances the simulation each frame. It never touches the
camera or raw pointer math directly.
For diagrams of the layers, the frame loop, the engine subsystems, input flow, and persistence, see ARCHITECTURE.md.
The simulation runs on one of two engines that must produce the same tower:
src/engine/) is the default today.engine-rs/), compiled to WebAssembly, which the game drives
directly through EnginePort. Add ?engine=wasm to the address to run it for
one visit. If the Rust engine fails to load, the game falls back to TypeScript.?engine=wasm link does for one visit. Automatic and TypeScript engine
(stable) start TypeScript. The Settings panel and the version line show which
engine is running.Version 3.0 is in development and is not released. The plan makes Rust the
default engine while keeping TypeScript selectable. TypeScript would be removed
in a later 3.x release. During the cutover the game moves onto one synchronous
numeric interface, EnginePort, that the Rust engine implements directly; see
ARCHITECTURE.md for
the design and CONTRIBUTING.md for building the engine
package (npm run wasm:build).
See docs/screenshots.md for how these are captured and how to add before/after images to a pull request.
| Day | Night |
|---|---|
A humble ground floor climbs the whole rating ladder to the coveted TOWER.
These frames come from the pinned-container screenshot pipeline (the
milestones scene in scripts/scenes/milestones.ts), the same one that
renders the rest of the gallery. The end-to-end test (e2e/milestones.spec.ts)
independently earns every rung on each run and uploads its own captures as a
CI artifact (to the untracked e2e/output/, never this gallery), so the rungs
shown here are continuously proven against what the game actually does.
| 1★ | 2★ | 3★ |
|---|---|---|
| 4★ | 5★ | TOWER |
|---|---|---|
Two tiers: a fast Vitest unit suite (engine, game logic, UI, sprites, audio,
storage: placement, reachability, economy, star gates, housekeeping, save/load
round-trips) and a Playwright browser end-to-end smoke that proves the game
stays winnable and renders. Run npm test and npm run e2e (after
VC_TOOLING=1 npm run build; the flag publishes the window.game handle the
e2e specs drive, which production builds compile away). The full testing
strategy (the two tiers, the enforced
coverage floors, and what's unit-tested vs. integration-covered) lives in
CONTRIBUTING.md → Testing & coverage.
The source code is MIT (LICENSE); the original art and audio are CC BY 4.0 (ASSETS-LICENSE). To get involved, see CONTRIBUTING.md.
Built fresh as a clean-room clone. None of the original game's code or assets are used.
| ◻︎ abstracted as an aggregate model |