• Nikitos's avatar
    Несколько форм на тело: зоны «голова»/«ноги» через .zone()
    · 53fc4716
    Nikitos создал
    Последний хвост §1.3: у тела движка была ОДНА форма, поэтому «попал в голову, а
    не в ногу» было недостижимо. Теперь у тела до 8 форм-зон, каждая со смещением от
    центра, а в контакте видно, КАКАЯ форма столкнулась.
    
    ЧТО СДЕЛАНО:
    * C: r2d_physics_add_shape(p, id, desc, dx, dy) и r2d_physics_shape_count;
      user data формы теперь кодирует И тело, И номер формы: (id+1) << 8 | index.
      Появился r2d__shape_index_of_shape; contact_between/contacts_of отдают индексы
      форм (shapeA/shapeB, shape/shapeOther);
    * engine.addShape(body, desc), engine.shapeCount(body);
    * $.world.zone(node, {type, w, h, x, y, tag, sensor, ...}), zoneTag, zoneCount,
      zonesTouching; метод узла .zone({...}).
    
    Имя метода — `zone`, а НЕ `shape`: `.shape()` уже занято формой хитбокса, и мой
    первый вариант молча перезаписался вторым def (это и обнаружилось тем, что
    `.shape({...})` возвращал обёртку, а формы не добавлялись).
    
    СМЕЩЕНИЕ ЗОНЫ — ОТДЕЛЬНЫЕ ПАРАМЕТРЫ dx/dy, а не поля описания тела. Сначала я
    положил смещение в R2DBodyDesc (shape_dx/shape_dy), и это тихо сломало ВСЕ
    коллизии: тела стали пролетать сквозь стены. Внешне всё выглядело здоровым —
    тела создавались, массы были правильные (смещение не меняет площадь), луч видел
    стены. Диагноз дал только контрольный опыт: с инлайн-созданием формы коллизии
    возвращались, а через helper — нет.
    
    ТРИ ГРАБЛИ, на которые я наступил, и что их ловит:
    1. `&sd` вместо `sd` в helper — движок падал с SIGTRAP на старте. Компилятор
       предупреждал, а я warnings не смотрел; теперь смотрю.
    2. Смещение в описании тела — сломало коллизии (см. выше).
    3. Имя `.shape()` занято — переименовал в `.zone()`.
    
    ПРОВЕРКА (tests/agent/highlevel_zones_test.py, 16 проверок): зоны получают
    индексы 1 и 2 (0 — основная форма), теги читаются; и главное — ФИЗИКА: пол
    касается формы 2 → тега `legs`, а НЕ головы; на втором теле стена касается зоны
    `foot`, а не основной формы. То есть зоны действительно разнесены в пространстве,
    а не «настройка сохранилась».
    
    ЧЕСТНО ПРО ГРАБЛИ В ТЕСТЕ: проверка «шип падает сверху в голову» оказалась
    хрупкой — шип у меня проскакивал мимо, а зоны часто перекрываются, и тогда пара
    касается нескольких форм сразу. Поэтому проверяю через `contactsOf` (какая форма
    лежит на полу) и добавил `zonesTouching` — он отдаёт ВСЕ задетые зоны, а не
    первую. Это записано в world.md §2.3.
    
    ДОКИ: world.md §2.3 (поля зоны, перекрытие, предел 8 форм), API.md
    (engine.addShape с прямым предупреждением, что x/y — смещение, а не позиция),
    TASKS §1.3 и §12.6.
    
    Полный qjs, агентские тесты (включая contact и tug, которые эту регрессию и
    поймали), doc_coverage, doc_claims, duplicate_keys, быстрый набор — зелёные.
    53fc4716