Порт утилиты LTC из LithTech на современный C++ — сжатие и распаковка .lta
(LZSS, окно 4096, look-ahead 17, собственный битовый поток).
Цель — побайтовый паритет с оригиналом: те же .ltc из тех же входных файлов и те же
коды возврата из тех же аргументов. Не «похожий LZSS», а именно тот.
LTC <filename> [-out <file>] [-view] [-verbose] [-help]
-verboseв оригинале — мёртвый флаг: принимается и ничего не печатает. Порт повторяет это поведение, а не «исправляет» (см.docs/porting-notes.md, Q1).CLTAHuffmanTreeизlibs/ltamgr— мёртвый код, в формате не участвует, порт его не реализует (Q7).
| этап | статус | |
|---|---|---|
| M0 | эталонная сборка, корпус, золотые файлы, детерминизм | готово |
| M1 | CLI 1:1 + битовый слой + запись без сжатия | готово |
| M2 | компрессор 1:1: те же .ltc, что у эталона |
готово |
| M2 | сжатие 1:1 (байт в байт) | готово |
| M3 | декодер, -view, round-trip |
готово |
| M4 | инварианты, второй компилятор, документация | готово |
Подробно: docs/roadmap.md. Разбор формата и найденных квирков: docs/ANALYSIS-4.md.
Сводка паритета и его границ одной страницей: docs/PARITY.md.
Журнал решений с обоснованием каждого отклонения: docs/porting-notes.md.
# 1. эталонный бинарник из оригинальных исходников (нужен g++-14)
ref-build/build.sh # -> ref-build/build/ltc
# 2. гейт M0: пересборка эталона, корпус, 56 золотых файлов, детерминизм
scripts/m0_gate.sh # ~10 с, печатает PASS/FAIL
scripts/m0_gate.sh --no-build # ~3 с, без пересборки эталона
# 3. порт и его гейт (CLI 1:1 против эталона)
cmake -S port -B port/build -G Ninja && cmake --build port/build
scripts/m1_gate.sh # ~13 с: сборка, 345 юнит-проверок, 44 CLI-сценария
scripts/m2_gate.sh # ~35 с: побайтовый паритет, фазз, ASan/UBSan
scripts/m3_gate.sh # ~37 с: декодирование, -view, round-trip, обрезки
scripts/m4_gate.sh # ~60 с: gcc+clang, инварианты, документация
scripts/all_gates.sh # вся приёмка: M0 → M4
scripts/m2_gate.sh --fuzz 1500 # глубокий фазз
port/build/ltc -helpСтатус. M1: CLI повторяет эталон. M2: компрессор совпадает побайтово — 56 золотых файлов, 1 500 фазз-случаев, файлы по 1 МБ. M3: декодер и
-view— 56/56 распаковок, round-trip порт→порт байт в байт, CLI-гейт 51 OK / 0 FAIL / 0 SKIP. Инструмент полностью работоспособен; M4 — инварианты, второй компилятор и финальная документация.Задокументированные расхождения (все — только на некорректных входах, см.
docs/PARITY.md): поток.ltc, оборванный посреди токена (эталон достраивает его из неинициализированного стека); мусор, ссылающийся в незаписанную часть окна; имена путей длиннее 255 символов (в оригинале — переполнение стека). На корректных файлах расхождений нет.Проверено без предупреждений под
g++-14иclang++(-Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wsign-conversion -Wcast-qual -Wold-style-cast … -Werror).
docs/
ANALYSIS-4.md рекогносцировка: формат .ltc, квирки, замеры, план
roadmap.md милстоуны и гейты
porting-notes.md журнал решений (каждое отклонение с обоснованием)
decode.py независимый декодер (проверка спецификации формата)
dump_tokens.py разбор .ltc на токены + инварианты формата
PARITY.md что именно проверено, и где порт сознательно отличается
ref-build/ эталон: оригинальные исходники + 2 правки под gcc + шимы
src/ LTC/ (275+441 строк) + ltamgr/ (~1500) + sdk/inc
shim/, build.sh, autocompat.py, PATCHES.md
golden/
make_corpus.py 56 fixture'ов, детерминированы seed'ами
generate.py пересборка золотых файлов эталоном + сверка (--check)
fixtures/ 1.03 МБ входных данных
expected/ 56 золотых .ltc (459 КБ)
manifest.txt размеры и хэши входа/выхода по каждому файлу
expected.sha256 контрольные суммы золотых .ltc
scripts/
m0_gate.sh гейт M0
m1_gate.sh гейт M1
m2_gate.sh гейт M2
m3_gate.sh гейт M3
m4_gate.sh гейт M4 (два компилятора, инварианты, документация)
all_gates.sh все гейты по порядку
cli_parity.sh дифференциальный CLI-гейт: эталон против порта
fuzz_parity.py фазз-сверка компрессора (детерминированные seed'ы)
truncation_sweep.py свип по обрезкам: граница детерминизма эталона
check_docs.py согласованность документации с деревом
port/ порт на C++20 + тесты
include/ltc/, src/ bits, file, lzss_window, lzss_decoder, compressed_stream, command_line, paths, app
tests/test_m1.cpp 345 проверок без внешних зависимостей
tests/test_m2.cpp 37 проверок: векторы компрессора, инварианты, повторная инициализация
tests/test_m3.cpp 25 проверок: векторы декодера, перекрывающийся span, обрезки
tests/test_m4.cpp 897 проверок: константы формата, обратимость битового слоя,
инварианты span'ов/offset/EOS на 84 случаях, пределы парсера
Биты внутри байта — от младшего к старшему, многобитные поля — старшим битом вперёд.
32 бита версии (всегда 0), затем токены: 1 + 8 бит = литерал, 0 + 12 бит offset +
4 бита (len−2) = span длиной 2…17, offset 0 = конец файла. Последний неполный байт
потока теряется — поэтому пустой файл занимает 6 байт, а не 7. Полная
спецификация с проверкой независимым декодером — docs/ANALYSIS-4.md §4.
Оригинальные исходники — © Monolith Productions / Touchdown Entertainment
(github.com/jsj2008/lithtech), см. NOTICE.md. Порт, тесты и документация — этого проекта.