fix(tci,cat): резерв бюджета переезжает писателю; строгий разбор полей ZZ-команд

Три замечания по 79132f1.

1. [P2] Лимит памяти рекордеров обходился при SAVE. Take собирал сплошную
   копию записи, а DropData тут же возвращал исходные куски в бюджет —
   копия жила дальше в асинхронном TTCIWavWriter уже неучтённой. Один SAVE
   поднимал настоящее потребление примерно вдвое, а на медленном или
   зависшем сетевом каталоге очередь writer-потоков и их буферов росла
   мимо потолка вовсе. Теперь копия НАСЛЕДУЕТ резерв кусков, из которых
   собрана: Take отдаёт его out-параметром Reserved, писатель держит до
   конца записи и отпускает в ReleaseData — ровно один раз, сколько бы
   путей выхода ни было у Execute. Новых денег у бюджета копия не берёт,
   так что потолок теперь считает и очередь сохранений тоже.

2. [P2] Пять ZZ-команд проверяли длину поля, но не содержимое:
   StrToIntDef(s, 0) превращал любую нечисловую пару символов в индекс 0.
   ZZBSxx; переключал диапазон на нулевой вместо ?;, ZZBMxx; и
   ZZAUxx;/ZZBPxx; двигали VFO, ZZFIxx; выбирал фильтр 0. Разбор приведён
   к идиоме ZZFL/ZZFH: TryStrToInt, иначе ошибка формата.

3. [P3] Сообщение об отказе запуска TCI звало в Settings → Advanced, а
   настройки там уже не живут — вкладка CAT (переезд был в de0830f).

Стенд 194/194: Take отдаёт резерв размером с копию, после Take занят ровно
он, писатель возвращает его по окончании. Без фикса первая проверка
краснеет. GUI (--ws=qt6) и демон зелёные. doc/TCI.md §2.5 и сводка стенда,
doc/CAT_STATUS.md — таблица разбора.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 12:50:21 +03:00
co-authored by Claude Opus 5
parent 79132f133c
commit 9b77809a73
7 changed files with 119 additions and 31 deletions
+6 -2
View File
@@ -2999,6 +2999,7 @@ var
Rx, Sec, Rate: Integer;
Path, Req: string;
Data: TTCIPcm;
Held: Int64; // байты бюджета, уходящие писателю вместе с Data
Old, New_: TTCIRecorder;
begin
if not TCITryArgInt(M, 0, Rx) or not ValidRx(Rx) then
@@ -3085,6 +3086,7 @@ begin
// потом снимаем с него данные: копия кольца — это десятки мегабайт, и делать
// её под локом DSP-потока нельзя.
Data := nil;
Held := 0;
Rate := TCI_AUDIO_ENGINE_RATE;
FStreamLock.Enter;
try
@@ -3095,7 +3097,9 @@ begin
end;
if Old <> nil then
begin
Data := Old.Take;
// ★Резерв бюджета переезжает вместе с данными: копию держит писатель, и
// отпустит её он же. Иначе SAVE был бы дырой в потолке памяти.
Data := Old.Take(Held);
Rate := Old.Rate;
Old.Free;
end;
@@ -3106,7 +3110,7 @@ begin
end;
// Пишет отдельный поток: файл может быть в десятки мегабайт, а мы сейчас в
// потоке клиента, который в это время не читает свой сокет.
TTCIWavWriter.Create(Path, Data, Rate);
TTCIWavWriter.Create(Path, Data, Rate, Held);
end;
procedure TTCIAdapter.DropClientRecorders(C: TTCIClient);