Дополнен точными данными из 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>
9.8 KiB
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)).
- Kid СТОИТ на loose-тайле —
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-полы требуют
динамического тайлового слоя:
- Массив модификаторов
room_modif[30](у нас естьbg[30]— можно переиспользовать/рядом) — состояние каждого тайла. - Очередь trob (список анимируемых тайлов) +
animate_looseпер-кадр → перерисовка ТОЛЬКО изменившихся тайлов (как heal-прямоугольник Kid). - make_loose_fall / do_knock / loose_make_shake — триггеры (из физики Kid: приземление→knock, стойка на loose→fall).
- mob-система (падающий кусок): минимум 1–2 mob'а, гравитация,
приземление → debris. Урон Киду (
check_loose_fall_on_kid) — можно Фазой 2. - Перерисовка тайла:
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:
- Общая МУТАБЕЛЬНАЯ копия комнаты: roomtest.c держит
uint8_t room_fg[30](копия room1_fg) и передаёт ОДИН указатель и вpop_room_draw, и вpop_map_set→ мутации loose видны обоим. (Сейчас g_fg —const; сделать неконстантным.) - Состояние:
uint8_t pop_loose_modif[30](0 / 0x80.. / 1..11). - Модель (pop_map):
check_press()вpop_phys_tick(make_loose_fall при стоянии на 11),animateкаждый кадр. - Перерисовка (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 показывает пусто.
- тряска/отсчёт (код всё ещё 11):
- 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_*).