{"skill":"playphoria-animaude","date":"2026-10-12","commit":"1c82bc7f3e4acc06793ab31a72d437b038d778e8","source":"git@github-mt-playphoria:playphoria-io/playphoria-skills.git","findings_count":20,"priorities":{"P1":3,"P2":17},"coverage":{"package_files":56,"javascript_files_syntax_checked":17,"python_files_syntax_checked":3,"json_files_checked":8,"markdown_literal_file_references_checked":88,"missing_markdown_literal_files":0,"examples":{"good":{"FAIL":0,"WARN":0},"robotic":{"FAIL":3,"WARN":8}},"gait_presets_tested_with_explicit_speed":15,"gait_default_speed_failures":13,"image_files_decoded":13,"synthetic_fbx_formats":["ASCII 7.x","binary 7400","binary 7500"],"numpy_version":"2.3.5","node_version":"26.11.1","python_version":"3.12.14"},"findings":[{"id":"F01","priority":"P1","title":"Походка по пресету или Fr без явной скорости не запекается","locations":[{"file":"scripts/gait.mjs","line":188,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/gait.mjs#L188"},{"file":"scripts/gait.mjs","line":496,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/gait.mjs#L496"}],"reproduction":"Gt.plan({rig: humanoid, preset: \"biped-walk\"}) → autoBody → bakeCycle: stride 1.589123028591556 не равен speed × cycle 1.58912; исключение. Подтверждено на 13 комбинациях пресета и штатного рига.","impact":"plan округляет speed до 4 знаков, а stride оставляет с исходной точностью; bakeCycle допускает расхождение только 1e-9. Это ломает обычное использование скорости по умолчанию. С явно заданной speed: 0.9 все 15 пресетов проходят проверку конечности значений и контракта цикла/опор.","fix":"Хранить полную точность скорости в плане и округлять только текст отчёта либо пересчитывать stride из той же скорости."},{"id":"F02","priority":"P2","title":"Документированный --height падает с TypeError","locations":[{"file":"scripts/rig-from-manifest.mjs","line":29,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/rig-from-manifest.mjs#L29"}],"reproduction":"node rig-from-manifest.mjs test-manifest.json --height 6 → exit 1, .toFixed is not a function. Тот же manifest без --height → exit 0.","impact":"Аргумент CLI является строкой; .toFixed вызывается до преобразования unary +. Нельзя задать высоту через публичную опцию.","fix":"Преобразовать выбранное значение в число до вызова toFixed и проверить конечность/положительность."},{"id":"F03","priority":"P1","title":"Lint падает при поддержке в одной точке","locations":[{"file":"scripts/lint.mjs","line":45,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/lint.mjs#L45"}],"reproduction":"Валидный риг с одним контактом foot без box и с tip [0,0,0], плюс удерживаемый mass-0 prop с massShare: 0.1: lint → TypeError в segDist/outsideHull.","impact":"Построение hull отбрасывает единственную точку; затем segDist получает undefined. Это достижимо при поддержке на одном небоксированном конце конечности с грузом, в том числе в ригах с несколькими ногами, когда остальные подняты.","fix":"Обрабатывать одну уникальную точку до удаления конечных вершин цепочек; отдельно обрабатывать две точки и вырожденный hull."},{"id":"F04","priority":"P2","title":"Упаковщик обещает проверку --hub, но не сравнивает позы","locations":[{"file":"scripts/pack.mjs","line":141,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/pack.mjs#L141"}],"reproduction":"pack([{idle}, {attack: hips смещены на 5 м}], {hub: \"idle\"}) успешно создаёт pack. checkContract того же attack со startPose: idle обнаруживает расхождение 5.000 м.","impact":"--hub проверяется только на существование имени. Заявленная защита от несовместимого входа one-shot отсутствует.","fix":"Сравнивать первую позу каждого применимого one-shot с позой hub по именам костей и согласованному допуску. Отдельно определить контракт выходов, которые должны возвращаться на hub."},{"id":"F05","priority":"P2","title":"Упаковщик принимает разорванный цикл с противоположными трансляциями","locations":[{"file":"scripts/pack.mjs","line":81,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/pack.mjs#L81"}],"reproduction":"Цикл с локальным hips.x от -1 до +1 м принят pack, хотя checkContract показывает разрыв 2.000 м.","impact":"Проверка a+b распространяет симметрию знака quaternion на координаты трансляции. Кроме того, знак вращения проверяется по отдельным компонентам, а не для quaternion целиком.","fix":"Сравнивать позиции непосредственно, а quaternion — целиком с учётом эквивалентности q и -q."},{"id":"F06","priority":"P1","title":"ClipPlayer теряет события при начале и окончании клипа","locations":[{"file":"scripts/player.mjs","line":282,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/player.mjs#L282"},{"file":"scripts/player.mjs","line":311,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/player.mjs#L311"}],"reproduction":"Первый показ нового hit-клипа на t=0.2 с событием hit@3: events пуст. Переход с t=0.9 к t=1.1 в клипе длительностью 1 с hit@29: events также пуст.","impact":"На смене источника события вообще не собираются; при переходе за конец one-shot источник меняется на loco до обработки оставшегося интервала. Теряются hit/release/FX, в том числе при пропусках показов под pose LOD.","fix":"Сначала собрать события пройденного интервала исходного клипа до его конца, затем переключать источник; на первом показе нового клипа обработать интервал от его начала."},{"id":"F07","priority":"P2","title":"Обработчики событий циклов не учитывают переходы через границу и несколько оборотов","locations":[{"file":"scripts/player.mjs","line":67,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/player.mjs#L67"},{"file":"scripts/player.mjs","line":211,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/player.mjs#L211"}],"reproduction":"timeline.eventsBetween(0.95,1.1) в цикле 1 с со step@29 и step@1 возвращает одно событие вместо двух. eventsCrossed для N=30, step@3, диапазона 0→65 возвращает одно событие вместо трёх: ожидаются 3,33,63.","impact":"Сравнение только начального и конечного frame не описывает весь интервал циклического времени; один event выдаётся максимум один раз.","fix":"Разбивать интервал на участки между границами циклов и возвращать все пересечения в порядке времени, включая повторения."},{"id":"F08","priority":"P2","title":"Look-at головы не обновляет её дочерние кости","locations":[{"file":"scripts/player.mjs","line":368,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/player.mjs#L368"}],"reproduction":"Риг head → eye с offset [1,0,0], pitch головы 0.25 рад: head поворачивается, eye остаётся [1,2,0] вместо [0.9689,2.2474,0]; ошибка 0.24935 м.","impact":"Меняется только матрица головы после общего FK; потомки и их позиции остаются от исходной позы. Глаза, челюсть и другие дочерние детали отделяются от повёрнутой головы.","fix":"Применять реактивное вращение до FK либо обновлять весь подграф потомков и согласованные мировые трансформы."},{"id":"F09","priority":"P2","title":"Зеркалирование не меняет местами анимированные позиции парных костей","locations":[{"file":"scripts/player.mjs","line":201,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/player.mjs#L201"}],"reproduction":"Клип с анимированными handL.x=1 и handR.x=2: после mirrorPose позиции остаются 1 и 2 вместо 2 и 1.","impact":"Обрабатываются знак z, quaternion и contact weights, но пропущена перестановка позиционных каналов в layout.pos. Зеркалирование клипов с позиционной анимацией конечностей неверно.","fix":"Передать операции зеркалирования отображение костей в позиционные каналы и переставлять все соответствующие позиции."},{"id":"F10","priority":"P2","title":"Удерживаемый предмет отрывается от руки после ground IK","locations":[{"file":"scripts/player.mjs","line":327,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/player.mjs#L327"}],"reproduction":"Предмет расположен в начале координат hand. После адаптации руки к возвышению hand.y=0.380025 м, prop.y=0.200000 м; разрыв 0.180025 м.","impact":"Позиция props берётся из исходного клипа с поправкой только на общий dy тела. Изменения руки от reach/ground не учитываются, хотя runtime.md требует перемещать предмет вместе с руками.","fix":"Сохранять относительный трансформ удерживаемого предмета к поддерживающей руке и восстанавливать его после реактивных слоёв; согласовать решение для двуручного хвата."},{"id":"F11","priority":"P2","title":"После settle следующий сегмент начинает с прежнего значения","locations":[{"file":"scripts/animaude.mjs","line":271,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/animaude.mjs#L271"}],"reproduction":"channel: .at(0,0).to(10,1,\"ease\").settle(7,{amp:0.1,period:10,halfLife:100}).hold(7,{drift:0}); f17=0.909399, f18=1.000000, скачок 0.090601.","impact":"settle записывает отклонения в track, но не переносит последнее значение и скорость в курсор. Следующий hold/to или хвост трека создаёт скачок, если settle заканчивается не точно в нуле.","fix":"Переносить фактическое конечное значение и скорость либо завершать settle плавным переходом к согласованному состоянию."},{"id":"F12","priority":"P2","title":"Рецепт атаки даёт дробные кадры, которые Channel молча пропускает","locations":[{"file":"references/attacks.md","line":172,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/references/attacks.md#L172"},{"file":"scripts/animaude.mjs","line":214,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/animaude.mjs#L214"},{"file":"scripts/animaude.mjs","line":266,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/animaude.mjs#L266"}],"reproduction":"Рецепт Heavy Brute, weight heavy, height 6: cock=36, hold(cock*0.4)=14.4. Следующие записи в Float64Array идут по дробным индексам и не сохраняются; yaw скачком меняется на 0.452672 рад (~25.94°) на кадре 33.","impact":"В самой документации есть дробные длительности. После f+=T курсор становится дробным, часть кривой остаётся заполненной нулями.","fix":"Округлять кадры в опубликованном рецепте. В Channel явно валидировать целые границы/длительности либо корректно семплировать непрерывное время."},{"id":"F13","priority":"P2","title":"Exaggeration 0 % подменяется Balanced 60 %","locations":[{"file":"scripts/animaude.mjs","line":112,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/animaude.mjs#L112"}],"reproduction":"A.dials({exaggeration:0}).exaggeration.timing → 0.6. A.exaggeration(0) также даёт Balanced; форма {dial:0} даёт правильный ноль.","impact":"if (!L) continue пропускает валидный числовой ноль. Числовая форма, заявленная в API, нарушает выбор пользователя и проверки вариантов 0 %.","fix":"Пропускать только отсутствующие слои, отличая undefined/null от числового 0."},{"id":"F14","priority":"P2","title":"planLeap выходит за фиксированный telegraph","locations":[{"file":"scripts/animaude.mjs","line":184,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/animaude.mjs#L184"}],"reproduction":"A.planLeap({telegraph:9}) возвращает rise=2, sink=4, squeeze=1, push=3, total=10.","impact":"После распределения времени минимальные значения фаз увеличивают сумму сверх бюджета. Вместо соблюдения 9 кадров или явного отказа launch оказывается на кадр позднее.","fix":"Проверять совместимость бюджета с суммой минимумов; распределять целые кадры с сохранением точной суммы либо явно отклонять слишком короткий telegraph."},{"id":"F15","priority":"P2","title":"Bake пропускает невалидный root и нулевые quaternion","locations":[{"file":"scripts/bake.mjs","line":27,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/bake.mjs#L27"},{"file":"scripts/bake.mjs","line":31,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/bake.mjs#L31"}],"reproduction":"root.p=[NaN,0,0] успешно запекается и сериализуется как [null,0,0,0]. joints.hips.q=[0,0,0,0] также успешно попадает в values.","impact":"Проверяются переводы суставов и конечность quaternion, но не root и не ненулевая длина quaternion. На выходе возникает повреждённый файл; нулевой quaternion позже отвергается pack.","fix":"Проверять конечность всех root/scale/transform-компонент, длину массивов и ненулевой quaternion до нормализации."},{"id":"F16","priority":"P2","title":"FBX-конвертер поворачивает исходное +Z в -X вместо +X","locations":[{"file":"scripts/refstudy/fbx2clip.py","line":59,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/refstudy/fbx2clip.py#L59"},{"file":"scripts/refstudy/fbx2clip.py","line":60,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/refstudy/fbx2clip.py#L60"}],"reproduction":"Синтетический ASCII FBX 7.x: исходный forward +Z, движение вперёд. Результат объявляет forward +x, фактическое направление ≈[-1,0,-9e-6], перемещение hips.x=-1.075 м.","impact":"Знак yaw/выбор транспонирования не согласованы с матрицей поворота. Референс разворачивается не в заявленное каноническое направление.","fix":"Согласовать atan2 и применяемую матрицу; проверять исходные +X,+Z,-X,-Z и диагональные направления по итоговому вектору forward."},{"id":"F17","priority":"P2","title":"Часть преобразований up axis создаёт отражение вместо вращения","locations":[{"file":"scripts/refstudy/rig.py","line":58,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/refstudy/rig.py#L58"},{"file":"scripts/refstudy/rig.py","line":61,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/refstudy/rig.py#L61"}],"reproduction":"Матрицы Clip.R имеют determinant -1 для up +X, -Y и -Z; +Y,+Z,-X дают +1.","impact":"Нечётная перестановка или одиночная смена знака образует отражение. Преобразование такой матрицы в обычный quaternion не сохраняет трансформ; SVD U@Vt не исправляет handedness.","fix":"Строить согласованный ортонормальный базис с determinant +1 и применять его к позициям и ориентациям; добавить проверки всех шести осей."},{"id":"F18","priority":"P2","title":"Все eval-сценарии ссылаются на отсутствующие входные файлы","locations":[{"file":"evals/evals.json","line":12,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/evals/evals.json#L12"},{"file":"evals/evals.json","line":92,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/evals/evals.json#L92"}],"reproduction":"9 сценариев, 18 ссылок, 6 уникальных отсутствующих путей: inputs/FORMAT.md, inputs/ambusher.rig.json, inputs/golem.rig.json, inputs/humanoid.rig.json, inputs5/FORMAT.md, inputs5/humanoid.rig.json.","impact":"Набор проверок из опубликованного пакета не воспроизводится без внешней подготовки, которая не описана. Часть соответствующих ригов есть в assets, но evals указывают иные пути; FORMAT.md не поставляется.","fix":"Использовать поставляемые риги по реальным путям и добавить/описать формат входов либо документированный способ подготовки fixture-каталогов."},{"id":"F19","priority":"P2","title":"Грейдер выбирает drop/attack для ambusher-idle","locations":[{"file":"evals/grade.mjs","line":279,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/evals/grade.mjs#L279"}],"reproduction":"Одинаковый clip.json в eval-5-ambusher-idle получает проверки падения, приземления и удара; в eval-5-idle — проверки idle. Различаются только имена каталогов.","impact":"Регулярное выражение /drop|ambush/ выполняется раньше /idle/ и совпадает с ambusher. Предусмотренный в evals.json сценарий idle оценивается не тем набором критериев.","fix":"Выбирать критерии по явному eval_id/типу сценария из метаданных, а не по частичным совпадениям имени существа."},{"id":"F20","priority":"P2","title":"Manifest с костью root превращается в непригодный риг","locations":[{"file":"scripts/rig-from-manifest.mjs","line":18,"url":"https://github.com/playphoria-io/playphoria-skills/blob/1c82bc7f3e4acc06793ab31a72d437b038d778e8/skills/3d/playphoria-animaude/scripts/rig-from-manifest.mjs#L18"}],"reproduction":"Manifest bones=[root(parent:-1), body(parent:0)] успешно конвертируется, но new Rig(result) бросает list parents before children (root).","impact":"Конвертер безусловно добавляет собственный joint root. У одноимённой кости получается дублирование имени и ссылка на саму себя через name→index.","fix":"Выбирать уникальное имя контейнера либо корректно использовать уже существующий root, сохраняя имена костей игрового скелета."}],"limits":["Проверены исходники, документация, встроенные примеры и изолированные воспроизведения. Полное покрытие всех ветвей/комбинаций параметров не заявляется.","Интеграция в реальную игру, визуальное качество новых анимаций и реальные производственные skinned mesh не проверялись. Mesh, adapter, skinning, palm measurement и рендер проверены на синтетическом объекте.","FBX-конвертация проверена на синтетических ASCII 7.x/binary 7400/7500 файлах. Реальные Mixamo/Iglesias/UE4/Biped наборы, ASCII 6.1 и архивы замеров в пакет не входят; их полнота/численные результаты не подтверждены.","Node 18 отдельно не запускался; проверки выполнялись на Node 26.11.1. Python выполнялся в bundled runtime 3.12.14 с numpy."],"additional_notes":["В baking.md требуется точное воспроизведение клипа, но bake добавляет текущий meta.bakedAt. Числовые данные анимации воспроизводимы в примерах, побайтовое равенство полного JSON из-за временной метки не гарантируется.","SKILL.md называет инструмент SendUserFile; доставка в Codex требует адаптации к доступным здесь средствам показа/сохранения файлов.","Инструкции о неизменной длительности strike и масштабировании каждой длительности по размеру требуют согласования формулировок с правилами planAttack."],"report_version":"visual-report-1","visual_count":13}