Files
goon-game/docs/LEVEL_SOLUTION_GRAPHS.md
T

295 lines
26 KiB
Markdown
Raw 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.
# Графы решений уровней 1–10
Документ фиксирует **авторский маршрут** каждого уровня текущего vertical slice. Он получен из `LevelCatalog`, моделей `LevelDefinition` и фактической логики `GameView`, затем проверен device-level ботами через production-команды движения и прыжка. `targetDeaths` ниже — целевое число жертв для результата уровня, но оно не доказывает минимальность решения.
На 18 августа 2026 года intended route всех уровней 1–10 автоматически выполнен по три раза подряд без retry masking. `EarlyLevelPlaythroughTest`, `LevelFivePlaythroughTest`, `LateLevelPlaythroughTest` и `LevelEightPlaythroughTest` управляют production `GameView` через те же флаги, что и touch-контролы. Для уровня 8 отдельно проверено восстановление после истечения таймера на дальней стороне `PIT`.
## Семантика, общая для графов
- `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`
**Статус:** доказан ботом, 3/3 последовательных прохождения без смертей.
**Ожидаемые смерти:** `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/неопределённость:** intended route и окна двух прыжков воспроизводимо подтверждены. Альтернативный проход под/мимо уступов отдельно не проверялся.
## Уровень 2 — `A Useful End`
**Статус:** доказан ботом, 3/3 последовательных прохождения с тремя телами.
**Ожидаемые смерти:** `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/неопределённость:** intended route с тремя телами воспроизводимо подтверждён. Отдельная попытка прямого zero-death прохода не подтвердила sequence break, но не является исчерпывающим negative-тестом; минимальность трёх смертей остаётся открытым follow-up.
## Уровень 3 — `Dead Weight`
**Статус:** доказан ботом, 3/3 последовательных прохождения с одной жертвой.
**Ожидаемые смерти:** `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` отдельно не перебирались.
## Уровень 4 — `The Long Lift`
**Статус:** доказан ботом, 3/3 последовательных прохождения без смертей.
**Ожидаемые смерти:** `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/неопределённость:** lift→gallery прыжок подтверждён в трёх последовательных запусках. Повторный контакт с `TOGGLE` по-прежнему выключает сигнал и может заморозить платформу; это предусмотренное состояние, доступное для restart.
## Уровень 5 — `Express Delivery`
**Статус:** доказан `LevelFivePlaythroughTest`, 3/3 последовательных прохождения.
**Ожидаемые смерти:** `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/неопределённость:** основной маршрут воспроизводимо подтверждён тремя последовательными запусками. Альтернативные решения и физическая стабильность на нескольких frame rates этим тестом не покрыты.
## Уровень 6 — `Cold Comfort`
**Статус:** доказан ботом, 3/3 последовательных прохождения с frozen stepping stone.
**Ожидаемые смерти:** `1` (`targetDeaths=1`).
**Источник:** `LevelCatalog.level6()`; `FREEZER=(690,776,100,104)`; 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` перекрывала игрока, стоящего на блоке, на 26 px и делала intended route невозможным. Высота зоны минимально исправлена до `104`; floor runner всё ещё погибает, а оба прыжка подтверждены тремя запусками.
## Уровень 7 — `Live Wire`
**Статус:** доказан ботом, 3/3 последовательных прохождения с conductive corpse.
**Ожидаемые смерти:** `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`
**Статус:** intended route и восстановление после истечения таймера доказаны production-input ботом, по три раза подряд.
**Ожидаемые смерти:** `0` (`targetDeaths=0`).
**Источник:** `LevelCatalog.level8()`; основной `TIMED=(560,810,90,70)`, `signalId="clock"`, duration `6.0`; recovery relay `TIMED=(1600,650,90,70)`, duration `3.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" на 6.0 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 без смерти
Ветка восстановления после пересечения PIT:
→ дождаться истечения "clock" на правом floor x≥1510
→ Door закрывается и подтверждает прежнюю trap-state
→ намеренно прыгнуть в высокий recovery relay x=1600, y=650
→ повторно запустить "clock" на 3.5 s
→ пройти Door и достичь goal без смерти/restart
```
**Механика и сигналы:** фронт контакта основного `TIMED` → `timedSignals["clock"]=6.0` → движение платформы по `travelX=380` и открытие Door; высокий relay выставляет тот же сигнал на `3.5 s`. Обычный бег проходит под relay, поэтому original route не активирует восстановление автоматически. Строгий grounded-checkpoint выявил нестабильное закрытие Door на `5.5 s`; добавленные `0.5 s` дают запас на проход двери, не меняя crossing или recovery.
**Bot checkpoints:** основной timer активен; игрок двумя прыжками пересекает PIT через moving platform; перед движением к relay явно подтверждены far floor `playerX≥1510`, `playerY≥779` и всё ещё открытая Door. Intended route проходит Door на первом таймере. Recovery route ждёт `allDoorsOpen=false` справа от PIT, прыжком получает `allDoorsOpen=true`, проходит Door и завершает уровень. Оба маршрута финально дают `completed=true`, `deaths=0`; каждый выполнен три раза подряд на Android 16.
**Softlock/неопределённость:** прежний blocker устранён: после истечения `"clock"` справа от PIT высокий relay гарантирует restart-free повторное открытие двери. Relay не помогает слева от PIT и не отменяет timed-platform crossing. Оба пути проверены без прямой мутации состояния, teleport, auto-complete или restart.
## Уровень 9 — `Two Good Reasons`
**Статус:** доказан ботом, 3/3 последовательных прохождения с AND из двух тел.
**Ожидаемые смерти:** `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/неопределённость:** первая смерть стабильно удерживает `"weight"`, но дверь остаётся закрытой; вторая активирует `"power"` и открывает AND-door. Цепочка подтверждена тремя запусками ровно с двумя телами.
## Уровень 10 — `The Last Procession`
**Статус:** mastery-маршрут доказан ботом, 3/3 последовательных прохождения с тремя телами.
**Ожидаемые смерти:** `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/неопределённость:** доставка saw-тела, три сохранённых тела, подъём и lift→ledge прыжок подтверждены тремя запусками. Обязательность frozen corpse всё ещё не доказана отрицательным тестом: возможный двухсмертный sequence break остаётся follow-up.
## Проверки, следующие из графов
1. Сохранять полный integrated device-regression: intended route уровней 1–10 по три раза и отдельный recovery route уровня 8.
2. Отдельно проверить предполагаемый sequence break уровня 2 и необязательность `FREEZER` на уровне 10.
3. Добавить параметризованные проверки restart из ключевых промежуточных состояний.
4. Повторить bot matrix на дополнительных frame rates и поддерживаемых версиях Android.
5. Провести ручные сессии этапа 2 и скорректировать подсказки/допуски без ослабления механик.