5ef084eca4
Дополнен точными данными из SDLPoP (сверено): кадровые таблицы loose_fram_left/right/bottom, get_loose_frame, y_loose_land, delay=11; триггеры с call-sites (check_press->make_loose_fall при стоянии на 11, frame79-сверху; do_knock на жёстком приземлении; animate каждый кадр); tile_is_floor(11)=1. Плюс подход интеграции в наш движок: общая мутабельная копия комнаты pop_map<->pop_bg + per-page запекание пустоты после падения; тонкое место — перерисовка динамического тайла в дабл-буфере. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
144 lines
9.8 KiB
Markdown
144 lines
9.8 KiB
Markdown
# Loose floors (проваливающиеся полы) — план порта
|
||
|
||
Разбор SDLPoP (seg007 loose/trob/mob, seg008 draw_loose). ПЛАН, ещё не
|
||
реализовано. Тайл в комнате 1: `[2,6] = 0x0B = tiles_11_loose`.
|
||
|
||
## 1. Хранение состояния
|
||
|
||
- **Тип тайла**: `curr_room_tiles[tilepos] & 0x1F == 11` (tiles_11_loose).
|
||
Бит `0x20` = «solid» loose (авто-падающий вариант, ур.13 — от шага НЕ
|
||
падает). После падения тайл → `0` (tiles_0_empty).
|
||
- **Модификатор** `curr_room_modif[tilepos]` = состояние анимации:
|
||
- `0` — покой (обычный loose-пол);
|
||
- `0x80..0x83` — **трясётся** (бит7); за ~4 кадра затухает обратно в 0;
|
||
- `1..11` — **обратный отсчёт до падения** (на нём что-то стоит);
|
||
достигает `loose_floor_delay = 11` → падает.
|
||
|
||
## 2. Анимация тряски (shake) — когда включается
|
||
|
||
- **Триггер = do_knock** (seg007:0FE0): на ЖЁСТКОМ приземлении в кадрах
|
||
посадки играет `SEQ_KNOCK_DOWN` → взводит `knock` → `check_knock()` →
|
||
`do_knock(room, curr_row − (knock>0))`.
|
||
- `do_knock(room, row)`: по всем колонкам ряда — если тайл loose →
|
||
`loose_make_shake()`.
|
||
- `loose_make_shake()` (seg007:0FB4): если `modif==0` (и не ур.13) →
|
||
`modif = 0x80`, `add_trob(type 1)`.
|
||
- **Отсюда кейс пользователя**: Kid падает/приземляется на `[2,4]` →
|
||
do_knock трясёт ВСЕ loose-тайлы ряда 2 → `[2,6]` трясётся. (Через
|
||
knock-смещение ряда может задеть и соседний ряд.)
|
||
- `animate_loose` (кадрово): `++modif`; при бите7 трясёт до `>=0x84` →
|
||
сброс в 0, `trob.type=-1`. `loose_shake()` играет звук
|
||
(sound 20/21/22) по таблице `loose_sound[]`.
|
||
|
||
## 3. Анимация падения (fall) — когда включается
|
||
|
||
- **Триггер = make_loose_fall(1)** (seg007:0EF6), вызывается когда:
|
||
- Kid СТОИТ на loose-тайле — `check_press()` (seg006): кадр с
|
||
FRAME_NEEDS_FLOOR над loose → make_loose_fall(1);
|
||
- зацеп/подтягивание на loose (`check_grab`, `check_jump_up`);
|
||
- пробой сверху: кадр 79 (jumphang) над loose → make_loose_fall(1);
|
||
- авто-падающие (ур.13) — `make_loose_fall(-(prandom&0x0F))`.
|
||
- `make_loose_fall(modifier)`: если НЕ solid (`tiles & 0x20 == 0`) и
|
||
`(sbyte)modif <= 0` → `modif = modifier`, `add_trob(type 0)`.
|
||
- `animate_loose`: `++modif`; когда `modif >= 11` (loose_floor_delay) →
|
||
`remove_loose()` (тайл → empty) + `add_mob()` (спавн падающего куска).
|
||
|
||
## 4. Падающий кусок (mob)
|
||
|
||
- `add_mob()` кладёт `curmob` в `mobs[]` (до 14). `do_mobs()` каждый
|
||
кадр: `move_mob()` (гравитация, y растёт) + `check_loose_fall_on_kid()`
|
||
(урон Киду/страже, если попал).
|
||
- Приземление куска → тайл под ним `curr_room_tiles[...] = tiles_14_debris`
|
||
(seg007 move_mob:1053). Т.е. **loose(11) упал → сверху empty(0), снизу
|
||
debris(14)**.
|
||
|
||
## 5. Отрисовка по статусу
|
||
|
||
- Куски тайла: `loose_fram_left[]={41,69,41,70,70,41,41,41,70,70,70,0}`,
|
||
`loose_fram_right[]={42,71,...}`, `loose_fram_bottom[]={43,73,...}`
|
||
(env-спрайты, seg008:518/596/608).
|
||
- Индекс кадра = `get_loose_frame(modifier)` (seg008): `0` = ровный
|
||
(41/42/43); `1..10` = дрожащие варианты (69–74); при бите7/большой
|
||
задержке — низкие индексы.
|
||
- **До падения**: рисуем loose с `get_loose_frame(modif)` (0 = ровно,
|
||
иначе колеблется). **После**: сверху empty, снизу debris(14) — обычная
|
||
статическая отрисовка (у нас уже есть tile 0x0E/14 debris в tile_table).
|
||
|
||
## 6. Что нужно в нашем движке (сейчас НЕТ)
|
||
|
||
Наш `pop_bg` рисует комнату СТАТИЧЕСКИ один раз. Loose-полы требуют
|
||
**динамического тайлового слоя**:
|
||
1. **Массив модификаторов** `room_modif[30]` (у нас есть `bg[30]` — можно
|
||
переиспользовать/рядом) — состояние каждого тайла.
|
||
2. **Очередь trob** (список анимируемых тайлов) + `animate_loose` пер-кадр
|
||
→ перерисовка ТОЛЬКО изменившихся тайлов (как heal-прямоугольник Kid).
|
||
3. **make_loose_fall / do_knock / loose_make_shake** — триггеры (из
|
||
физики Kid: приземление→knock, стойка на loose→fall).
|
||
4. **mob-система** (падающий кусок): минимум 1–2 mob'а, гравитация,
|
||
приземление → debris. Урон Киду (`check_loose_fall_on_kid`) — можно
|
||
Фазой 2.
|
||
5. **Перерисовка тайла**: `draw_tile(row,col)` у нас уже умеет loose
|
||
(`code==11`, `loose_fram_*` в env) — нужно вызывать его выборочно с
|
||
текущим модификатором (сейчас draw_tile берёт статический bg).
|
||
|
||
**Порядок реализации (предложение):**
|
||
- L1: room_modif[] + выборочная перерисовка тайла по модификатору
|
||
(draw_loose с get_loose_frame) — статика→динамика одного тайла.
|
||
- L2: trob-очередь + animate_loose (тряска по do_knock на приземлении).
|
||
- L3: make_loose_fall (стойка на loose) + отсчёт + remove → empty.
|
||
- L4: mob (падающий кусок → debris снизу).
|
||
- L5: урон Киду от падающего куска.
|
||
|
||
## Конкретика из SDLPoP (сверено 2026-07-18, готово к реализации)
|
||
|
||
Таблицы (seg008.c), индекс = `get_loose_frame(modif)`:
|
||
- `loose_fram_left[] = {41,69,41,70,70,41,41,41,70,70,70,0}`
|
||
- `loose_fram_right[] = {42,71,42,72,72,42,42,42,72,72,72,0}`
|
||
- `loose_fram_bottom[]= {43,73,43,74,74,43,43,43,74,74,74,0}`
|
||
- `get_loose_frame(m)`: если `(m&0x80)` (или delay>11) → `m&=0x7F; if(m>10) return 1;` → `return m;`
|
||
- `y_loose_land[] = {2,65,128,191,254}` (mob), `loose_floor_delay = 11`.
|
||
|
||
Триггеры (call-sites):
|
||
- **make_loose_fall(modifier=1)** — из `check_press()` (seg006): когда Kid
|
||
СТОИТ на тайле (FRAME_NEEDS_FLOOR, action < hang_climb / turn / bumped) и
|
||
`get_tile_at_char()==11`; ИЛИ `frame==79` (прыжок вверх) и
|
||
`get_tile_above_char()==11` (пробой сверху). `tile_is_floor(11)==1` —
|
||
Kid стоит на loose (start_fall НЕ зовётся). Тело:
|
||
`if(!(tile&0x20) && (sbyte)modif<=0){ modif=modifier; add_trob(type0); }`
|
||
- **do_knock(row)** — на ЖЁСТКОМ приземлении (SEQ_KNOCK_DOWN→check_knock,
|
||
seg003): по всем колонкам ряда `if(tile==11) loose_make_shake()`
|
||
(`if(modif==0){ modif=0x80; add_trob(type1); }`).
|
||
- **animate_loose** (каждый кадр, seg007:816): `++modif`; если `&0x80`
|
||
(тряска): `if(modif>=0x84){modif=0; trob=-1;}`; иначе (отсчёт):
|
||
`if(modif>=11){ remove_loose(tile→0); trob=-1; add_mob(); } else shake`.
|
||
|
||
## Интеграция в наш движок (roomtest) — подход
|
||
|
||
Наш движок ПЕКЁТ комнату один раз (двойной буфер: своя ОЗУ-копия на
|
||
страницу). Loose требует динамики + общего состояния pop_map↔pop_bg:
|
||
1. **Общая МУТАБЕЛЬНАЯ копия комнаты**: roomtest.c держит `uint8_t
|
||
room_fg[30]` (копия room1_fg) и передаёт ОДИН указатель и в
|
||
`pop_room_draw`, и в `pop_map_set` → мутации loose видны обоим.
|
||
(Сейчас g_fg — `const`; сделать неконстантным.)
|
||
2. **Состояние**: `uint8_t pop_loose_modif[30]` (0 / 0x80.. / 1..11).
|
||
3. **Модель** (pop_map): `check_press()` в `pop_phys_tick` (make_loose_fall
|
||
при стоянии на 11), `animate` каждый кадр.
|
||
4. **Перерисовка** (pop_bg, на back-странице каждый кадр):
|
||
- тряска/отсчёт (код всё ещё 11): `gfx_heal(tile rect)` (вернуть
|
||
печёный фон) + нарисовать loose-кадр (left/right/bottom по
|
||
get_loose_frame) банком SPRITE;
|
||
- падение (11→0): mutate `g_fg[pos]=0` + «запечь пустоту» на ОБЕИХ
|
||
страницах (bake-счётчик 2: чёрный bar + draw_tile empty банком NORMAL
|
||
на текущей странице 2 кадра подряд) → дальше heal показывает пусто.
|
||
5. **L4 mob**: падающий кусок → debris(14) снизу (y_loose_land); пока
|
||
отложено — упавший loose = пусто. **L5** урон — позже.
|
||
|
||
Риск: перерисовка динамического тайла в двойном буфере (per-page heal +
|
||
bake) — единственное тонкое место; остальное — прямой порт логики выше.
|
||
|
||
## Связанные
|
||
|
||
Триггеры завязаны на физику Kid ([[pop_hang_state]] check_press/check_grab,
|
||
приземление land/SEQ_KNOCK_DOWN). Отрисовка — [[pop_fore_layer]] /
|
||
[[pop_background_strategy]] (draw_tile уже знает loose_fram_*).
|