22t: two-factor laws REFUTED by descent (quad Q->0, resrp rp->0 — geometry already in lvl=am/res); real render of sim-optimum 7.6/1.694 = 1.898 with group regressions, canon stays; error budget: dual = 62% of corpus abs-error -> inter-band acc/f6f8 consumer is priority #1

This commit is contained in:
2026-08-23 15:10:06 +03:00
parent 3857205a6b
commit 376bd955c9
3 changed files with 173 additions and 53 deletions
+24
View File
@@ -245,6 +245,17 @@ static void process_band_structural(
} }
} }
// RT_RESPRP=1 (NOTES 22t): keep ONLY the res^rp factor of the warp cascade
// while NOWARP skips the full kBand768*kWarp*res^rp blanket. Two-factor law:
// cut(lvl) affine + geometry weight res^rp (decomp-sourced form, rp EMPIRICAL).
static const int resrp_only = getenv("RT_RESPRP") ? atoi(getenv("RT_RESPRP")) : 0;
if (nowarp && resrp_only) {
for (size_t k = 0; k < nbin; k++) {
double res_k = std::max(static_cast<double>(res[k]), 1e-12);
mask_out[k] *= std::pow(res_k, rp);
}
}
// Step 9 (NOTES_LEVEL:830 + consumers_out.txt:955-1075): IIR3 inline, // Step 9 (NOTES_LEVEL:830 + consumers_out.txt:955-1075): IIR3 inline,
// TWO bidirectional passes [reset, forward, backward] x2 (state persists // TWO bidirectional passes [reset, forward, backward] x2 (state persists
// from forward into backward within a pair; reset between pairs). // from forward into backward within a pair; reset between pairs).
@@ -330,6 +341,11 @@ void FramedDetector::setParams(const std::vector<DetectorBand>& bands) {
res_.clear(); res_.clear();
track_.clear(); track_.clear();
// RT_DUMPRESPATH=<file> (NOTES 22t): static twin-response spectra per band,
// binary {int32 band, int32 nbin, float res[nbin]} records (append).
FILE* rp_dump = nullptr;
if (const char* dp = getenv("RT_DUMPRESPATH")) rp_dump = fopen(dp, "ab");
for (const auto& b : bands_) { for (const auto& b : bands_) {
std::vector<float> r(half + 1, 1.0f); std::vector<float> r(half + 1, 1.0f);
float sens_lin = std::pow(10.0f, b.sens * SENS_SCALE / 20.0f); float sens_lin = std::pow(10.0f, b.sens * SENS_SCALE / 20.0f);
@@ -348,8 +364,16 @@ void FramedDetector::setParams(const std::vector<DetectorBand>& bands) {
r[k] = std::sqrt(out[k].re * out[k].re + out[k].im * out[k].im); r[k] = std::sqrt(out[k].re * out[k].re + out[k].im * out[k].im);
r[k] = std::max(r[k], 1e-12f); r[k] = std::max(r[k], 1e-12f);
} }
if (rp_dump) {
int32_t bi = static_cast<int32_t>(res_.size());
int32_t nb = static_cast<int32_t>(r.size());
fwrite(&bi, sizeof(int32_t), 1, rp_dump);
fwrite(&nb, sizeof(int32_t), 1, rp_dump);
fwrite(r.data(), sizeof(float), r.size(), rp_dump);
}
res_.push_back(std::move(r)); res_.push_back(std::move(r));
} }
if (rp_dump) fclose(rp_dump);
track_.assign(bands_.size(), std::vector<float>(half + 1, 1.0f)); track_.assign(bands_.size(), std::vector<float>(half + 1, 1.0f));
} }
+42
View File
@@ -2016,3 +2016,45 @@ x_i=dA*/dS — точная эффективная координата ке
не решается, подтверждено покейсовой бисекцией. не решается, подтверждено покейсовой бисекцией.
3. Реплей-инфраструктура готова для быстрого перебора ЛЮБЫХ параметрических законов 3. Реплей-инфраструктура готова для быстрого перебора ЛЮБЫХ параметрических законов
(2 c корпус) — использовать вместо реальных рендеров до финальной валидации. (2 c корпус) — использовать вместо реальных рендеров до финальной валидации.
## ============ UPDATE 2026-08-23b (22t): ДВУХФАКТОРНЫЕ ЗАКОНЫ ОТВЕРГНУТЫ — БЮДЖЕТ ОШИБКИ В DUAL ============
Инструменты расширены:
- `RT_DUMPRESPATH=<file>` в FramedDetector::setParams — статические спектры twin-response
res[band][bin] ({i32 band,i32 nbin,f32[]}, append); lawfit collect кладёт их в npz рядом с lvl.
- `RT_RESPRP=1` в process_band_structural — применять res^rp при NOWARP (для реальных
рендеров двухфакторного закона).
- `lawfit22r.py fit`: координатный спуск по семействам законов на реплее (2 c/корпус).
### Результат фит-семейств (офлайн, старт 7.6/1.6)
| семейство | A | S | Q | rp | TOTAL(sim) |
|-----------|---|---|---|----|-----------|
| scalar | 7.600 | 1.694 | 0 | 0 | **1.860** |
| quad (Q·x²) | →scalar | | **→0** | | 1.860 |
| resrp (mask·res^rp) | →scalar | | | **→0** | 1.860 |
| quad+resrp | 7.452 | 1.540 | +0.069 | 0 | 1.865 |
ОБА вторых фактора отброшены спуском: кривизна не нужна, res-вес не нужен.
Объяснение resrp: lvl = am/res УЖЕ содержит геометрию твин-юбки; домножение на
res^rp сверху — двойной учёт (в каноне res^rp компенсировал другое — отсутствие
res в xv-домене LUT).
### Реальный рендер скалярного оптимума (7.6, 1.694)
t1kq 0.954 / t1k 0.842 / al 0.799 / res **0.117** / dual **3.246** / comb 4.855
⇒ TOTAL **1.898** (sim 1.860 + стат. смещения реплея ~0.04). Против HEAD 1.931:
выигрыш мизерный, t1kq/t1k/comb РЕГРЕССИРУЮТ ⇒ гейт не пройден, канон НЕ тронут.
### Бюджет ошибки — где лежит остаток (решающий факт)
abs-сумма по корпусу при (7.6,1.694): 115.4 дБ·кейсов, из них:
- **dual: 22 кейса × 3.246 = 71.4 (62%!)** — межполосный механизм (Шаг 2/9, консюмер acc/f6f8);
- comb: 24.3 (21%) — мультиполосное взаимодействие;
- всё прочее: ~20 (17%).
Если бы dual был на уровне bridge (0.726), TOTAL упал бы до ~0.96. Никакая
донастройка закона это не даст — ПРИОРИТЕТ №1 подтверждён количественно:
искать точку потребления acc/f6f8 (межполосный каскад), а не крутить маску.
### NEXT
1. dual/межполосный консюмер: декомп-охота вокруг FUN_180529fe0 callers
(52e260 render-loop, multiband combine), трассировка acc/f6f8 в живом плагине.
2. comb: после dual — пере-тест 4-полосного взаимодействия.
3. Реплей-инфра готова; для любых новых гипотез сначала офлайн (2 c), потом рендер.
+102 -48
View File
@@ -91,10 +91,14 @@ def collect():
store = {} store = {}
traj_bin = '/tmp/opencode/lawfit_traj.bin' traj_bin = '/tmp/opencode/lawfit_traj.bin'
resp_bin = '/tmp/opencode/lawfit_res.bin'
for key, u in sorted(uniq.items()): for key, u in sorted(uniq.items()):
if os.path.exists(traj_bin): if os.path.exists(traj_bin):
os.remove(traj_bin) os.remove(traj_bin)
env = {**os.environ, **BASE_ENV, 'RT_DUMP_ALL': traj_bin} if os.path.exists(resp_bin):
os.remove(resp_bin)
env = {**os.environ, **BASE_ENV, 'RT_DUMP_ALL': traj_bin,
'RT_DUMPRESPATH': resp_bin}
subprocess.run([corpus.RB, u['inp'], '/tmp/opencode/lawfit_out.wav'] + u['args'], subprocess.run([corpus.RB, u['inp'], '/tmp/opencode/lawfit_out.wav'] + u['args'],
capture_output=True, text=True, env=env, capture_output=True, text=True, env=env,
cwd='/home/m/re-tools') cwd='/home/m/re-tools')
@@ -104,9 +108,26 @@ def collect():
continue continue
lvl = arr.reshape(-1, u['nb'], RBIN) # [frame][band][bin] lvl = arr.reshape(-1, u['nb'], RBIN) # [frame][band][bin]
store[key] = lvl store[key] = lvl
print(f'{key}: {lvl.shape[0]} frames x {u["nb"]} bands') # static twin-response spectra per band ({i32 band, i32 nbin, f32[nbin]})
res = None
if os.path.exists(resp_bin):
raw = open(resp_bin, 'rb').read()
off = 0
bands = []
while off < len(raw):
bi, nb = struct.unpack_from('<ii', raw, off)
off += 8
r = np.frombuffer(raw, dtype='<f4', count=nb, offset=off)
off += 4 * nb
bands.append(r.astype(np.float32))
if len(bands) == u['nb'] and all(b.size == RBIN for b in bands):
res = np.stack(bands)
if res is not None:
store[key + '|res'] = res
print(f'{key}: {lvl.shape[0]} frames x {u["nb"]} bands'
+ (' +res' if res is not None else ' NORES'))
np.savez_compressed(TRAJ, **store) np.savez_compressed(TRAJ, **store)
print(f'\nwrote {len(store)} trajectories -> {TRAJ}') print(f'\nwrote {len(store)} entries -> {TRAJ}')
def load_traj(): def load_traj():
@@ -135,16 +156,24 @@ class CaseInput:
return lvl.shape[0] == self.nf return lvl.shape[0] == self.nf
def masks_from_lvl(lvl, nb, A, S): def masks_from_lvl(lvl, nb, A, S, Q=0.0, res=None, rp=0.0):
"""Per-frame lower-half mask exactly as bare chain + LAWAFFINE produces.""" """Per-frame lower-half mask exactly as bare chain + LAWAFFINE produces.
Law families (22t): scalar cut=A+S*log2(lvl); quad adds curvature Q*x^2;
resrp multiplies per-band mask by res^rp (decomp warp-cascade factor,
applied BEFORE cross-band min like the C++ warp section).
"""
nf = lvl.shape[0] nf = lvl.shape[0]
lv64 = lvl.astype(np.float64) lv64 = lvl.astype(np.float64)
mm = np.exp2(-((A + S * np.log2(np.maximum(lv64, 1e-12))) / 6.0174)) x = np.log2(np.maximum(lv64, 1e-12))
mm = np.exp2(-((A + S * x + Q * x * x) / 6.0174))
low = lv64 <= 1e-6 # C++ fallback: exp2(-level) low = lv64 <= 1e-6 # C++ fallback: exp2(-level)
if low.any(): if low.any():
mm[low] = np.exp2(-lv64[low]) mm[low] = np.exp2(-lv64[low])
if res is not None and rp:
mm = mm * res[None].astype(np.float64) ** rp
if nb > 1: if nb > 1:
lo = np.min(mm, axis=1) lo = np.min(mm, axis=1) # min across bands
else: else:
lo = mm[:, 0, :] lo = mm[:, 0, :]
full = np.ones((nf, NFFT)) full = np.ones((nf, NFFT))
@@ -153,10 +182,10 @@ def masks_from_lvl(lvl, nb, A, S):
return full return full
def replay(ci, lvl, nb, A, S): def replay(ci, lvl, nb, A, S, Q=0.0, res=None, rp=0.0):
"""Return trimmed 44.1k output for law (A,S) on prepared CaseInput ci.""" """Return trimmed 44.1k output for law params on prepared CaseInput ci."""
assert ci.frames_ok(lvl), f'traj {lvl.shape[0]} != frames {ci.nf}' assert ci.frames_ok(lvl), f'traj {lvl.shape[0]} != frames {ci.nf}'
full = masks_from_lvl(lvl, nb, A, S) full = masks_from_lvl(lvl, nb, A, S, Q, res, rp)
segs = ci.xext[ci.offs[:, None] + np.arange(NFFT)[None, :]] * _WIN[None, :] segs = ci.xext[ci.offs[:, None] + np.arange(NFFT)[None, :]] * _WIN[None, :]
yspec = np.fft.fft(segs, axis=1) * full yspec = np.fft.fft(segs, axis=1) * full
td = np.real(np.fft.ifft(yspec, axis=1)) td = np.real(np.fft.ifft(yspec, axis=1))
@@ -203,20 +232,18 @@ def build_eval_index(trajs):
return idx return idx
def sim_errors(trajs, idx, A, S, ref_cache=None): def sim_errors(trajs, idx, A, S, Q=0.0, rp=0.0):
errs = {} errs = {}
ycache = {} ycache = {}
for e in idx: for e in idx:
ck = (e['key'], e['nb']) ck = (e['key'], e['nb'], round(Q, 6), round(rp, 6))
if ck not in ycache: if ck not in ycache:
ci = case_input(e['key'], e['inp']) ci = case_input(e['key'], e['inp'])
ycache[ck] = replay(ci, trajs[e['key']], e['nb'], A, S) ycache[ck] = replay(ci, trajs[e['key']], e['nb'], A, S,
Q, trajs.get(e['key'] + '|res'), rp)
y44 = ycache[ck] y44 = ycache[ck]
if ref_cache is None:
errs[e['name']] = corpus.db(corpus.ta(y44, e['f'])) - \ errs[e['name']] = corpus.db(corpus.ta(y44, e['f'])) - \
corpus.db(corpus.ta(corpus.load_mono(e['ref']), e['f'])) corpus.db(corpus.ta(corpus.load_mono(e['ref']), e['f']))
else:
errs[e['name']] = corpus.db(corpus.ta(y44, e['f'])) - ref_cache[e['name']]
return errs return errs
@@ -250,45 +277,72 @@ def sanity(A, S, json_path):
def fit(A0, S0): def fit(A0, S0):
trajs = load_traj() trajs = load_traj()
idx = build_eval_index(trajs) idx = build_eval_index(trajs)
fit2(trajs, idx, A0, S0)
def objective(A, S, sub=None):
def fit2(trajs, idx, A0, S0):
"""Law-family comparison: scalar / quad / resrp / quad+resrp (NOTES 22t)."""
def objective(p, sub=None):
A, S, Q, rp = p
ii = idx if sub is None else sub ii = idx if sub is None else sub
return group_stats(sim_errors(trajs, ii, A, S)) return group_stats(sim_errors(trajs, ii, A, S, Q, rp))
best = (A0, S0) def descend(p0, steps, sub=None, label=''):
bst = objective(*best) best = list(p0)
print(f'start A={A0} S={S0}: TOTAL={bst["TOTAL"]:.3f}') bst = objective(tuple(best), sub)
stepA, stepS = 0.8, 0.25 print(f'{label} start {[round(v, 4) for v in best]}: '
for it in range(4): f'TOTAL={bst["TOTAL"]:.3f}', flush=True)
while all(s > 1e-4 for s in steps.values()):
moved = False moved = False
for A, S in [(best[0] + stepA, best[1]), (best[0] - stepA, best[1]), for i, nm in enumerate(['A', 'S', 'Q', 'rp']):
(best[0], best[1] + stepS), (best[0], best[1] - stepS)]: if nm not in steps:
st = objective(A, S) continue
mark = '' st = steps[nm]
if st['TOTAL'] < bst['TOTAL'] - 1e-4: for d in (+st, -st):
best, bst = (A, S), st cand = list(best)
cand[i] = round(cand[i] + d, 6)
if cand[3] < 0 or (nm == 'Q' and abs(cand[2]) > 3):
continue
s2 = objective(tuple(cand), sub)
if s2['TOTAL'] < bst['TOTAL'] - 1e-4:
best, bst = cand, s2
moved = True moved = True
mark = ' *' print(f' {label} {nm}{d:+.4g}: TOTAL={s2["TOTAL"]:.3f} '
print(f' [{it}] A={A:+7.3f} S={S:+6.3f}: TOTAL={st["TOTAL"]:.3f}{mark}') f'{[round(v, 4) for v in best]}', flush=True)
if not moved: if not moved:
stepA /= 2 for nm in steps:
stepS /= 2 steps[nm] /= 2
if stepA < 0.05: return best, bst
break
print(f'\nBEST global: A={best[0]:.3f} S={best[1]:.3f} TOTAL={bst["TOTAL"]:.3f}')
for k, v in bst.items():
print(f' {k:>6}: {v:.3f}')
print('\n=== per-group greedy optima (grid around global best) ===') res_all = {}
for g in ['t1kq', 't1k', 'al', 'res', 'dual', 'comb']:
sub = [e for e in idx if e['grp'] == g] b, t = descend([A0, S0, 0.0, 0.0], {'A': 0.4, 'S': 0.15}, label='[scalar]')
bA, bS, bval = None, None, 1e9 res_all['scalar'] = (list(b), dict(t))
for A in np.arange(best[0] - 2.5, best[0] + 2.51, 0.5):
for S in np.arange(max(0.25, best[1] - 1.0), best[1] + 1.01, 0.25): bA, bS, _, _ = res_all['scalar'][0]
st = objective(float(A), float(S), sub) b, t = descend([bA, bS, 0.0, 0.0], {'A': 0.3, 'S': 0.15, 'Q': 0.06},
if st[g] < bval: label='[quad]')
bA, bS, bval = float(A), float(S), st[g] res_all['quad'] = (list(b), dict(t))
print(f'{g:>6}: A={bA:6.2f} S={bS:5.2f} mean|e|={bval:.3f}', flush=True)
b, t = descend([bA, bS, 0.0, 0.03], {'A': 0.3, 'S': 0.15, 'rp': 0.01},
label='[resrp]')
res_all['resrp'] = (list(b), dict(t))
bA, bS, bQ, _ = res_all['quad'][0]
b, t = descend([bA, bS, bQ, 0.03], {'A': 0.25, 'S': 0.12, 'Q': 0.05,
'rp': 0.008}, label='[quad+resrp]')
res_all['quad+resrp'] = (list(b), dict(t))
print('\n================ LAW FAMILY SUMMARY ================')
for fam, (p, st) in res_all.items():
gs = ' '.join(f'{g}={st[g]:.3f}' for g in
['t1kq', 't1k', 'al', 'res', 'dual', 'comb'])
print(f'{fam:>12}: A={p[0]:7.3f} S={p[1]:6.3f} Q={p[2]:+6.3f} '
f'rp={p[3]:5.3f} TOTAL={st["TOTAL"]:.3f}\n{"":>14}{gs}')
json.dump({f: {'params': p, 'groups': s} for f, (p, s) in res_all.items()},
open('/tmp/opencode/lawfit_fit2.json', 'w'), indent=1)
print('\nwrote /tmp/opencode/lawfit_fit2.json')
def freq_of(name): def freq_of(name):