Files
fool-cardgame/docs/PLAN.md
T
ftcandClaude Opus 5 7fbe4c324d Игра запускается и играется: три рантайм-бага и адаптивная вёрстка
Проверено вживую в Chrome: лобби -> матч -> бот ходит -> результат ->
статистика в лобби. Полный матч отыгран, оба исхода видел.

Баги, которые собирались и типизировались, но падали только в рантайме:

1. Canvas не менял размер под экран. Своего cc.Widget у него не было,
   а alignCanvasWithScreen правит только камеру. Canvas оставался
   1280x720, и все растянутые по нему дети — тоже.
2. [...map.values()] превращается сборкой в [].concat(map.values()),
   а concat не разворачивает итератор, а кладёт его одним элементом.
   Отсюда "view.cancelAnimations is not a function" на первой же уборке
   карт. Теперь Array.from.
3. Рубашка козыря рисовалась поверх колоды, а не под ней.

Вёрстка переделана под требование UI.md (4:3 .. 21:9):
- fitWidth даёт постоянную ширину, но не высоту: на 4:3 видно только
  ±720 юнитов. Кнопка "Leave" на y=840 и низ руки на -860 были за
  экраном именно там, где это важнее всего
- рука и панель действий считаются от нижней границы экрана,
  "Leave" прибит виджетом к левому верхнему углу
- стол — один ряд до шести карт вместо двух рядов по три: экономит
  ~215 юнитов высоты, которых на 4:3 нет. Ряд центрируется по тем
  картам, что реально на столе
- затемнение под окном результата рисуется непрозрачным спрайтом:
  тонировать и без того полупрозрачный спрайт почти ничего не даёт

Инструменты:
- npm run serve — отдаёт сборку и роут /_aspects: игра в трёх
  фиксированных iframe-ах 4:3 / 16:9 / 21:9. Движок подгоняет себя
  под окно, поэтому iframe — единственный простой способ увидеть
  вёрстку в форме, которой окно не имеет
- npm run check:spreads — ловит спред неитерируемого в массив
- npm run check — чистота core, спреды, сцены, типы, тесты

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 12:41:47 +03:00

5.8 KiB
Raw Blame History

Development plan

Living document. Updated as work progresses — each milestone is committed to master together with the update to this file.

Legend: [ ] not started · [~] in progress · [x] done

Source specs this plan implements: GAMEPLAY.md, ARCHITECTURE.md, LOBBY.md, UI.md, ART.md. Autonomous choices are logged in DECISIONS.md; the resulting structure is described in TECHNICAL.md.

Iteration 1 scope

Single-player match of podkidnoy durak against a bot, 1v1, no transferring (perevod). Lobby → match → result → lobby. Savegame framework in place with a statistics section only (no currency, no levels yet). Portrait mobile UI, targets: Web, Android, iOS.

Guiding principle

The rules live in a pure TypeScript core with no cc imports, driven as a deterministic state machine. That makes the whole game logic testable headlessly with npm test — no editor, no device — which is what allows unattended progress. The Cocos layer only renders state and turns taps into actions.

Milestones

M0 — Foundation [x]

  • Read specs, confirm open questions with the user
  • docs/PLAN.md, docs/TECHNICAL.md, docs/DECISIONS.md
  • Asset folder skeleton under assets/
  • Project settings: design resolution 1080×1920, fit-width
  • Headless test harness (vitest) wired to assets/scripts/core, plus a core purity guard and a strict typecheck

M1 — Rules core [x]

  • Seeded RNG and shuffle
  • Suit, Rank, Card, 36-card deck
  • GameState: hands, deck, trump, table slots, discard, phase, turn order
  • First-attacker selection per GAMEPLAY.md (lowest trump → lowest card → random)
  • Legal action generation: Attack, Defend, Take, Pass
  • Turn resolution: beat-all / take, discard, draw order, player elimination
  • End conditions and loser ("fool") determination
  • Unit tests covering every rule clause, including the add-card limits

M2 — Bot and headless match runner [x]

  • PlayerView projection — a bot sees only what a player legitimately sees
  • BasicBot exactly per GAMEPLAY.md "Basic bot"
  • MatchRunner driving a full headless match
  • Soak test: thousands of seeded self-play matches finish with no illegal state

M3 — Savegame module [x]

  • ISaveSection contract, SaveManager registry, JSON keyed by section name
  • Storage backend over sys.localStorage (works on web and native)
  • StatsSection: games played / won / lost, current and best win streak
  • Versioning + tolerant load (unknown or missing sections do not break a load)
  • Tests for save/load round-trip and corrupted-payload recovery

M4 — Art pipeline [x]

  • Pick the 36 needed faces from local/, downscale to the 1080-wide budget
  • Author the missing pieces: card back, table background, UI panels/buttons
  • Import into assets/art/, configure sprite frames and atlasing
  • Remove consumed source art from local/, record attribution/licence

M5 — Scenes and bootstrap [x]

  • loader scene: module initialisation, then transition to lobby
  • lobby scene: New Game button, statistics readout
  • Canvas / resolution policy and a reusable safe-area-aware layout root

M6 — Game screen [x]

  • Card prefab, face/back flipping, sizing
  • Hand layout (fan), table layout (attack/defence pairs), deck and trump display
  • Deal, attack, defend, take and discard animations driven by core events
  • Tap/drag input producing core actions; illegal moves visibly rejected
  • Bot acts with human-readable pacing

M7 — Match flow [x]

  • Result overlay (win / lose), stats written through the savegame module
  • Return to lobby; start another match without a scene leak
  • Leave-match button, which ends the match as a loss

M8 — Responsive and touch pass [x]

  • Fit-width canvas, safe-area root, widget-stretched background
  • Every interactive element at or above the 120-unit minimum (buttons 330×140 and 560×150; hand cards expose ~128 units each)
  • Hand and action bar follow the bottom edge; Leave pinned to the top-left
  • Seen running at 4:3, 16:9 and 21:9 — npm run serve then /_aspects

M9 — Build verification and hand-off [x]

  • web-mobile build completes clean; command recorded in TECHNICAL.md
  • Card art reaches the build and packs into a single 2048² atlas
  • Every component reference in the scene verified populated
  • A full match played in a browser: deal, attack, defend, take, discard, redraw, result overlay, statistics written and shown back in the lobby
  • Android/iOS texture settings written up in TECHNICAL.md (they have to be applied once in the Inspector; they live in the .meta files)
  • README

Known follow-ups

  • The default skybox cubemap (~1.1 MB) still reaches the build. skybox.enabled is off in both scenes, but the scene keeps its _envmapHDR reference and the builder follows it. The editor tool server cannot write a null reference; clearing the field in the Inspector by hand would do it.
  • Nothing verifies the presentation layer automatically. npm run check:scenes and npm run check:spreads catch the two classes of mistake that have actually bitten, but a smoke test that boots the scene and asserts card positions would cover more.
  • At 21:9 there is a lot of empty felt between the table and the hand. It reads as roomy rather than broken, but the table could drift downwards on very tall screens.

Deliberately out of scope for iteration 1

Multiplayer or networking, more than two players at the table (the core is written for N players, but only 1v1 is exercised), transferring (perevod), smarter bots, currency, levels, shop, sound design, localisation.