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,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Asyq League — digital asyq atu (Kazakh traditional game) for students & university tournaments. Narxoz Incubator 2026.
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
Asyq League — асық ату в браузере Цифровая версия казахской игры на меткость асық ату. Прицелься, выбери силу броска и выбей асыки из кона сақой. Есть обучение, 28 испытаний с нарастающей сложностью и историей игры, дуэль вдвоём на одном телефоне, испытание дня, лига университетов, редактор расстановок со ссылкой для друга и магазин косметики за игровые тиыны (без доната).檢視完整 README ↗
Цифровая версия казахской игры на меткость асық ату. Прицелься, выбери силу броска и выбей асыки из кона сақой. Есть обучение, 28 испытаний с нарастающей сложностью и историей игры, дуэль вдвоём на одном телефоне, испытание дня, лига университетов, редактор расстановок со ссылкой для друга и магазин косметики за игровые тиыны (без доната).
demo, пароль asyq2026 (на странице входа кнопка «Войти как demo» создаёт отдельный временный аккаунт с 1000 🪙 для каждого проверяющего; общий demo при каждом входе пополняется до 1000 🪙)Работа для технического этапа Narxoz Incubator 2026 (задание «Асық ату»).
Асық ату знакомо родителям по летним дворам, но многие студенты уже не играли в него сами. Asyq League — это игра для студентов и школьников Казахстана, в которую можно сыграть за минуту с телефона, а потом бороться за свой университет в лиге вузов.
Идея продукта: не просто перенести асыки на экран, а дать повод возвращаться каждый день.
| Событие | Очки |
|---|---|
| Асык полностью за меловой линией кона | +1 |
| Выбил несколько за один бросок — за каждый сверх первого ✦ | +1 |
| Сақа остановилась внутри кона (как «айып» во дворе) | −1 |
| Кон очищен — за каждый неиспользованный бросок ✦ | +2 |
Выбитый асык сразу удаляется из состояния раунда, поэтому очки за него невозможно получить дважды. Счёт не опускается ниже нуля.
Отмечены ✦ выше и значком «авторское» в самом приложении:
PUDDLE_DRAG): асык и сақа быстро теряют скорость.
Сильный бросок пролетает воду, слабый застревает, а асык у края кона в луже не выкатывается за линию. Лужа детерминирована
(как и вся физика), поэтому сервер пересчитывает броски с лужами точно так же. Прицельный пунктир учитывает лужу: он
обрывается там, где сақа остановится в воде. Лужи можно ставить в редакторе
(до 4 штук), они сохраняются в ссылках-испытаниях и работают в дуэлях со своим полем./history): что такое асық и четыре его стороны, лента времени от древности до списка ЮНЕСКО,
сақа, как играли во дворе, современность (ЮНЕСКО 2017, Всемирные игры кочевников), карточки «Знаешь ли ты?», символы
уровней и список источников. Факты сверены по открытым источникам, ссылки приведены на странице./rules): схема броска, пять карточек с картинками «за что даются очки», пошаговый
пример подсчёта, объяснение звёзд, отличие очков от звёзд и тиынов, частые вопросы и советы новичку.Мультиплеер без аккаунтов: «Дуэль → Онлайн по ссылке» → «Создать матч» → отправить ссылку /m/КОД другу.
Друг вводит имя, и матч начинается. Каждый играет на своём устройстве, а каждый бросок выглядит у обоих одинаково:
у соперника бросок проигрывается с анимацией.
Как это устроено:
sx, vx, vy). Физика детерминированная, поэтому каждый клиент сам получает
одно и то же поле, очки и очередь, повторяя те же броски через duelThrow.Match, сам проигрывает их и проверяет: чей сейчас ход, честен ли бросок,
не устарел ли запрос (защита от гонок через version). Подделать бросок за соперника или сходить не в свою очередь нельзя.localStorage), поэтому можно закрыть вкладку и вернуться.scripts/online-e2e.mjs: два независимых браузера играют матч, счёт совпадает после каждого броска.decodeChallenge + validLayout).Match.mode, Match.perTurn, Match.rounds), поэтому соперник видит их ещё до входа,
а сервер проверяет каждый бросок по полю текущего раунда.src/game/duel.test.ts и сквозным scripts/duel-modes-e2e.mjs (два браузера: свои раунды, 2 броска за ход,
порядок H H G G H H G G H G …, второй раунд начинает гость).mustache: true/false) читаются как раньше; сервер проверяет новые поля через zod, а платные части ещё и по покупкам.Игру можно добавить на экран телефона: в Chrome «Установить приложение», в Safari «На экран Домой». Для этого есть манифест
(src/app/manifest.ts) и иконки 192/512 px; запускается в полноэкранном режиме без адресной строки.
src/i18n/dict/{ru,kk,en}.ts; TypeScript
проверяет, что у всех трёх языков одинаковый набор ключей. Формы множественного числа учитываются
(«1 бросок / 3 броска / 5 бросков», «1 throw / 5 throws»).Accept-Language).src/game/physics.ts — простой движок для кругов на плоскости:
1/120 с, не зависит от FPS;+ − × ÷ и Math.sqrt. По IEEE 754 они дают бит-в-бит одинаковый результат
в любом браузере и в Node.js. sin/cos/atan2 такой гарантии не дают, поэтому направление броска
задаётся вектором (vx, vy), а не углом.Благодаря детерминированности бросок полностью описывается тремя числами {sx, vx, vy}:
src/lib/verify.ts). Подделать рекорд правкой запроса
или localStorage нельзя.replayRound).Проверено тестами (npm test): одинаковый результат повторного броска, отсутствие двойного счёта,
реплей раунда, валидация ссылок и проходимость каждого уровня и испытаний дня ботом-перебором.
Сложность уровней калибровалась симуляцией «неточного игрока» со случайной ошибкой прицела и силы.
src/game/ чистый TypeScript без React: физика, правила раунда, уровни, дуэль,
испытание дня, ссылки-вызовы, советы, рендер canvas
src/components/ GameCanvas (ввод + анимация), PlayScreen, ResultModal, провайдер аккаунта
src/app/ страницы (App Router) и API-маршруты
src/lib/ Prisma, сессии, проверка попыток, рейтинги, каталог магазина, localStorage, звук
prisma/ схема и миграции
scripts/ применение миграций на Turso, e2e-проверка в браузере
User (с персонажем), Attempt (все сыгранные раунды с бросками), Purchase, CoinTx (журнал тиынов).jose) в httpOnly-cookie.src/avatar/Avatar.tsx). Сначала выбирается пол: у девочки
сразу косички, юбка и ресницы, у мальчика короткая стрижка и шорты (дальше всё можно поменять). Бесплатно настраиваются кожа, глаза,
причёска, цвет волос и усы: внешность человека не продаём. Костюмы кочевников продаются за тиыны:
шапан, камзол, қыз көйлегі, тон, батыр сауыты, а также три вида тақии (ою, киіз, зерлі), бөрік, тымақ, сәукеле с үкі, дулыға и цвета одежды.
Всё можно примерить до покупки. Персонаж виден в профиле, в шапке и в рейтинге.CoinTx с уникальным ключом, поэтому одну и ту же награду нельзя получить дважды:
повторное прохождение уровня на те же звёзды ничего не даёт, повторный запрос награды тоже.За игру несколько дней подряд даётся бонус тиынов: +5 🪙 за каждый день серии, максимум +35 🪙 в день (первый день серии бонуса
не даёт). Серия считается по попыткам, сохранённым на сервере (день по Алматы), и не рвётся, пока не закончился сегодняшний день.
Бонус выдаётся один раз в день (refKey = streak:<user>:<день>), поэтому повторные попытки его не накручивают. Серия видна
в профиле, а в окне результата показывается строка «🔥 Серия N дней: +X 🪙». Логика подсчёта вынесена в чистую функцию
computeStreak и покрыта тестами.
21 достижение с наградой в тиынах (от +10 до +150): первая победа, пороги звёзд (10 / 30 / 60 / все 84), прохождение глав, три звезды в 5
и 15 испытаниях, победа за один бросок, прохождение уровня с лужей, 100 и 500 выбитых асыков, серии 3 и 7 дней, испытание дня в 3 и
10 разных дней, первая покупка, пять уровней без пунктира, победа в онлайн-дуэли. Условия считаются на сервере по уже хранимым данным (попытки, покупки,
серия, дуэли), отдельной таблицы нет: факт получения записывается в журнал тиынов с уникальным refKey = ach:<user>:<id>, поэтому
награда выдаётся ровно один раз, даже при гонке запросов, а у старых игроков достижения засчитываются задним числом.
Новые достижения проверяются после попытки, покупки и конца дуэли и показываются в окне результата; полный список с прогрессом
есть в профиле (GET /api/achievements). Список и пороги покрыты тестами (src/lib/achievements.test.ts).
В окне результата и в профиле есть кнопка «Картинка для шаринга»: в браузере рисуется PNG 1080×1350 (формат 4:5 для Telegram и Instagram) с расстановкой испытания (с лужами и камнями), звёздами, очками и ссылкой на игру. На телефоне открывается системное окно «Поделиться» с файлом, на компьютере картинка скачивается. Рисование идёт в canvas тем же шрифтом, что и на сайте, поэтому кириллица и казахские буквы отображаются правильно, а сервер для этого не нужен.
Кнопка «Прицел» в панели управления переключает три уровня помощи (настройка хранится в браузере и не влияет на очки и звёзды, которые сервер всё равно пересчитывает по броскам): лёгкий — пунктир до первого удара (с учётом луж), средний — только короткий хвост из пяти штрихов у сақа, сложный — без пунктира (остаётся резинка и процент силы).
Бонус за сложность: за выигранный уровень на среднем прицеле +5 🪙, на сложном +10 🪙 (сложный засчитывает и средний, итого
не больше 15 🪙 за уровень). Каждая ступень на каждом уровне оплачивается один раз (refKey = aim:<user>:<уровень>:<ступень>).
Сложность называет клиент и сервер проверить её не может, поэтому награда небольшая и ограничена числом уровней; очки, звёзды и рейтинг
от сложности не зависят. Достижение «Орлиный глаз» даётся за пять уровней на сложном прицеле.
В дуэли на одном устройстве есть кнопки «Закончить» (побеждает тот, у кого больше очков, при равенстве ничья) и «Сдаться» (проигрывает тот, чей сейчас ход). Статус хода и номер раунда показываются в полосе над полем, а не поверх него.
В игре нет реальных денег и платных пакетов: тиыны зарабатываются только игрой — звёзды в испытаниях, участие и места в турнире дня, приветственный бонус. На них покупаются только косметика (костюмы, головные уборы, цвета, скины сақа, площадки) и набор «Дала аңыздары». Физика одинакова для всех скинов, а ничего из магазина не влияет на очки и рейтинг. Премиальный костюм «Алтын адам» требует ещё и 56 звёзд из 84 возможных в испытаниях: купить его только накоплением тиынов нельзя. Реального платёжного провайдера, пополнения и тестовой карты в проекте нет.
Проект проверен на базовые классы веб-атак (OWASP / PortSwigger Academy):
| Класс атаки | Как закрыто |
|---|---|
| Path traversal | Нет маршрутов, читающих файлы по пути от пользователя; единственный динамический параметр (код матча) проверяется регулярным выражением |
| Незащищённая админка / роль из запроса | Админки и ролей нет. Личность берётся только из подписанного JWT (HS256, httpOnly, sameSite=lax, secure), а не из параметров запроса |
| IDOR | Ни один API не принимает userId из запроса — всё выбирается по сессии. Матчи защищены секретом места (18 случайных байт), в ответах нет токенов и хэшей паролей; в таблице лидеров чужие id не отдаются |
| Перебор логинов | Вход выполняет bcrypt.compare всегда (даже для несуществующего логина) и отвечает одинаково: время и текст не выдают, есть ли такой пользователь |
| Перебор паролей / спам | Rate limit в базе (по IP и по логину) на вход, регистрацию, demo, пополнение, покупки и матчи; пароль от 8 символов, частые пароли запрещены |
| 2FA, SSRF, загрузка файлов, OS-инъекции | Не применимо: этих функций нет, сервер не делает запросов на адреса от пользователя, не запускает команды и не принимает файлы |
| SQL-инъекции | Только Prisma с параметризованными запросами, сырого SQL нет; весь ввод проходит валидацию zod |
| CSRF | sameSite=lax + проверка Origin/Sec-Fetch-Site для всех изменяющих запросов (src/proxy.ts) |
| Кликджекинг, XSS, утечки | CSP, X-Frame-Options: DENY, nosniff, HSTS, Referrer-Policy, Permissions-Policy; React экранирует весь пользовательский текст |
| Кража токена | Выход из аккаунта отзывает все выданные токены (sessionVersion) |
| Гонки в экономике | Покупка проверяет баланс и списывает его в одной транзакции; награды защищены уникальным refKey, дозачисление demo идемпотентно |
| Нагрузка на CPU (DoS) | Каждая попытка/бросок перепроигрывается на сервере, поэтому у attempts, sync и match/throw свои лимиты |
| Кэширование чужих данных | Все ответы /api/* идут с Cache-Control: private, no-store |
| Бесконечные тиыны | Пополнения за деньги нет: тиыны выдаёт только сервер за игру, награды защищены уникальным |
Сообщить об уязвимости: GitHub Security Advisories (см. также /.well-known/security.txt).
Общий логин demo / asyq2026 публичен намеренно, в нём нет личных данных.
npm install # также выполнит prisma generate
cp .env.example .env # DATABASE_URL="file:./dev.db", AUTH_SECRET=любая длинная строка
npx prisma migrate dev # создать локальную SQLite-базу
npm run dev # http://localhost:3000
npm test # тесты физики, правил и баланса уровней
libsql://…) и токен.DATABASE_URL, TURSO_AUTH_TOKEN, AUTH_SECRET.npm run build сначала запускает scripts/db-apply.mjs: он накатывает миграции на Turso
(prisma migrate deploy не умеет работать с libsql://) и создаёт тестовый аккаунт demo.next/font)@prisma/adapter-libsql, Turso (libSQL)jose (JWT), bcryptjs (пароли), zod (валидация)create-next-app. Готовых игровых шаблонов и физических движков не используется:
физика, рендер, уровни и звук написаны с нуля для этого проекта.refKey| Cookie и секреты | __Host- префикс, срок сессии 7 дней, сравнение токенов матча за постоянное время, предупреждение в логах, если AUTH_SECRET короче 32 символов |
| Журналы | Структурные события (auth_fail, rate_limited, server_error) без паролей и токенов; клиент видит только код ошибки |
| Зависимости | Dependabot и GitHub Actions (npm audit --omit=dev --audit-level=critical, CodeQL). npm audit без флага показывает dev-зависимость prisma CLI (mysql2/deepmerge-ts): в рантайме она не используется |