Проверено вживую в 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>
5.8 KiB
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 acorepurity guard and a strict typecheck
M1 — Rules core [x]
- Seeded RNG and shuffle
Suit,Rank,Card, 36-card deckGameState: 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]
PlayerViewprojection — a bot sees only what a player legitimately seesBasicBotexactly per GAMEPLAY.md "Basic bot"MatchRunnerdriving a full headless match- Soak test: thousands of seeded self-play matches finish with no illegal state
M3 — Savegame module [x]
ISaveSectioncontract,SaveManagerregistry, 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]
loaderscene: module initialisation, then transition tolobbylobbyscene: New Game button, statistics readout- Canvas / resolution policy and a reusable safe-area-aware layout root
M6 — Game screen [x]
Cardprefab, 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 servethen/_aspects
M9 — Build verification and hand-off [x]
web-mobilebuild 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
.metafiles) - README
Known follow-ups
- The default skybox cubemap (~1.1 MB) still reaches the build.
skybox.enabledis off in both scenes, but the scene keeps its_envmapHDRreference 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:scenesandnpm run check:spreadscatch 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.