Files
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

119 lines
5.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](GAMEPLAY.md),
[ARCHITECTURE.md](ARCHITECTURE.md), [LOBBY.md](LOBBY.md), [UI.md](UI.md),
[ART.md](ART.md). Autonomous choices are logged in [DECISIONS.md](DECISIONS.md);
the resulting structure is described in [TECHNICAL.md](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]`
- [x] Read specs, confirm open questions with the user
- [x] `docs/PLAN.md`, `docs/TECHNICAL.md`, `docs/DECISIONS.md`
- [x] Asset folder skeleton under `assets/`
- [x] Project settings: design resolution 1080×1920, fit-width
- [x] Headless test harness (vitest) wired to `assets/scripts/core`, plus a
`core` purity guard and a strict typecheck
### M1 — Rules core `[x]`
- [x] Seeded RNG and shuffle
- [x] `Suit`, `Rank`, `Card`, 36-card deck
- [x] `GameState`: hands, deck, trump, table slots, discard, phase, turn order
- [x] First-attacker selection per GAMEPLAY.md (lowest trump → lowest card → random)
- [x] Legal action generation: `Attack`, `Defend`, `Take`, `Pass`
- [x] Turn resolution: beat-all / take, discard, draw order, player elimination
- [x] End conditions and loser ("fool") determination
- [x] Unit tests covering every rule clause, including the add-card limits
### M2 — Bot and headless match runner `[x]`
- [x] `PlayerView` projection — a bot sees only what a player legitimately sees
- [x] `BasicBot` exactly per GAMEPLAY.md "Basic bot"
- [x] `MatchRunner` driving a full headless match
- [x] Soak test: thousands of seeded self-play matches finish with no illegal state
### M3 — Savegame module `[x]`
- [x] `ISaveSection` contract, `SaveManager` registry, JSON keyed by section name
- [x] Storage backend over `sys.localStorage` (works on web and native)
- [x] `StatsSection`: games played / won / lost, current and best win streak
- [x] Versioning + tolerant load (unknown or missing sections do not break a load)
- [x] Tests for save/load round-trip and corrupted-payload recovery
### M4 — Art pipeline `[x]`
- [x] Pick the 36 needed faces from `local/`, downscale to the 1080-wide budget
- [x] Author the missing pieces: card back, table background, UI panels/buttons
- [x] Import into `assets/art/`, configure sprite frames and atlasing
- [x] Remove consumed source art from `local/`, record attribution/licence
### M5 — Scenes and bootstrap `[x]`
- [x] `loader` scene: module initialisation, then transition to `lobby`
- [x] `lobby` scene: New Game button, statistics readout
- [x] Canvas / resolution policy and a reusable safe-area-aware layout root
### M6 — Game screen `[x]`
- [x] `Card` prefab, face/back flipping, sizing
- [x] Hand layout (fan), table layout (attack/defence pairs), deck and trump display
- [x] Deal, attack, defend, take and discard animations driven by core events
- [x] Tap/drag input producing core actions; illegal moves visibly rejected
- [x] Bot acts with human-readable pacing
### M7 — Match flow `[x]`
- [x] Result overlay (win / lose), stats written through the savegame module
- [x] Return to lobby; start another match without a scene leak
- [x] Leave-match button, which ends the match as a loss
### M8 — Responsive and touch pass `[x]`
- [x] Fit-width canvas, safe-area root, widget-stretched background
- [x] Every interactive element at or above the 120-unit minimum
(buttons 330×140 and 560×150; hand cards expose ~128 units each)
- [x] Hand and action bar follow the bottom edge; Leave pinned to the top-left
- [x] Seen running at 4:3, 16:9 and 21:9 — `npm run serve` then `/_aspects`
### M9 — Build verification and hand-off `[x]`
- [x] `web-mobile` build completes clean; command recorded in TECHNICAL.md
- [x] Card art reaches the build and packs into a single 2048² atlas
- [x] Every component reference in the scene verified populated
- [x] A full match played in a browser: deal, attack, defend, take, discard,
redraw, result overlay, statistics written and shown back in the lobby
- [x] Android/iOS texture settings written up in TECHNICAL.md (they have to
be applied once in the Inspector; they live in the `.meta` files)
- [x] 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.