Files
admin 0593ff7e62
nginx serve /uploads direct (audio speed) / patch-nginx (push) Failing after 1m34s
数字我 Digital Me:全量入库(大文件走 Git LFS)
2026-10-10 16:24:43 +08:00

146 lines
8.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ★★★★★ 2026-07-17 第三刀:【gapOf —— 「排在后面 ≠ 不参选」这一族的第八次】
//
// 【机理,先说死,它是算术,不是史学】
// 六轴的取值是 sat() 饱和曲线的产物,【只有五个值】:40 / 63 / 73 / 79 / 83(本刀实测)。
// ⇒ (40,63] 之间【没有任何一个 profile 落得进去】。
// ⇒ 【gate{wu:41} 与 gate{wu:55} 与 gate{wu:63} 是【同一道闸】】—— 开/关逐格相同,零例外。
// ★ 即:55 这个数【作为闸是一个空操作】—— 本文件自己也这么说:
// 「因此 gate 的口径是:55 ≈「这条轴上至少点过一项」,70 ≈「至少两项」。」
// ⇒ ★★【它自陈是一个【代号】,不是一个刻度。】
//
// 【而它在另一个地方【被当成刻度读了】】
// `gapOf(t) = Math.max(...t.missing.map((m) => m.need - m.got))`
// ⇒ 【need − got】。★ need 是一个代号,got 是一个曲线的产物 —— 两者相减【不是一段距离】。
// ⇒ 而 nearest 的第二级就是拿它排的。⇒ 【一个自陈是代号的数,在暗处裁决了「你离哪条路最近」。】
//
// 本 census 分三问,逐问都拿引擎自己的产物答:
// ① 【同一道闸的那几个数,开/关变几格?】(应为 0 —— 若不为 0,我上面的机理是错的)
// ② 【而它们让 nearest 变几格?】
// ③ 【gapOf 裁决的时候,比的是不是同一条轴?】(同 lexCompare 的那一问,实测同轴 0 次)
import { DYNASTIES, computeRank, computeAxes, AXIS_IDS } from '../src/dynasties.js';
import { chushenOf } from '../src/chushen.js';
import { SUB_REGISTRY } from '../src/talents.js';
const SUBS = SUB_REGISTRY.map((s) => s.id);
const WANGS = [0, 29, 30, 54, 55, 74, 75, 100];
const TUPLES = (() => {
const m = new Map();
for (let b = 0; b < (1 << 16); b++) {
const subs = []; for (let i = 0; i < 16; i++) if (b & (1 << i)) subs.push(SUBS[i]);
const a = computeAxes(subs);
m.set(AXIS_IDS.map((x) => a[x]).join(','), (m.get(AXIS_IDS.map((x) => a[x]).join(',')) || 0) + 1);
}
return [...m].map(([k, w]) => ({ axes: Object.fromEntries(k.split(',').map((v, i) => [AXIS_IDS[i], Number(v)])), w }));
})();
const CONFIGS = {};
for (const d of DYNASTIES) { const dim = chushenOf(d.id); CONFIGS[d.id] = dim ? [null, ...dim.values.map((v) => v.id)] : [null]; }
const N = (n) => n.toLocaleString();
const pad = (s, n) => { s = String(s); let w = 0; for (const ch of s) w += ch.charCodeAt(0) > 0x2000 ? 2 : 1; return s + ' '.repeat(Math.max(1, n - w)); };
// ★ 六轴的全部可能取值 —— 让引擎自己报,不许我手写
const VALS = {}; for (const a of AXIS_IDS) VALS[a] = [...new Set(TUPLES.map((t) => t.axes[a]))].sort((x, y) => x - y);
console.log('══ ① 六轴的取值:sat() 曲线只给得出这几个数 ══');
for (const a of AXIS_IDS) console.log(` ${pad(a, 6)} ${VALS[a].join(' ')}`);
// 一道闸 need=d 的【等价类】:所有让「开/关」逐格相同的整数 n
const classOf = (axis, d) => {
const vs = VALS[axis];
const passes = (n) => vs.filter((v) => v >= n).join(',');
const me = passes(d);
const out = [];
for (let n = 1; n <= 100; n++) if (passes(n) === me) out.push(n);
return out;
};
console.log('\n══ ② 每一道六轴闸的【等价类】—— 类里的任何一个数,是【同一道闸】 ══');
console.log(pad('闸', 34) + pad('等价类(这些数【是同一道闸】)', 34) + '类的大小');
console.log('─'.repeat(84));
const GATES = [];
for (const d of DYNASTIES) for (const c of d.channels) for (const [k, need] of Object.entries(c.gate || {})) {
if (k === 'wang' || k === 'lao') continue; // wang 是 0–100 的自由值,不是 sat 的产物 ⇒ 它的数是真的
const cls = classOf(k, need);
GATES.push({ d, c, k, need, cls });
console.log(pad(`${d.id}/${c.id} gate{${k}:${need}}`, 34) + pad(`${cls[0]}–${cls[cls.length - 1]}`, 34) + cls.length);
}
// ── 跑:把每一道闸换成它等价类里的每一个端点,看【开/关】与【nearest】各变几格
console.log('\n══ ③ 换成等价类里的另一个数:【开/关】变几格?【nearest】变几格? ══');
console.log(pad('闸 → 换成', 40) + '开/关变'.padStart(10) + 'nearest 变'.padStart(12) + ' ★');
console.log('─'.repeat(72));
const scan = (dynId, cs) => {
const out = [];
for (const t of TUPLES) for (const wang of WANGS) {
const r = computeRank({ axes: t.axes, wang, chushen: cs }, dynId);
// 【开/关】= 哪几条路对你开着(引擎自己的 kai + blocked)
const gates = r.kai.map((k) => k.channelId).sort().join(',') + '#' + r.blocked.map((b) => b.channel).sort().join(',');
out.push({ w: t.w, gates, near: r.nearest || '-' });
}
return out;
};
const BASE = {};
for (const d of DYNASTIES) for (const cs of CONFIGS[d.id]) BASE[`${d.id}|${cs}`] = scan(d.id, cs);
let deadGate = 0, movedNear = 0;
for (const g of GATES) {
for (const alt of [g.cls[0], g.cls[g.cls.length - 1]]) {
if (alt === g.need) continue;
const old = g.c.gate[g.k];
g.c.gate[g.k] = alt;
let dg = 0, dn = 0;
for (const cs of CONFIGS[g.d.id]) {
const b = BASE[`${g.d.id}|${cs}`], now = scan(g.d.id, cs);
for (let i = 0; i < b.length; i++) { if (b[i].gates !== now[i].gates) dg += b[i].w; if (b[i].near !== now[i].near) dn += b[i].w; }
}
g.c.gate[g.k] = old;
if (dg === 0) deadGate++;
if (dn > 0) movedNear += dn;
console.log(pad(`${g.d.id}/${g.c.id} {${g.k}:${g.need}→${alt}}`, 40) + N(dg).padStart(10) + N(dn).padStart(12) +
' ' + (dg === 0 ? '★ 同一道闸(0 格)' : '⚠ 不是同一道闸!') + (dn > 0 ? ' ★★ 而 nearest 变了' : ''));
}
}
console.log(`\n★★★【${deadGate} 处:换个数,开/关一格都不动 —— 它作为闸是空操作】`);
console.log(`★★★【而 nearest 一共变了 ${N(movedNear)} 格 —— 那个空操作在暗处裁决了「你离哪条路最近」】`);
// ── ④ gapOf 裁决的时候,比的是不是同一条轴?
console.log('\n══ ④ 【无路者】那一支:nearest 是谁判的?gapOf 比的是不是同一条轴? ══');
const gapOf = (ms) => (ms.length ? Math.max(...ms.map((m) => m.need - m.got)) : 0);
const worstAxis = (ms) => { let b = null; for (const m of ms) if (!b || m.need - m.got > b.need - b.got) b = m; return b; };
for (const d of DYNASTIES) for (const cs of CONFIGS[d.id]) {
const S = { noRoad: 0, byCount: 0, byGap: 0, byIdx: 0, gapSameAxis: 0, gapCrossAxis: 0 };
const pairs = new Map();
for (const t of TUPLES) for (const wang of WANGS) {
const r = computeRank({ axes: t.axes, wang, chushen: cs }, d.id);
if (r.channel) continue;
S.noRoad += t.w;
if (!r.nearest) continue;
// 复算这一支的三级(★ 与 computeRank 的 sort 逐字同源),只为回答「是哪一级判的」
const dd = DYNASTIES.find((x) => x.id === d.id);
const rows = [];
dd.channels.forEach((c, idx) => {
const val = (k) => (k === 'wang' ? wang : t.axes[k]);
const ms = Object.entries(c.gate || {}).filter(([k, n]) => val(k) < n).map(([k, n]) => ({ axis: k, need: n, got: Math.round(val(k)) }));
rows.push({ name: c.name, ms, idx });
});
const pool = rows.filter((x) => r.blocked.some((b) => b.channel === x.name) && !r.blocked.find((b) => b.channel === x.name).byChushen);
if (pool.length < 2) continue;
const win = pool.find((x) => x.name === r.nearest);
if (!win) continue;
const rivals = pool.filter((x) => x !== win);
if (rivals.every((x) => x.ms.length > win.ms.length)) { S.byCount += t.w; continue; }
const same = rivals.filter((x) => x.ms.length === win.ms.length);
if (same.every((x) => gapOf(x.ms) > gapOf(win.ms))) {
S.byGap += t.w;
for (const x of same) {
const a = worstAxis(win.ms), b = worstAxis(x.ms);
if (a && b) { if (a.axis === b.axis) S.gapSameAxis += t.w; else { S.gapCrossAxis += t.w; const k = `${a.axis}(差${a.need - a.got}) vs ${b.axis}(差${b.need - b.got})`; pairs.set(k, (pairs.get(k) || 0) + t.w); } }
}
continue;
}
if (same.some((x) => gapOf(x.ms) === gapOf(win.ms))) S.byIdx += t.w;
}
if (!S.noRoad) continue;
console.log(`\n── ${d.name}(chushen=${cs || '未答'})一条路都不开:${N(S.noRoad)} 格`);
console.log(` nearest 由【差几条】判:${N(S.byCount)} 由【gapOf 缺口】判:${N(S.byGap)} 由【本表的编号】判:${N(S.byIdx)}`);
if (S.byGap) console.log(` ★ gapOf 裁决时:【同轴 ${N(S.gapSameAxis)} 次】/【跨轴 ${N(S.gapCrossAxis)} 次】`);
for (const [k, v] of [...pairs].sort((a, b) => b[1] - a[1]).slice(0, 5)) console.log(` ${pad(k, 40)} ${N(v)}`);
}