Files
Sprinter-SDCC/applications/PoP/docs/loose_floors_plan.md
T
snark13 5ef084eca4 docs: loose_floors_plan — конкретика SDLPoP + подход интеграции
Дополнен точными данными из 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>
2026-07-18 21:43:43 +03:00

9.8 KiB
Raw Blame History

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 → взводит knockcheck_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 <= 0modif = 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_*).