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,即可验证所有权并自行管理介绍。
一分钟完成验证An immersive 1920s Prohibition-era Halloween masquerade mystery, archive terminal, RSVP ledger, and pre-event CTF.
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
The Black Veil is an immersive historical-fiction invitation and archive for a real All Hallows’ Eve event. The public story is set in Manchester, New Hampshire, from 1921 to 1926, with fictional proprietor Cassandra “Cassie” Castello at the center of an unresolved 1924 disappearance.
The club, Cassandra, its alleged crimes, and all witness accounts are fictional. Manchester, Prohibition, the Amoskeag industrial setting, the Merrimack River, Elm Street, and the 1922 strike provide documented historical context. Every archive record carries provenance metadata that distinguishes those layers.查看完整 README ↗The Black Veil is an immersive historical-fiction invitation and archive for a real masquerade on October 23, 2026. The public story is set in Manchester, New Hampshire, from 1921 to 1926, with fictional proprietor Cassandra “Cassie” Castello at the center of an unresolved 1924 disappearance.
The club, Cassandra, its alleged crimes, and all witness accounts are fictional. Manchester, Prohibition, the Amoskeag industrial setting, the Merrimack River, Elm Street, and the 1922 strike provide documented historical context. Every archive record carries provenance metadata that distinguishes those layers.
npm install
npm run dev
Open http://localhost:3000.
Quality checks:
npm run lint
npm run typecheck
npm run build
/ — 1926 invitation entry and the early Cassandra newspaper hook/archive — archive drawers organized by year, 1921–1926/archive/[slug] — full-size physical artifact view with prior/next record navigation/guest-ledger — real-name RSVP, persisted to Postgres and signed in for this device/invitation — private invitation revealed after archive access and RSVP/game — the guest's night: three-tab character card ("Characters coming very soon" until the host presses Start game), objectives, final answers, results. See docs/game/README.md/black-frog — the seven-trial CTF (server-verified flags) and in-person point claims/postscript — the unlisted bonus flag (footer link once the register is unsealed; the flag is in the unlocked terminal ledger)/bench/[ticket] — one restoration bench: a graded engineering exercise (RST-01 … RST-12)/resume — request a magic-link sign-in to restore your register on a new device/admin — staff-only, passcode-gated cast page: who is coming, their character, sign in as them, and the one Start game button that releases every card and objective (linked from the footer)src/data/archive.ts defines typed newspaper pages, clippings, photographs, police documents, telegrams, notices, invitations, images, catalog numbers, and provenance. Real images and articles carry a sourceUrl, which surfaces on the record page as a "Learn more about this record" link; fictional artifacts do not show a provenance panel.
Important newspaper copy is deterministic HTML/CSS rather than text baked into generated images. This keeps headlines readable at full size while preserving a scanned-newspaper appearance. Photographs can be inspected in a native dialog.
public/archive/fictional/cassandra-castello-portrait.png — canonical Cassandra portrait; a privately supplied photograph, not AI-generatedpublic/archive/fictional/cassandra-society-cigarette-holder.png — society portrait using the same canonical likenesspublic/archive/fictional/cassandra-arrival-by-carriage.png — Cassandra's carriage arrival before the society desk's cameraspublic/archive/fictional/cassandra-masquerade-evidence.png — fictional 1924 masquerade evidence photograph using the same likenesspublic/archive/fictional/black-veil-passage.png — fictional Manchester mill-passage photographpublic/archive/real/elm-street-manchester-c1908.jpg — Library of Congress, Detroit Publishing Company Collection; no known restrictionspublic/archive/real/amoskeag-strike-picket-1922.jpg — Manchester Historic Association image, public domain in the United States, accessed via Wikimedia CommonsGenerated images are never described as authentic historical photographs. The real Elm Street image is explicitly captioned as context and not as a depiction of The Black Veil.
The original archive-to-terminal-to-ledger flow is preserved. The 1926 invitation record contains a hidden seal that opens the simulated archival terminal; solving it unlocks the real-name RSVP, private invitation, and Black Frog trials. The terminal is client-side fiction and never executes commands on the visitor’s device.
RSVPs use a guest’s real name and do not assign a fictional character. src/lib/rsvp-service.ts defines the persistence boundary. remoteRsvpService (used by GuestLedger) POSTs to /api/rsvp, which upserts a guests row by email, writes to rsvps, and signs the visitor's device in (see "Guest accounts and resuming" below). localRsvpService remains for local development without a database connection. Submissions are also mirrored to localStorage purely so the visitor's device can render "you already RSVP'd" without a login step; the database, not localStorage, is the source of truth.
Black Frog flags are checked server-side in POST /api/ctf/submit against src/data/ctf-flags.ts, a module never imported by client code — only src/lib/ctf-scoring.ts and the intentionally source-revealing /secret page (challenge VII) import it. src/data/ctf.ts holds public challenge copy (title, briefing, clue, points) with no flag field, safe for "use client" components. Each solve is written to ctf_solves, keyed by guest, so a correct flag can never be re-derived by reading client JS.
Not advertised for launch: while eventConfig.benchPromoted is false (src/config/event.ts) there is no footer link, no link from the trials, and no conservation slips in the newspapers. /bench still answers if typed.
/black-frog carries two tracks. The Manchester trials (I–VII) are the original in-world puzzles, answered with a VEIL{…} flag. The Restoration Bench (RST-01 … RST-12) is an engineering track: twelve labs where the flag is a receipt for work the server actually checked, not a string to be guessed.
The fiction is the present-day team digitising the archive. Every newspaper record carries exactly one conservation slip in its right-hand column, naming the ticket open against that paper — src/components/newspaper-artifact.tsx renders it from getBenchLabForRecord(record.slug), so the slip and the lab can never drift apart.
Each lab is one of three shapes, or a combination: a console (RST-01 replays a real-ish git history; RST-08 drives a deliberately vulnerable vault sandbox), files to edit (a Dockerfile, a pipeline config, an IAM policy, SQL, a conflicted register), and findings to report (error-budget arithmetic, review line numbers, characterization values).
The split of responsibility mirrors the trials:
src/data/bench.ts — public copy, starting material, and the published datasets. Client-safe; contains no answers. Deliberately broken starting files live here.src/lib/bench/graders.ts — server-only. Every expected answer is here or derived here from the published data. Graders return named checks, so a rejected submission reads like a build log.src/lib/bench/git-repo.ts, src/lib/bench/vault-sandbox.ts — server-only console fixtures. The register entry RST-01 asks for, and the record RST-08 asks for, exist only here, so they can only be reached by running commands.src/data/bench-flags.ts — server-only receipts, issued after the checks pass.POST /api/bench/[ticket] — action: "run" replays a console history; action: "submit" grades and, on a pass, writes the solve to ctf_solves under bench-<lab id>.RST-10 executes guest-written SQL. It runs in an ephemeral in-memory PGlite instance created and discarded per request, seeded from ledgerSchemaSql, never against DATABASE_URL; submissions are limited to a single read-only statement with a statement timeout. PGlite is listed in serverExternalPackages (next.config.ts) because its wasm files do not survive bundling.
Both tracks score into the same ctf_solves table and the same totalScore in GET /api/me.
npm run bench:check
scripts/bench-selftest.ts works all twelve labs correctly and then submits all twelve untouched, asserting that the first passes and the second fails. Run it after touching any lab's starting material or grader.
A guest's identity is a guests row keyed by email, created on first RSVP. Saving an RSVP signs that device in with an httpOnly session cookie (src/lib/auth/session.ts, HMAC-signed, no session table). To resume on a different device — or after clearing storage — a guest visits /resume, enters their email, and gets a one-time sign-in link (src/lib/auth/magic-link.ts, POST /api/auth/request-link → GET /api/auth/verify). Clicking it sets the session cookie and lands on /resume/restored, which calls GET /api/me and rehydrates the localStorage mirrors the rest of the UI already reads (RSVP, CTF progress), then redirects into /guest-ledger.
Sign-in links are emailed via Resend if RESEND_API_KEY is set (src/lib/email.ts); without it, the link is logged to the server console — fine for local development, not for production.
Sealed for launch: while eventConfig.charactersReleased is false, GET /api/me never returns a character (whatever guests.characterId holds), the Guest Ledger shows a compact "Sealed" strip, and POST /api/admin/characters refuses assign with a 409. Flip it and redeploy to release the cast.
guests.characterId links a guest to a row in characters (public profile + private dossier fields, per the shapes src/features/game/models.ts already sketched). Once assigned, GET /api/me returns the public fields (characterName, occupation, publicBiography, factions) and, because that route only ever answers for the signed-in guest, their own dossier (privateBiography, secrets, objectives). CharacterCard shows both on /guest-ledger, with the dossier behind a seal. Murderer/victim flags never leave the server except to /admin. Character content is authored as part of the game design (docs/mystery/) and entered into the characters table with npm run db:studio or the /admin Database panel. Staff deal or cast characters from /admin (see below).
Digital flags self-score, but not everything can be auto-checked — an in-person objective, a piece of roleplay. POST /api/claims lets a signed-in guest submit a claim (label + optional note); it stays pending until a staff member reviews it at /admin, gated by a shared ADMIN_SECRET passphrase (no per-staff accounts). Approving sets the points awarded; GET /api/me sums approved claim points into totalScore alongside the CTF score, which ChallengeStandings displays.
The same passphrase opens the rest of the Staff Office at /admin. GET /api/admin/overview feeds headline counts, solves per challenge, every guest, and the cast. POST /api/admin/characters with action: "assign" deals each attending guest without a character a random unheld one; "clear" takes them all back. Casting a specific guest to a specific character is done in the Database panel. POST /api/admin/impersonate signs the staff browser in as any guest, as their magic link would, so staff can see the site as that guest does. The Database panel edits or deletes any guest, RSVP, character, claim or solve (GET/POST /api/admin/db, with a whitelist of editable fields per table). The unlock form calls GET /api/admin/session, which reports a wrong passphrase, a missing ADMIN_SECRET, and an unreachable database as three different errors.
.env.example to .env.local and fill in DATABASE_URL (Supabase's pooled "Transaction" connection string for serverless/Next.js), SESSION_SECRET (openssl rand -hex 32), and ADMIN_SECRET. RESEND_API_KEY/EMAIL_FROM are optional (see above).npm run db:migrate to apply the migrations in drizzle/ (creates rsvps, guests, magic_links, characters, ctf_solves, point_claims). Bench solves reuse ctf_solves, so no additional migration is needed for them.npm run db:generate regenerates migrations after schema changes in src/lib/db/schema.ts; npm run db:studio opens Drizzle Studio against the configured database — also how character content gets entered for now.DATABASE_URL, SESSION_SECRET, and ADMIN_SECRET in the host's environment (for every environment that serves the site, preview builds included — .env.local is not deployed), and run step 3 against that database before the first RSVP. Skipping either leaves POST /api/rsvp returning a 503 and the register refusing names.GET /api/standings ranks every guest by the same score as GET /api/me (solves plus approved claims) for the Guest Ledger leaderboard. It is signed-in only; other guests appear by first name and initial. The leaderboard and the /admin dashboard poll every five seconds, so flags submitted anywhere show up on open screens without a reload.
The model supports a primary 1926 murder and an optional advanced investigation into Cassandra and the 1924 masquerade without choosing how those mysteries connect.
The party game (the old man, Cassie, the five survivors, the Butler's vial) is specified in docs/game/ and implemented under src/data/game (the bible, server-only), src/lib/game (grading, scoring, services, tests) and src/app/api/game / src/app/api/admin/game. Run npm run game:check, npm run game:test, npm run game:docs, and at the end of the night npm run game:grade -- --help-style commands (see docs/game/README.md).
The public site does not establish what happened during the 1924 masquerade, whether Cassandra was victim or suspect, who reportedly died, what became of Cassandra, who protected the club, who sent the 1926 invitations, who will die in 1926, who will kill them, or how the two mysteries ultimately connect.