From 8ee8cff7738122630062102e6c80f4763f99f2d9 Mon Sep 17 00:00:00 2001 From: "E.Gavrilov" Date: Tue, 18 Aug 2026 01:01:36 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=BF=D0=B8=D1=81=D0=B0=D1=82=D1=8C=20?= =?UTF-8?q?=D0=B3=D1=80=D0=B0=D1=84=D1=8B=20=D1=80=D0=B5=D1=88=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B9=20=D0=B4=D0=B5=D1=81=D1=8F=D1=82=D0=B8=20=D1=83?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=BD=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + docs/LEVEL_SOLUTION_GRAPHS.md | 287 ++++++++++++++++++++++++++++++++++ 2 files changed, 288 insertions(+) create mode 100644 docs/LEVEL_SOLUTION_GRAPHS.md diff --git a/README.md b/README.md index 6f34bd5..e40d92a 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Project documents: - [`docs/DEVELOPMENT_PLAN.md`](docs/DEVELOPMENT_PLAN.md) — approved GDD + TDD + production roadmap; - [`docs/CURRENT_ROADMAP.md`](docs/CURRENT_ROADMAP.md) — execution roadmap for the existing vertical slice; +- [`docs/LEVEL_SOLUTION_GRAPHS.md`](docs/LEVEL_SOLUTION_GRAPHS.md) — source-backed intended solution graph for every current level; - [`docs/BEADS_WORKFLOW.md`](docs/BEADS_WORKFLOW.md) — local backlog structure and daily Beads commands. ## Playable content diff --git a/docs/LEVEL_SOLUTION_GRAPHS.md b/docs/LEVEL_SOLUTION_GRAPHS.md new file mode 100644 index 0000000..1cf544f --- /dev/null +++ b/docs/LEVEL_SOLUTION_GRAPHS.md @@ -0,0 +1,287 @@ +# Графы решений уровней 1–10 + +Документ фиксирует **предполагаемый авторский маршрут** каждого уровня текущего vertical slice. Он получен из `LevelCatalog`, моделей `LevelDefinition` и фактической логики `GameView`, а не из ручного прохождения. `targetDeaths` ниже — целевое число жертв для результата уровня, но оно не доказывает минимальность решения. + +На 18 августа 2026 года автоматически доказан только уровень 5: `LevelFivePlaythroughTest` управляет production `GameView` через те же флаги движения и прыжка, что и touch-контролы. Все остальные графы имеют статус «предполагаемый, не доказан ботом» до появления воспроизводимого `connectedDebugAndroidTest`. + +## Семантика, общая для графов + +- `PRESSURE` активен, пока с его `box` пересекается достаточная масса живого игрока и/или тел (`requiredMass`). Сигнал не защёлкивается. +- `TOGGLE` меняет значение сигнала только на фронте контакта; повторный вход переключает его обратно. +- `TIMED` запускает таймер только на фронте контакта; удержание на переключателе не перезапускает истёкший таймер. +- `Door.requiredSignals` использует AND-семантику: дверь открыта, только когда активны все указанные сигналы. +- Управляемая движущаяся платформа накапливает `motionTime` только при активном `controlId`; при потере сигнала она замирает в текущей фазе `GameMath.pingPong`. +- `SPIKES` оставляет закреплённое тело над шипами; `SAW` передаёт телу `impulseX`/`impulseY`; `FREEZER` создаёт неподвижный блок `104×104`; `ELECTRIC` оставляет закреплённое проводящее тело внутри опасности; `PIT` тела не оставляет. +- Тела являются твёрдыми опорами для игрока. Смерть сохраняет тела и состояния уровня, respawn возвращает нового игрока в `spawnX`/`spawnY`; restart очищает состояние. +- Пересечение живого игрока с `goal` переводит сцену в `COMPLETE`. + +## Уровень 1 — `The Volunteer` + +**Статус:** предполагаемый маршрут, ботом не доказан. +**Ожидаемые смерти:** `0` (`targetDeaths=0`). +**Источник:** `LevelCatalog.level1()`; уступы `(640,760,230)` и `(1060,690,250)`; `goal=(1690,770,86,110)`. + +### Solution graph 1 + +```text +Goal расположен справа от двух перекрывающих путь уступов +→ разогнаться вправо и запрыгнуть на уступ x=640..870 +→ сохранить движение и перепрыгнуть на более высокий уступ x=1060..1310 +→ спуститься на сплошной floor(0,1920) +→ пересечь goal x=1690..1776 +→ COMPLETE без смерти +``` + +**Механика и сигналы:** только движение, прыжок и статические `Platform`; сигналов и дверей нет. + +**Bot checkpoints:** `playerX>640` при `playerY≤660` → `playerX>1060` при `playerY≤590` → `playerX>1310` → `completed=true`, `deaths=0`. + +**Softlock/неопределённость:** blocker-softlock по данным модели не ожидается; точные окна двух прыжков и возможность альтернативного прохода под/мимо уступов автоматически не проверены. + +## Уровень 2 — `A Useful End` + +**Статус:** предполагаемый маршрут, ботом не доказан. +**Ожидаемые смерти:** `3` (`targetDeaths=3`). +**Источник:** `LevelCatalog.level2()`; `SPIKES=(610,850,260,30)`; статичный уступ `(1180,740,240)`; `goal=(1830,770,86,110)`. + +### Solution graph 2 + +```text +Spike lane x=610..870 перекрывает наземный маршрут +→ первый доброволец погибает на ближнем краю SPIKES +→ закреплённое тело становится первой твёрдой опорой +→ следующие добровольцы продвигаются прыжками и оставляют ещё два тела +→ цепочка из трёх тел образует предусмотренный путь через lane +→ следующий доброволец пересекает тела, возвращается на floor +→ проходит уступ x=1180..1420 и достигает goal +→ COMPLETE с целевыми тремя смертями +``` + +**Механика и сигналы:** `SPIKES` закрепляет каждое тело на `hazard.box.y-corpse.height`; двери и сигналы отсутствуют. + +**Bot checkpoints:** после каждой предусмотренной жертвы `deaths/corpseCount=1/1`, `2/2`, `3/3`; затем живой игрок `playerX>870`; финально `completed=true`, `deaths=3`. + +**Softlock/неопределённость:** тела становятся горизонтальными коллайдерами и требуют прыжка уже после первой смерти. При текущих `RUN_SPEED`/`JUMP_SPEED` ширина lane может допускать перепрыгивание с меньшим числом смертей; это возможный sequence break, а не подтверждённый маршрут. Нужны бот-проверка intended route и отдельный negative/alternative-solution тест. + +## Уровень 3 — `Dead Weight` + +**Статус:** предполагаемый маршрут, ботом не доказан. +**Ожидаемые смерти:** `1` (`targetDeaths=1`). +**Источник:** `LevelCatalog.level3()`; `SAW=(565,760,92,92)`, impulse `(0,-150)`; `PRESSURE=(540,860,150,20)`, `signalId="gate"`; дверь `(1110,570,78,310)` требует `"gate"`. + +### Solution graph 3 + +```text +Door закрывает путь к goal +→ для Door требуется активный signal "gate" +→ PRESSURE удерживает "gate" только при постоянной массе +→ первый доброволец входит в SAW над зоной плиты +→ тело с нулевым horizontal impulse оседает на PRESSURE +→ "gate" становится active и Door открывается +→ respawn следующего добровольца +→ перепрыгнуть SAW/тело, пройти открытую Door +→ пересечь goal и получить COMPLETE +``` + +**Механика и сигналы:** checkpoint цепочки — `corpseCount=1` → `switchContact(PRESSURE)=true` → `signalActive("gate")=true` → `doorOpen=true`. + +**Bot checkpoints:** `deaths=1`, `corpseCount=1`; после respawn `playerAlive=true`; требуется расширить snapshot состоянием `"gate"` либо использовать `allDoorsOpen=true`; затем `playerX>1188` и `completed=true`. + +**Softlock/неопределённость:** успех зависит от фактической точки первого пересечения игрока с `SAW`, потому что тело сохраняет `player.x`; достаточное перекрытие с плитой ботом ещё не доказано. Если тело не удержит плиту, продолжение невозможно без дополнительной жертвы или restart. + +## Уровень 4 — `The Long Lift` + +**Статус:** предполагаемый маршрут, ботом не доказан. +**Ожидаемые смерти:** `0` (`targetDeaths=0`). +**Источник:** `LevelCatalog.level4()`; `TOGGLE=(520,820,72,60)`, `signalId="lift"`; moving platform `(1040,790,260,34)`, travel `(0,-390)`, period `5.2`, `controlId="lift"`; `PIT=(1500,880,320,200)`; верхний уступ `(1660,500,540)`; `goal=(1960,390,86,110)`. + +### Solution graph 4 + +```text +Goal находится на верхней галерее за PIT +→ обычный прыжок с floor не даёт требуемой высоты +→ один раз войти в TOGGLE и включить signal "lift" +→ активный "lift" запускает вертикальный цикл moving platform +→ запрыгнуть на platform x=1040..1300 и подняться +→ в верхней фазе прыгнуть вправо через край PIT на ledge x=1660..2200 +→ пройти по галерее до goal +→ COMPLETE без смерти +``` + +**Механика и сигналы:** фронт контакта `TOGGLE` → `toggleSignals["lift"]=true` → растёт `motionTime` платформы → `travelY*pingPong` поднимает её максимум на `390`. + +**Bot checkpoints:** `signal "lift"=true`; координата платформы уменьшается от `y=790`; игрок получает `supportIndex` движущейся платформы; затем `playerX≥1660`, `playerY≤400`; финально `completed=true`, `deaths=0`. Для этого snapshot нужно расширить сигналами, `supportIndex` и состоянием платформы. + +**Softlock/неопределённость:** повторный контакт с `TOGGLE` выключит сигнал и заморозит платформу в текущей фазе. Прыжок от правого края платформы к началу галереи имеет небольшой геометрический запас и не подтверждён ботом; это риск точного тайминга/непроходимости. + +## Уровень 5 — `Express Delivery` + +**Статус:** автоматически доказан `LevelFivePlaythroughTest` на production `GameView`. +**Ожидаемые смерти:** `1` (`targetDeaths=1`); acceptance-тест требует минимум одну. +**Источник:** `LevelCatalog.level5()`; `SAW=(570,760,96,96)`, impulse `(500,-700)`; уступ `(850,800,330)`; `PRESSURE=(930,780,160,20)`, `signalId="gate"`; дверь `(1390,560,82,320)` требует `"gate"`; `goal=(2080,770,86,110)`. + +### Solution graph 5 + +```text +Door закрывает путь к goal +→ для Door требуется signal "gate" +→ удалённую PRESSURE нужно удерживать постоянной массой +→ первый доброволец идёт вправо в SAW +→ SAW убивает и запускает тело impulse (500,-700) +→ тело приземляется на уступе/плите x=930..1090 +→ PRESSURE активирует "gate", Door открывается +→ следующий доброволец respawn-ится +→ прыжками пересекает SAW и зону доставленного тела +→ проходит открытую Door и достигает goal +→ COMPLETE +``` + +**Механика и сигналы:** `SAW` создаёт динамическое тело → контакт тела с `PRESSURE` → `signalActive("gate")=true` → `allDoorsOpen=true`. + +**Bot checkpoints:** реализованы в `LevelPlaytestBot.proveLevelFive`: первая смерть; `corpseCount≥1`; `allDoorsOpen=true`; прыжок после `playerX≥390`, дополнительный прыжок после `playerX≥750`; затем `completed=true`. Тест проверяет `completed`, смерть, сохранённое тело и факт открытия двери. + +**Softlock/неопределённость:** основной маршрут воспроизводимо подтверждён одним device-level acceptance-тестом, но пока не выполняется требование roadmap о трёх последовательных прогонах. Альтернативные решения, restart и физическая стабильность на нескольких frame rates этим тестом не покрыты. + +## Уровень 6 — `Cold Comfort` + +**Статус:** предполагаемый маршрут, ботом не доказан. +**Ожидаемые смерти:** `1` (`targetDeaths=1`). +**Источник:** `LevelCatalog.level6()`; `FREEZER=(690,750,100,130)`; floor заканчивается на `x=1120`; уступы `(810,590,520)` и `(1430,610,570)`; `goal=(1880,500,86,110)`. + +### Solution graph 6 + +```text +Высокий уступ x=810..1330 недоступен с уровня floor обычным прыжком +→ первый доброволец входит в FREEZER +→ смерть создаёт неподвижный frozen corpse block 104×104 у x=688 +→ следующий доброволец respawn-ится +→ запрыгивает на frozen block как на промежуточную ступень +→ с блока прыгает на высокий уступ x=810..1330 +→ пересекает разрыв до уступа x=1430..2000 +→ достигает goal на втором уступе +→ COMPLETE с одной смертью +``` + +**Механика и сигналы:** `FREEZER` принудительно ставит `frozen=true`, размеры `104×104`, позицию `(source.x-2, 880-height)`; тело не обновляется физикой и остаётся твёрдой ступенью. Сигналов нет. + +**Bot checkpoints:** `deaths=1`, `corpseCount=1`; после respawn игрок стоит на блоке около `playerY=676`; затем на первом уступе `playerY=490`, `playerX≥810`; на втором `playerY=510`, `playerX≥1430`; финально `completed=true`. + +**Softlock/неопределённость:** маршрут зависит от возможности запрыгнуть на блок рядом с активным `FREEZER`, а затем набрать высоту до уступа; оба перехода ботом не проверены. Frozen block фиксирован детерминированно, поэтому физический дрейф тела не ожидается. + +## Уровень 7 — `Live Wire` + +**Статус:** предполагаемый маршрут, ботом не доказан. +**Ожидаемые смерти:** `1` (`targetDeaths=1`). +**Источник:** `LevelCatalog.level7()`; `ELECTRIC=(650,780,110,100)`, `relayId="power"`; дверь `(1130,560,84,320)` требует `"power"`; `goal=(1930,770,86,110)`. + +### Solution graph 7 + +```text +Door закрывает путь к goal +→ Door требует signal "power" +→ "power" активен только от conductive corpse внутри ELECTRIC relay +→ первый доброволец входит в ELECTRIC +→ смерть оставляет закреплённое проводящее тело внутри hazard.box +→ relay "power" становится active и Door открывается +→ следующий доброволец respawn-ится +→ перепрыгивает ELECTRIC/тело и проходит открытую Door +→ достигает goal +→ COMPLETE с одной смертью +``` + +**Механика и сигналы:** `ELECTRIC` выставляет `conductive=true`, центрирует тело внутри hazard и закрепляет его; `signalActive("power")` требует пересечения этого тела с relay hazard. + +**Bot checkpoints:** `deaths=1`, `corpseCount=1`; `allDoorsOpen=true` после появления conductive corpse; после respawn `playerX>1214`; финально `completed=true`, `deaths=1`. Для прямой проверки механики snapshot должен отдельно сообщать conductive state или активный `"power"`. + +**Softlock/неопределённость:** позиция relay-тела задаётся детерминированно, поэтому потеря сигнала не ожидается. Не доказано, что следующий игрок стабильно перепрыгивает совмещённые hazard/body без дополнительной смерти. + +## Уровень 8 — `Borrowed Time` + +**Статус:** предполагаемый маршрут, ботом не доказан. +**Ожидаемые смерти:** `0` (`targetDeaths=0`). +**Источник:** `LevelCatalog.level8()`; `TIMED=(560,810,90,70)`, `signalId="clock"`, duration `5.5`; moving platform `(990,770,240,34)`, travel `(380,0)`, period `4.0`, `controlId="clock"`; `PIT=(900,880,610,200)`; дверь `(1770,560,82,320)` требует `"clock"`; `goal=(2220,600,86,110)`. + +### Solution graph 8 + +```text +PIT разрывает floor, а Door закрывает дальнейший путь +→ войти в TIMED и запустить signal "clock" на 5.5 s +→ "clock" одновременно запускает horizontal moving platform и открывает Door +→ без задержки добежать до края floor x=900 +→ прыгнуть на moving platform и пересечь PIT +→ сойти на правый floor x≥1510 +→ пройти Door x=1770..1852 до истечения "clock" +→ запрыгнуть на ledge x=1980..2450 +→ пересечь goal и получить COMPLETE без смерти +``` + +**Механика и сигналы:** фронт контакта `TIMED` → `timedSignals["clock"]=5.5` → движение платформы по `travelX=380` и открытие Door; таймер уменьшается каждый update. + +**Bot checkpoints:** `"clock"` active с remaining time; platform `x>990`; игрок получает её `supportIndex`; затем `playerX≥1510`; Door остаётся open при `playerX>1852`; финально `completed=true`, `deaths=0`. Требуется добавить в snapshot remaining time и координаты платформы. + +**Softlock/неопределённость:** если `"clock"` истечёт после пересечения PIT, Door закроется, платформа замрёт, а переключатель останется на левой стороне; это потенциальный blocker-softlock до restart. Достаточность 5.5 s и синхронизация прыжка на платформу ботом не доказаны. + +## Уровень 9 — `Two Good Reasons` + +**Статус:** предполагаемый маршрут, ботом не доказан. +**Ожидаемые смерти:** `2` (`targetDeaths=2`). +**Источник:** `LevelCatalog.level9()`; `SPIKES=(520,850,130,30)`; `PRESSURE=(510,842,160,38)`, `signalId="weight"`; `ELECTRIC=(910,780,110,100)`, `relayId="power"`; дверь `(1510,540,86,340)` требует `"weight"` и `"power"`; `goal=(2310,770,86,110)`. + +### Solution graph 9 + +```text +Final Door использует AND из "weight" и "power" +→ первый доброволец погибает на SPIKES внутри зоны PRESSURE +→ закреплённое spike-тело удерживает signal "weight" +→ следующий доброволец пересекает spike-тело и погибает в ELECTRIC +→ закреплённое conductive body удерживает relay signal "power" +→ "weight" AND "power" одновременно active +→ Door открывается +→ третий доброволец respawn-ится +→ перепрыгивает обе опасности, проходит открытую Door +→ достигает goal и получает COMPLETE +``` + +**Механика и сигналы:** spike corpse → `switchContact(PRESSURE)` → `"weight"`; conductive corpse в relay → `"power"`; `doorOpen` проверяет оба `requiredSignals`. + +**Bot checkpoints:** после первой смерти `deaths/corpses=1/1`, `"weight"=true`, Door ещё closed; после второй `2/2`, `"power"=true`, `allDoorsOpen=true`; после respawn `playerX>1596`; финально `completed=true`, `deaths=2`. Snapshot нужно расширить поимёнными сигналами, чтобы доказать AND, а не только открытие двери. + +**Softlock/неопределённость:** первое тело сохраняет `player.x` в момент контакта со `SPIKES`; нужно подтвердить, что его пересечение с узкой общей зоной PRESSURE стабильно. Если `"weight"` не удерживается, дополнительная spike-смерть может изменить счётчик, но не гарантирует предусмотренное решение. + +## Уровень 10 — `The Last Procession` + +**Статус:** предполагаемый mastery-маршрут, ботом не доказан. +**Ожидаемые смерти:** `3` (`targetDeaths=3`). +**Источник:** `LevelCatalog.level10()`; `SAW=(520,760,96,96)`, impulse `(700,-240)`; `PRESSURE=(860,860,160,20)`, `signalId="lift"`; `FREEZER=(1110,750,100,130)`; `ELECTRIC=(1510,780,110,100)`, `relayId="power"`; moving platform `(1840,790,250,34)`, travel `(0,-410)`, period `5.0`, `controlId="lift"`; `PIT=(1880,880,260,200)`; дверь `(2310,310,82,570)` требует `"power"`; верхний уступ `(2420,440,680)`; `goal=(2880,330,86,110)`. + +### Solution graph 10 + +```text +Goal расположен на верхнем уступе за PIT и закрытой высокой Door +→ первый доброволец входит в SAW +→ impulse (700,-240) доставляет тело на PRESSURE x=860..1020 +→ тело удерживает signal "lift" и запускает вертикальную moving platform +→ следующий доброволец погибает в FREEZER +→ frozen corpse становится стабильной ступенью для повторного прохода этой зоны +→ следующий доброволец погибает в ELECTRIC +→ conductive corpse удерживает relay signal "power" и открывает Door +→ четвёртый доброволец respawn-ится и пересекает сохранённые тела/опасности +→ садится на активную platform у x=1840..2090 +→ поднимается над PIT и в верхней фазе прыгает вправо через открытую Door +→ приземляется на ledge x=2420..3100 +→ достигает goal и получает COMPLETE с тремя смертями +``` + +**Механика и сигналы:** launched saw corpse → `"lift"` → vertical platform; frozen corpse → твёрдая ступень; conductive corpse → `"power"` → Door open; platform и Door используют независимые постоянные сигналы от сохранённых тел. + +**Bot checkpoints:** после SAW `deaths=1`, `"lift"=true`, platform `y<790`; после FREEZER `deaths=2` и наличие frozen corpse; после ELECTRIC `deaths=3`, `"power"=true`, `allDoorsOpen=true`; затем игрок на moving-platform `supportIndex`, в верхней фазе `playerY≤280`; после прыжка `playerX≥2420`, `playerY≤340`; финально `completed=true`. Snapshot должен различать типы тел, оба сигнала, platform position/support. + +**Softlock/неопределённость:** обязательность frozen corpse не следует однозначно из текущей геометрии — `FREEZER` потенциально можно перепрыгнуть, сохранив только две необходимые сигнальные жертвы. Доставка saw-тела на плиту зависит от динамического торможения, а прыжок с платформы к ledge имеет небольшой горизонтальный запас. Это два sequence-break/проходимость follow-up, которые нельзя считать решёнными до acceptance-бота. + +## Проверки, следующие из графов + +1. Обобщить `AutomationSnapshot`: поимённые сигналы, состояния/координаты платформ, `supportIndex`, типы и флаги тел. +2. Для уровней 1–4 и 6–10 реализовать production-input acceptance-ботов; каждый intended route выполнить три раза подряд. +3. Отдельно проверить предполагаемые sequence break уровня 2 и необязательность `FREEZER` на уровне 10. +4. Проверить blocker-softlock уровня 8 после истечения `"clock"` справа от `PIT`. +5. Проверить узкие/динамические места: тело на плите уровней 3 и 9, прыжок lift→ledge уровней 4 и 10.