Skip to content

Latest commit

 

History

History
332 lines (280 loc) · 32.2 KB

assets.md

File metadata and controls

332 lines (280 loc) · 32.2 KB

Under Fire: текстуры, атласы и объём ассетов для браузера

Исследование от 29.09.2026. В raw/ и в проекте ничего не менялось. Все скрипты, данные и картинки лежат в scratchpad/research/assets/ (далее assets/). Окружение: Mac arm64, 8 ядер, 8 ГБ ОЗУ. Отдельный venv assets/.venv (uv, Python 3.12.3): texture2ddecoder 1.0.6, Pillow 12.3.0, numpy 2.5.3, pyoxipng 9.1.1, pngquant 3.0.3 (пакет pngquant-cli), imageio-ffmpeg 0.6.0 (ffmpeg 7.1 с libopus, aac_at и libmp3lame) и brotli 1.2.0. cwebp 1.6.0 взят из Homebrew, он уже был установлен. Глобально ничего не ставилось.

Обозначения: [проверено] — подтверждено командой, числом или картинкой; [предположение] — вывод без прямой проверки.

Кратко

  • Альфа — подтверждено. У всех 3 405 текстур с удвоенной высотой нижняя половина строго серая: R=G=B у 100% пикселей, максимальная разница каналов 0. Это маска, выровненная с цветом пиксель в пиксель. Альфа прямая, не premultiplied. У 703 текстур (все карты и фоны) маска целиком равна 255, то есть альфа им не нужна. 98 текстур без удвоения — непрозрачные изображения.
  • Атласы — подтверждено с уточнениями. Поля такие: x y w h — прямоугольник в цветной половине страницы, ox oy — смещение обрезанного кадра от левого верхнего угла исходного кадра (Y вниз), W H — исходный размер кадра. Сверх гипотезы найден 10-й необязательный флаг r (1 340 кадров из 149 933). Такой кадр хранится повёрнутым на 90° по часовой, а пары w,h и ox,oy у него записаны переставленными.
  • Объём браузерного комплекта: около 439 МБ при WebP q90 + WebP-иконках + Opus + сжатых данных. Сейчас оригинал занимает 559 МБ, PNG без оптимизации — 1,21 ГБ.
  • На старте: примерно 17–21 МБ (плюс JS-код) и ещё около 4 МБ до первого боя.
  • Хостинг. Рекомендую держать на GitHub Pages только код, а ассеты не публиковать, а собирать в браузере из XAPK, который игрок приносит сам (см. раздел 5). Если публиковать ассеты всё же решено, подходит Cloudflare Workers/Pages или R2. GitHub Releases не отдают CORS, а LFS не работает с Pages.

1. Декодер .pkm.ccz

Цепочка (реализация — assets/pkm.py): заголовок CCZ! → zlib → PKM → ETC1 → RGB → разбиение на цвет и альфу. Декодер ETC1 — texture2ddecoder.decode_etc1: он возвращает BGRA, альфа равна 255.

Заголовки всех 3 503 файлов [проверено] (assets/texture_headers.json):

  • CCZ: сигнатура CCZ!, тип сжатия 0 (zlib) и версия 2 у всех, резерв 0, длина совпадает с распакованной.
  • PKM: PKM 10, формат 0 (ETC1 RGB) у всех. Размер данных у всех ровно (W/4)·(H/4)·8 байт.
  • Высота: у 3 405 закодированная высота ровно вдвое больше исходной, при этом ширина совпадает, а исходная высота кратна 4, так что половины не делят блоки 4×4. У 95 высота не удвоена. Ещё у 3 иконок зданий 456×280 при исходных 455×279 — это просто дополнение до кратного 4.
  • Все размеры — степени двойки, кроме этих 3 иконок и maps/city/city_1 (3392×2452).
  • Всего: 455 МБ CCZ, 2,28 ГБ распакованного ETC1, 4 563 Мпикс закодировано, из них 2 299 Мпикс видимых.
  • Уникальных по содержимому 3 336. 167 файлов — побайтовые дубли (9,1 МБ CCZ). Например, buildings/X/w_1.* совпадает с buildings/X/w/w_1.*, а одинаковые тайлы встречаются в разных картах.

Скорость [проверено]: текстура 2048×2048 декодируется за 0,03 с. Проверка серости по всем 3 405 текстурам заняла 14 с в 6 процессах. Полный прогон (декод и 5 кодировок) — 648 с.

Образцы (assets/samples/): для каждого есть *_etc_full.png (сырой декод обеих половин), *_rgba.png и *_preview.png (верх | низ | RGBA на шахматке).

Образец Текстура Размер (закод.)
building_acid_barrel buildings/acid_barrel_1_1/w_1 1024×512
building_champion buildings/champion_1/w/w_1 2048×2048
unit_bomb_pig units/bomb_pig/bomb_pig_1 1024×1024
mob_bug mobs/bug/bug_3 1024×2048
citizen_colonist citizens/colonist_female_1/..._1 512×512
turret_gun turrets/gun_1/gun_1_2 2048×2048
effect_air_strike effects/air_strike/air_strike_1 2048×1024
bg_preloader background/preloader_art/..._0_0 1024×2048
map_mission_tile maps/mission/ch_1_s_1_map_04_0_0 1024×2048
ui_button_close, ui_city_scene interface/1024x768/city/... 256×512, 2048×4096
без удвоения: noalpha_icon_base, _icon_attraction, _road, _reactive_gun_alpha, _map_star1_09 icons/buildings, road_texture, effects, maps/mission 256², 456×280, 256², 512², 2048²

2. Гипотеза про альфу

Подтверждена [проверено].

  1. Серость. У всех 3 405 текстур с удвоением нижняя половина строго серая: R=G=B у 100% пикселей, максимальная разница каналов 0 (assets/gray_check.py, gray_check.json).
  2. Выравнивание. Для 8 образцов спрайтов и UI пересечение «альфа > 128» и «цвет не чёрный» максимально при нулевом сдвиге среди сдвигов ±2 px (samples/samples_report.json). Для эффекта air_strike эта метрика неинформативна из-за свечения, для карт и фонов — из-за альфы 255.
  3. Визуально. В превью силуэты маски совпадают со спрайтами. Взрывы превращаются в мягкое свечение, тени — в полупрозрачные. При увеличении ×4 на белом и пурпурном фоне нет тёмной или цветной каймы: samples/unit_bomb_pig_edges_zoom.png, ui_city_scene_edges_zoom.png.
  4. Альфа прямая, не premultiplied. У видимых пикселей часто max(RGB) > A+16: 73% у building_champion, 62% у air_strike. При premultiplied такое невозможно. Под alpha=0 цвет в основном чёрный: медиана 95% пикселей по 2 684 текстурам, 10-й перцентиль 70%. Следствие: при билинейной фильтрации без premultiply на краях возможна тёмная кайма, поэтому текстуры нужно премультиплицировать при загрузке (UNPACK_PREMULTIPLY_ALPHA_WEBGL; в PixiJS это, насколько мне известно, режим по умолчанию [предположение]).
  5. У 703 текстур маска целиком равна 255: maps/mission (523), background/mission_prepare (102), maps/city (61), background/worldmap (9), preloader_art (4) и 4 иконки. Для браузера их стоит хранить как RGB — это главный выигрыш в lossy-WebP.
  6. Шум ETC1 в альфе. 0–7% пикселей имеют альфу 1–8 из 255 вдали от спрайтов (дальше 4 px от альфы > 32). Это хвосты теней и шум ETC1: он не виден, трогать его не нужно.
  7. 98 текстур без удвоения — непрозрачные изображения, альфа им не нужна:
    • 92 иконки icons/buildings: здание на фоне, 256² или 455×279;
    • maps/mission/star_1_map_09 (2048², мини-карта с чёрной виньеткой) и city_map_final_fixed (4096²);
    • maps/city/city_1 (3392×2452) — то же изображение, что склейка 12 тайлов city_1_*: PSNR 39,6 дБ, средняя разница 0,43 уровня, то есть сжато отдельно. Одну копию можно не хранить;
    • road_texture — тайл дороги 256²;
    • effects/reactive_gun*/..._alpha — чёрный фон с яркими полосами. [Предположение] Они рисуются аддитивно, а _alpha в имени — это режим смешивания, а не маска.

3. Гипотеза про .atlas

Подтверждена с уточнениями [проверено] (скрипты assets/atlas*.py, картинки в assets/atlas_frames/).

Формат строки: /имя.png⇥x⇥y⇥w⇥h⇥ox⇥oy⇥W⇥H⇥[r], окончание строки CRLF. Первая строка — textures: X.png. Всего 2 685 атласов (уникальных 2 630), 149 933 кадра, 129 694 уникальных прямоугольника: одинаковые кадры упаковщик хранит один раз.

  • Обычный кадр. x y w h — прямоугольник в цветной (верхней) половине страницы. ox oy — позиция обрезанного кадра в исходном кадре от левого верхнего угла, Y вниз. W H — исходный размер, он же width/height направления в config/visuals/*.xml (у bomb_pig 128×125). Условие ox+w ≤ W, oy+h ≤ H выполняется у всех кадров.

  • Флаг r (1 340 кадров, 0,9%). Кадр хранится повёрнутым на 90° по часовой, а поля заданы в повёрнутой системе. Реальный размер — h×w, реальное смещение — (oy, ox). Восстановление: повернуть область на 90° против часовой и поставить в x=oy, y=ox. Доказательства:

    • в этой интерпретации условие выполняется у всех 1 340 кадров, а «как есть» ломается у 559, «поменять только w и h» — у 351;
    • при сравнении с соседними кадрами той же анимации вариант выигрывает в 1 538 из 1 576 случаев (97,6%), остальные — ничьи с разницей ≤ 0,02 или кадры взрывов;
    • прямой пример: research_center_3_e_45 (r) имеет w=180 h=146 ox=7 oy=54, а соседний e_43 — w=146 h=180 ox=54 oy=7.

    Картинки: rotated_frames_check.png, cut_rotated_research_center_e_45.png.

  • Начало отсчёта — левый верхний угол. По анимациям из visuals соседние кадры ближе друг к другу при отсчёте сверху в 55 визуалах из 57 (1 ничья, 1 противоположный с 7 парами кадров). Независимое подтверждение даёт якорь: (-offsetX, -offsetY) направления и полигон hitArea относительно него точно накрывают спрайт. Для acid_barrel_1_1 hitArea занимает x 0…60, y −38…20, а спрайт — x +4…+53, y −36…+18 (cut_building_acid_barrel_w_0.png). У юнита якорь попадает под ноги (cut_unit_bomb_pig_e_27.png).

  • Обрезка плотная. Зазор между краем кадра и bbox альфы > 0: 0 px у 48–65% сторон, 1 px у 22–39%, 2 px и больше примерно у 10% (выборка 250 атласов). [Предположение] Зазор в 1 px — это ETC1, обнуливший слабую альфу.

  • Упаковка без отступов. У 95% обрезанных кадров соседний прямоугольник вплотную (0 px). В UI-атласах кадры не обрезаны и стоят с шагом 2 px (cut_ui_button_cross.png). При масштабировании с билинейной фильтрацией возможно просачивание соседних кадров. В оригинале было то же самое; если появятся швы, при сборке можно переупаковать с extrude 1–2 px.

  • Аномалии, которые должен учесть загрузчик:

    • effects/tracers/tracers.atlas ссылается на обычный tracers.png, а не на pkm;
    • maps/city/cells.atlas в заголовке называет atlas.png, хотя текстура — cells.pkm.ccz, и имена кадров идут без /. Текстуру надо искать по имени файла атласа, а не по заголовку;
    • 4 кадра interface/*/city/tutorial_menu.atlas выходят за страницу на 2 px (x=2, w=2048) — обрезать по краю;
    • 73 кадра пустые (w или h равно 0), 957 кадров не обрезаны;
    • имена кадров не уникальны между HD- и SD-интерфейсом, поэтому пространство имён нужно вести по атласу.
  • Текстуры без атласа (820): maps/mission (535), background/mission_prepare (102), icons/buildings (96), maps/city (71), background/worldmap (9) и другие. Большие картинки нарезаны на тайлы и описаны XML back_atlas (<frame img x y width height>), остальные используются целиком.

Перевод в JSON-формат TexturePacker (для PixiJS или Phaser) [предположение — проверить в выбранном движке на research_center_3_e_45, эталон есть]:

  • обычный кадр: frame {x,y,w,h}, spriteSourceSize {x:ox, y:oy, w, h}, sourceSize {W,H}, trimmed = (w,h) ≠ (W,H);
  • кадр с r: rotated: true, frame {x, y, w:h, h:w}, spriteSourceSize {x:oy, y:ox, w:h, h:w};
  • pivot = (-offsetX/W, -offsetY/H) из направления в visuals.

4. Объём

Методика: assets/convert_all.py декодирует каждую уникальную текстуру и кодирует её пятью способами. Файлы не сохраняются — пишутся только размеры в sizes_textures.jsonl. Маски, равные 255 везде, кодируются как RGB. Кодировки:

  • PNG через Pillow, zlib 6;
  • oxipng -o2;
  • pngquant --quality=65-90 --speed 3; если качество недостижимо, берётся oxipng — так было у 58 из 3 336;
  • cwebp -lossless;
  • cwebp -q 90 (альфа остаётся lossless).

Полная выдача — assets/summary.txt.

Текстуры браузерного комплекта [проверено]: 3 242 уникальных файла. Исключены 93 текстуры SD-интерфейса interface/1024x768_sd и дубль city_1. 166 дублей по путям уже не учитываются (в WebP это 8,3 МБ); в клиенте их можно раздавать через манифест.

Категория Файлов Мпикс CCZ (оригинал) PNG PNG+oxipng PNG8 pngquant WebP LL WebP q90
buildings 1 757 543 124,4 319,5 268,1 94,5 154,0 118,4
maps/mission 525 573 84,5 199,6 155,1 118,0 116,3 49,0
units 208 241 79,3 207,7 168,3 84,0 115,3 83,8
mobs 192 217 76,9 198,5 161,6 81,3 111,1 80,5
interface HD 92 313 17,2 36,1 31,0 16,8 20,7 13,3
background 115 121 17,1 33,3 26,6 22,7 20,1 7,2
effects 136 39 12,4 28,8 24,1 7,7 14,4 10,7
citizens 44 17 7,8 20,9 16,9 10,0 11,5 8,8
maps/city 64 66 9,1 19,1 15,3 11,4 11,8 4,5
turrets 12 21 6,6 19,1 15,7 7,3 9,3 7,9
icons (ETC) + road 97 6 2,3 5,5 4,4 3,6 3,3 1,6
Итого, МБ 3 242 2 155 437,7 1 088,1 886,9 457,1 588,0 385,8

Если для каждого файла выбирать меньшее из WebP q90 и PNG8, выйдет 333,6 МБ. SD-интерфейс занял бы ещё 5,0 МБ в WebP q90 (81 Мпикс).

Качество lossy [проверено] на 6 образцах, эталон — lossless-декод, который сам уже прошёл через ETC1. PSNR premultiplied RGB:

Образец WebP q90 pngquant
здание 44,8 дБ 41,2 дБ
юнит 35,4 дБ 35,5 дБ
эффект 49,8 дБ 43,5 дБ
UI 41,7 дБ качество недостижимо

На непрозрачных картах WebP q90 даёт 37,8–40,2 дБ при 189–205 КБ, а PNG8 — 34–39 дБ при 447–520 КБ. При увеличении ×3 WebP q90 визуально неотличим от исходника, а у pngquant в мягких ореолах (взрывы, брызги) заметен зернистый дизеринг: assets/quality/*_compare.png. Поэтому рекомендую WebP q90.

Прочее [проверено]:

  • Готовые PNG (1 952 файла, 98,6% уже индексированные PNG8): как есть 68,6 МБ, oxipng 63,4, WebP lossless 57,1, WebP q90 32,6.
  • Звук (178 файлов, 32,6 мин; MP3 128 кбит/с, WAV моно 22 кГц и стерео 44,1 кГц): как есть 47,6 МБ.
    • Opus (музыка 64k, моно SFX 32k, стерео SFX 48k) — 14,8 МБ.
    • AAC (96k, 48k, 64k) — 22,3 МБ.
    • MP3 96k/64k — 22,1 МБ.
    • Главная экономия — на actions/*.wav: 14 МБ → около 1 МБ.
  • Данные — всего около 5,4 МБ в gzip:
    • атласы 9,1 МБ → 1,4 МБ gzip;
    • config.json 14,1 МБ → 0,36 gzip, 0,20 brotli;
    • config/maps 12,9 → 1,18;
    • миссии 3,9 → 0,40;
    • локали 1,6 → 0,30;
    • visuals 4,1 → 0,3–0,8;
    • ccbi 0,6 → 0,24;
    • TTF 0,6 → 0,22.

Полный комплект, МБ [проверено; состав — моё решение]:

Вариант Текстуры PNG-иконки Звук Данные Итого
Как есть (CCZ + PNG + WAV/MP3) 437,7 68,6 47,6 5,4 559,4
PNG без оптимизации 1 088,1 68,6 47,6 5,4 1 209,8
PNG + oxipng, AAC 886,9 63,4 22,3 5,4 978,0
PNG8 pngquant, AAC 457,1 63,4 22,3 5,4 548,2
WebP lossless, Opus 588,0 57,1 14,8 5,4 665,3
WebP q90, Opus (рекомендую) 385,8 32,6 14,8 5,4 438,6
WebP q90, AAC 385,8 32,6 22,3 5,4 446,1
min(WebP q90, PNG8), Opus 333,6 32,6 14,8 5,4 386,4

Если держать на сервере и Opus, и AAC, выйдет около 461 МБ, но каждый клиент качает только один формат. Файлов около 8–10 тысяч: 3 242 текстуры, до 2 630 JSON-атласов, 1 952 иконки, 178 звуков и данные. Самый большой файл в WebP — 1,02 МБ (city_map_final_fixed).

Старт [объёмы проверены; состав набора — предположение]. Набор собран по initial_users/default:

  • постройки base → base_1, trade_area → wait_1, citizen_house → colony_house_1, invasion_house → barriers_2_1;
  • фон загрузки preloader_art, карта колонии (12 тайлов, substrate, cells, blue_circle), колонисты;
  • иконки ресурсов и аватаров, 91 UI-звук, городская тема 01 и ambient_city.

Каким будет реальный набор, выяснится, когда появится загрузчик.

Набор Текстуры WebP q90 (CCZ) PNG Звук Opus (AAC) Данные Итого
Минимальный старт (UI: splashscreen, mainmenu, city) 12,3 (16,5) 1,0 1,8 (2,9) ~2 ~17 МБ
Старт со всем HD-интерфейсом (+battle, worldmap) 16,4 (21,6) 1,0 1,8 (2,9) ~2 ~21 МБ
Дозагрузка к первой миссии (tutorial_01_map_01, туториальные юниты и мобы, авиаудар, все actions, боевая тема) 2,5 — 1,9 — ~4,4 МБ

К этому добавляется JS-код движка. Сеть не узкое место, узкое место — видеопамять. Стартовые текстуры в RGBA8 занимают 1,05–1,36 ГБ (263–339 Мпикс), почти всё приходится на HD-интерфейс: 92 текстуры, 313 Мпикс, в основном 2048² с пустыми областями. Весь комплект занял бы 8,6 ГБ. В оригинале ETC1 занимал 0,5 байта на пиксель на каждую половину. Что с этим делать:

  • грузить и выгружать UI по экранам;
  • на мобильных использовать SD-интерфейс (81 Мпикс);
  • позже перевести тяжёлые категории на KTX2/Basis — это не измерялось.

5. Хостинг

Лимиты проверены 29.09.2026 по официальной документации.

Площадка Лимиты Источник
GitHub, репозиторий Предупреждение на файлах > 50 МиБ, блок > 100 МиБ, загрузка через браузер ≤ 25 МиБ. Репозиторий «в идеале < 1 GB», «настоятельно < 5 GB». Размер на диске 10 GB, push ≤ 2 GB, в каталоге ≤ 3 000 записей. about-large-files, repository-limits
Git LFS Free и Pro: 10 GiB хранения и 10 GiB трафика в месяц; Team и Enterprise: 250/250. Файл ≤ 2 GB (Free/Pro), 4 GB (Team), 5 GB (Enterprise). LFS нельзя использовать для сайтов GitHub Pages. Цены сверх квоты на странице документации не указаны. В сниппете поиска фигурируют $0,07 за ГиБ·мес хранения и $0,0875 за ГиБ трафика — не подтверждено. На github.com/pricing — «$5 за 50 GB». LFS billing, about LFS
GitHub Releases Файл < 2 GiB, до 1 000 ассетов в релизе, общий размер и трафик не ограничены. Проверено curl: ассет редиректит на release-assets.githubusercontent.com и отдаётся без Access-Control-Allow-Origin, поэтому fetch() из браузерной игры с другого домена не сработает. about-releases
GitHub Pages Сайт ≤ 1 GB, исходный репозиторий рекомендуется ≤ 1 GB, трафик — мягкий лимит 100 GB в месяц, 10 сборок в час, деплой ≤ 10 мин. Не для коммерческих сервисов. Проверено: access-control-allow-origin: *, cache-control: max-age=600. pages-limits
Cloudflare Pages 20 000 файлов на сайт (Free), 100 000 на платных тарифах; файл ≤ 25 МиБ; 500 сборок в месяц (Free). pages limits
Cloudflare Workers Static Assets «Requests to static assets are free and unlimited». 20 000 файлов на версию (Free), 100 000 (Paid); файл ≤ 25 МиБ. billing-and-limitations, workers limits
Cloudflare R2 Free: 10 GB·мес, 1 млн операций класса A и 10 млн класса B в месяц, исходящий трафик бесплатный. Сверх квоты: $0,015 за GB·мес, $4,50 за млн A, $0,36 за млн B. Объект ≤ 5 GiB. Адрес r2.dev ограничен по частоте и предназначен только для разработки; для продакшена нужен свой домен, и только с ним работает кэш. r2 pricing, public buckets, r2 limits

Что это значит для рекомендуемого комплекта (438,6 МБ, около 10 тысяч файлов, самый большой 1 МБ):

  • GitHub Pages технически вмещает комплект. Но ассеты пришлось бы коммитить в git: история раздувается, и после пары обновлений репозиторий перевалит за 1 GB. 100 GB трафика в месяц — это около 230 полных скачиваний.
  • Releases не подходят для прямой загрузки из браузера: нет CORS.
  • LFS не отдаётся через Pages, а 10 GiB трафика — это около 20 клонов.
  • Cloudflare Workers/Pages вмещают комплект с запасом (лимит — 20 тысяч файлов по 25 МиБ), статика бесплатна и не ограничена, а заливка через wrangler идёт прямо из локальной сборки, минуя git. R2 — альтернатива для крупных редко меняющихся данных: 10 GB бесплатно, но нужны свой домен и CORS.

Рекомендация. Главное ограничение не техническое. В README проекта сказано, что графика, звук и данные игры принадлежат правообладателям и в репозиторий не входят. Любая публичная раздача сконвертированных ассетов — это их распространение (на GitHub, кроме того, есть риск DMCA для всего репозитория).

  1. Основной путь — «свой XAPK». На GitHub Pages лежит только клиент.
    • Игрок выбирает свой XAPK или OBB. main.13 (480 МБ) и patch.59 (154 МБ) — обычные ZIP с Deflate и Stored [проверено unzip -v].
    • Клиент читает нужные записи через File.slice по центральному каталогу ZIP. CCZ распаковывается через DecompressionStream('deflate'), это zlib-обёртка. ETC1 декодируется в Web Worker (JS или WASM), маска берётся из нижней половины. Всё кэшируется в OPFS или IndexedDB.
    • Декод дешёвый: 0,03 с на 2048² в нативном коде. [Предположение] В WASM в пределах ×2–3.
    • Экономия на хостинге — 100%, и это согласуется с политикой README.
  2. Если публиковать ассеты всё же решено (с разрешения или для закрытого теста):
    • хостинг — Cloudflare Workers Static Assets или Pages через wrangler deploy из локальной сборки, либо R2 со своим доменом;
    • формат — WebP q90 (RGB для карт и фонов, RGBA для спрайтов), атласы в JSON, звук Opus с запасным AAC (в Safari на macOS поддержка Opus частичная, caniuse), данные в JSON с brotli или gzip от CDN;
    • раскладка — сохранить дерево каталогов (лимит 3 000 записей в каталоге GitHub), имена с хешем содержимого и манифест (дедупликация 166 путей), Cache-Control: immutable, Service Worker.
  3. Загрузка в клиент в любом варианте:
    • пакетами по сценам: старт около 17–21 МБ, миссия несколько МБ, постройки по требованию;
    • premultiply при загрузке текстур;
    • выгрузка текстур при уходе со сцены;
    • SD-интерфейс для мобильных;
    • при швах на кадрах — переупаковка атласов с extrude.

Файлы

  • Код:
    • assets/pkm.py — чтение CCZ и PKM, ETC1, альфа;
    • decode_samples.py, gray_check.py — шаги 1–2;
    • atlas.py — разбор и проверка всех атласов;
    • atlas_check.py, atlas_seq_test.py, atlas_rot_test.py — смещение и поворот;
    • atlas_cut_examples.py — вырезка кадров;
    • convert_all.py, icons_sizes.py, audio_sizes.py, summarize.py — объёмы.
  • Данные: texture_headers.json, texture_hash_groups.json, sizes_textures.jsonl, sizes_png.json, sizes_audio.json, sizes_text.json, gray_check.json, summary.txt, browser_set.json.
  • Картинки: assets/samples/ (16 образцов, превью и увеличения краёв), assets/atlas_frames/ (вырезки кадров и проверка поворота), assets/quality/ (сравнение WebP и pngquant).