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 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗An unofficial, non-commercial Pokémon action battler built with Vite, vanilla JavaScript and Canvas 2D. Pokémon and related names belong to Nintendo / Game Freak / The Pokémon Company. This project is not affiliated with them.
npm install
npm run dev
npm run server # separate terminal, for matchmaking, drafts and co-op
Open http://localhost:5173. For two local players use separate browsers, or two dev tabs with ?pid=a and ?pid=b. Bot battles and solo raids work without the match server.
A faint automatically sends out another teammate. There are no revives; co-op continues while either participant has usable Pokémon. A disconnected participant has 15 seconds to reconnect with the same identity. New players cannot join an ongoing encounter. Rooms expire, and a raid ends after 10 minutes.
Rayquaza and Mewtwo Mega Evolve once at 50% HP into Mega Rayquaza and Mega Mewtwo Y. Remaining and maximum HP are preserved; form stats, type/ability and sprites change together. Co-op clients follow the server's form state.
From stage two, bosses periodically channel their ultimate behind a shield worth 5% of maximum HP. Break the shield before the countdown expires to cancel the attack and earn a four-second vulnerability window. Shield damage can overflow into HP. An uninterrupted ultimate hits active players through dodges for 40/55/70/85% of maximum HP on Easy/Normal/Hard/Legendary. Existing Dragon Ascent, Psystrike and Aeroblast choreography plays cosmetically alongside the authoritative outcome. Dragon Ascent rises above the arena before diving. Player ultimates retain their charge intro and attack animation without pausing the shared raid.
Bosses hold their position, altitude and facing while casting. Beams attach to their mouths. Rayquaza uses wind/meteor effects and Lugia uses mist/spray. Each has one SpriteCook disc beneath it; their mouths, meteors and dive trails do not repeat that disc. Mewtwo retains its psychic sigil. Dark wisps and impact effects keep the battlefield readable. Ultimate damage lands on the signature action's impact; movement attacks such as Dragon Ascent keep their flight/dive.
Rayquaza and Lugia share layered cast, trail and impact effects across their raid patterns and playable moves. Meteors have molten rock cores, movement leaves a short slipstream, storm lightning starts at its cloud, and water waves/columns break into foam and spray. Dragon Pulse retains its travelling rings. Aeroblast uses a distinct mouth-attached pressure stream with rotating shear gusts; Water Pulse retains its compact beam and travelling rings. Rayquaza's pillars are separate translucent wind funnels, and Lugia's are purple water spouts, with space between their silhouettes. Mewtwo's beams use focused psychic cores and travelling filaments. Nova and cataclysm releases sustain moving pressure fronts, airborne gusts/spray or psychic shards after the warning fades. Their safe center or shelters remain visibly clear. Ultimate impacts also send pressure fronts across the floor. Visual effects do not decide damage or safe zones.
Each boss has its own raid environment and original synthesized battle theme, with a faster final-stage arrangement: Rayquaza fights above the clouds at Sky Pillar, Armored Mewtwo in a containment laboratory, and Shadow Lugia at a storm-lashed island fortress. These are lore-inspired designs; regular arena selections stay unchanged. Rayquaza's Tempest Vortex uses spiraling wind and mist around a calm safe eye, without dragon silhouettes. Mewtwo's cinematic throw renders one opponent and one shadow through capture, launch and return, while the server retains authority over combat.
Lugia also uses Water Pulse: Dragon Pulse's layered beam and travelling rings, water-blue for the fourth regular move slot and purple for the Shadow Lugia raid boss. Thunderstorm forms seven cloud clusters and seven staggered bolts on High/Balanced (four on Performance); extra visual bolts remain inside the warning circle and add no hidden damage.
Compatible hits within four seconds can trigger Flash Freeze, Conduction, Wildfire, Melt, Steam, Grounded or Fanned Flames. Shields and dodges prevent priming. Reactions consume their aura, have a 1.5-second guard and bounded chains; chains do not hit allies or recursively trigger more reactions. Freeze retains the six-second thaw guard. Ground types resist conduction. The in-game guide lists combinations.
All 17 Mega forms use verified base/form stat ratios. HP is preserved, physical/special damage uses the appropriate effective stats, and switching does not stack bonuses. See src/data/mega-stats.js for source data and model notes.
| Action | Keyboard |
|---|---|
| Move | WASD / arrows |
| Four moves | 1–4; hold chargeable moves |
| Ultimate | R / 5 |
| Dodge | Space |
| Switch | Q, then 1–6 |
| Mega Evolve | M |
| Pause | P / Escape |
| Start / skip intro or finale | Enter |
Phones use the floating joystick, move/ultimate buttons, Dodge/Mega buttons and teammate portraits. ?touch forces touch controls for layout checks.
The 60 Hz shared engine runs in the browser for solo play and headlessly in Node for authoritative online battles. The match server sends snapshots at 30 Hz using protocol 2. Prediction, interpolation and reconciliation remain in src/matchmaking.js. server/raids.js owns shared raid sessions; server/drafts.js owns online drafts. BossPresenter and src/raid-cinematics.js add local visuals without deciding damage.
Results include surviving team members, damage, moves, reactions, transformations and earned XP. Friend-room progression is labelled local/unverified.
Mastery is stored under the versioned msb.mastery key. Version 1 saves migrate to version 2. Result IDs prevent duplicate rewards. Match history is bounded to 30 entries, event logs to 600 events per match and processed result IDs to 500. Clearing browser storage loses local progression; there is no cross-device synchronization. Existing trainer and team saves retain their keys.
Vercel serves the frontend; a persistent Node/WebSocket service runs npm run server on Render. Existing deployed frontend: https://pokemon-gules-delta.vercel.app/. Changes in the local expansion branch are not deployed automatically until merged/pushed to the deployment branch.
| Variable | Location | Purpose |
|---|---|---|
VITE_MATCH_SERVER_URL | Vercel/build | Public wss:// match-server URL |
PORT | Server | Host's listening port; local default 8787 |
ALLOWED_ORIGINS | Server | Comma-separated allowed frontend origins |
UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN | Server only | Persistent ratings; otherwise local JSON fallback |
LEADERBOARD_FILE | Server | Optional fallback JSON file path |
MAX_MATCHES | Server | Match capacity, default 3 |
BOT_MATCH_AFTER_MS | Server | Ranked Online Battles: after this long with nobody compatible, a computer trainer is matched (AI difficulty rolled from the player's online win streak; bots are not counted online and coins from them share the Bot Battles daily cap). Default 25000; 0 turns it off |
MAX_RAIDS | Server | Concurrent raid limit, default 1; a co-op raid also consumes match capacity |
KEEP_AWAKE, KEEP_AWAKE_URL, RENDER_EXTERNAL_URL | Server | Optional best-effort self-ping; KEEP_AWAKE=0 disables it |
MATCH_TEST_HP | Local testing only | Reduced starting HP; never enable for production |
The frontend recognizes legacy protocol-2 /health responses when /ready is absent, but waits for a WebSocket welcome before calling a connection ready. New modes check the server feature list and report an update requirement rather than searching indefinitely on older deployments.
No new secrets or environment variables are required by the expansion. Never put Redis credentials in VITE_* variables. Use /health for liveness/load information and /ready for startup readiness. Storage failures are reported separately; gameplay can run with the fallback. Readiness is served only after server initialization and WebSocket setup.
The small startup entry wakes the match host while the game downloads, before fonts and sprites load. Requests share an in-flight wake and a 15-second successful readiness cache; a failed wake can retry immediately. The client retries temporary failures within a two-minute startup budget, waits for the WebSocket welcome, and aborts startup work on cancellation. If a legacy host omits HTTP CORS headers or returns a boot HTML page, a successful WebSocket welcome can establish readiness. Team assets load alongside connection setup before queueing, and cancelled attempts cannot queue after a retry. Local battles do not depend on readiness. The critical HTML boot screen hides unfinished battle markup on refresh, and fonts and sprites load in parallel.
Opponent positions use adaptive snapshot interpolation without a second smoothing delay. Co-op partner movement is interpolated between snapshots and its sprite advances each render frame. Local movement and attacks retain immediate prediction. These reduce client delay; they cannot remove physical network latency. Place the match service close to players and use always-on compute for immediate online availability. Render Free spins down after 15 idle minutes and can take about one minute to wake (Render documentation); self-pinging does not guarantee always-on matchmaking. Actual production sleep-to-ready timing and internet latency have not been measured here.
npm test
npm run build
node scripts/profile.mjs
For co-op: both tabs choose Legendary Raids, matching boss/team size, then either Online Partner or Create Room / Join Room. For drafts: both choose Draft Battles with matching mode and format, make picks, then confirm final teams. Friend Room uses its existing separate peer-to-peer room flow.
High/Balanced/Performance settings bound particles and rendering resolution. Expensive decorative overlays are suppressed in Performance mode; essential telegraphs, shield warnings and status indicators remain. Decorative effect queues and replay histories are bounded. Background tabs skip drawing and resume with a capped simulation step.
Sprites and art receive one-week cache headers from vercel.json; version the URL or filename if an existing asset changes.
Browser profiling: open /scripts/browser-bench.html through the Vite dev server; add ?soak=60 for a mixed-effects soak. node scripts/network-profile.mjs measures two ordinary matches plus a raid against an isolated local server. These diagnostic pages are not part of the production Vite entry.
See docs/PERFORMANCE.md for measured results and limits, and docs/EXPANSION.md for the implementation checklist. There is no guarantee of 60 FPS on every device. Finishing sequences include a short visual replay of actual captured frames (raw pixel budget below 3 MB in High quality, cleared on exit). Full deterministic battle replay and network hit rewind are not implemented; match-history event timelines and existing authoritative hit timing are used instead.
Raid bosses alone use the supplied Shadow Lugia, Armored Mewtwo and Dark Rayquaza skins. Playable legends and their Mega forms retain their original sheets and names. Shadow Lugia has the reference sheet's red eyes, purple beams/waves/lightning and a purple SpriteCook disc restricted to the floor. Mega boss skins retain their form stats and timings; Armored Mewtwo keeps its armor after evolving. Source sheets and the reproducible import script are retained under art/sources/legendary-skins and scripts/prepare-legendary-skins.py.
public/sprites.For visual review, scripts/browser-bench.html?preview=rayquaza&audit (also mewtwo / lugia) captures every raid pattern in the real renderer at wind-up, release and follow-through. Armor poses are extracted as connected source characters to exclude neighboring tails and body fragments.
Daily Missions, Chaos Shop and Collection are available from the main menu; the trainer header shows the selected wallet's balance. Three deterministic missions reset at 00:00 UTC. The weekly victory trail resets Monday at 00:00 UTC. Completing all three dailies unlocks a separate bonus; offline days have no penalty. Reward values and the ten-item catalogue live in src/economy.js.
The shop contains Amethyst Circuit and Ember Crest portrait frames, Skybound and Tempest Keeper titles, Astral Orbit and Jade Slipstream ground auras, Sapphire Arrival send-out rings, Victory Hop and Victory Roar poses, and Astral Overdrive ultimate-opening stars. Frames appear in the menu and battle HUD, titles beneath the menu trainer name, and the other cosmetics in actual battle/victory presentation. Poses use the selected animation where supported and the species' native victory fallback otherwise. No cosmetic changes HP, damage, hitboxes, timing or cooldowns. Existing mastery cosmetics remain available; an equipped shop cosmetic takes precedence in its slot.
Local Journey is a browser-only prototype stored separately at msb.economy.local. It uses the completed local combat engine, has duplicate-result protection and a monotonic session clock, but a user who modifies browser storage/code or the device clock can tamper with it. It is not secure currency and never uploads or merges into the online wallet. Offline play remains available. The online countdown uses a clock anchored to server time after a successful response; a fully offline local journey uses device time and clearly remains local-only.
Online Journey uses the existing anonymous player ID plus a random browser credential. The server stores only its hash. This prevents an ID alone from authorizing wallet operations, but it is not an account, paid-purchase identity or cross-device login. Clearing browser storage loses access; there is no recovery or automatic device transfer. Credentials are local browser data; use HTTPS/WSS outside localhost.
The match server exposes a bounded, rate-limited POST /economy API for view/claim/buy/equip only. Prices and rewards are server-defined. Transactions validate ownership and balance, retain receipt IDs, and write the complete new state to a temporary file, fsync and rename before confirming success. Duplicate requests cannot debit or credit twice. Equip/unequip operations require ownership and are idempotent. Corrupt/unsupported server saves fail closed; corrupt local saves are preserved for recovery. Requests failing or timing out never replace a valid wallet with empty defaults. Cached online wallets remain separate and do not become spendable local funds.
Set ECONOMY_FILE to a path on a persistent disk (default server/data/economy.json). This single-process file store survives ordinary server restarts with that disk. It does not use the leaderboard's optional Redis backend; ephemeral hosting loses it on redeploy, and shared-file multi-instance deployment is unsupported. Use a transactional database before horizontal scaling. Back up the ledger and browser credentials appropriately. No secrets or generated ledgers belong in Git.
Before real payments: implement durable authenticated accounts, recovery, a transactional database, provider-side payment verification, idempotent webhooks, reconciliation/refunds, entitlement audits, privacy/security controls and applicable legal requirements. Keep payment records separate from earnable coins. No payment provider, premium currency, loot boxes or simulated real-money purchases are implemented. This Pokémon fan game has no assumed commercial/IP clearance; review rights and monetization implications before any paid launch.
The playable roster is now 137 final-stage or single-stage base species, down from 220. All selections, random generators and server validation use that roster. Full removed/retained lists and regional decisions are in docs/ROSTER-AUDIT.md. Saved teams are backed up under msb.teams.pre-final-v1 and migrated without transferring mastery to replacements. Archived species data/assets remain for records, shared moves and narrative scenes.
Run locally in separate terminals:
npm install
npm run dev
npm run server
The game is at http://localhost:5173; the match server defaults to port 8787. For the economy unit checks use node --test tests/economy.test.js; npm test includes real WebSocket draft/co-op and HTTP anti-exploit checks. npm run build produces the client build.
For an isolated UI transaction review, run node scripts/guild-simulation.mjs, then open http://localhost:5173/scripts/guild-review.html while Vite runs. Five battles are simulated through the real combat engine; the resulting rewards load into a memory-only QA wallet, with an explicit banner. Claims/purchases there do not affect real player saves. The generated fixture is ignored by Git and these script pages are not part of the Vite production build. No arena skins, emotes or badges are sold: only the implemented categories are displayed.