diff --git a/modules/variables/variables-core.js b/modules/variables/variables-core.js index d46495b..f9d2f76 100644 --- a/modules/variables/variables-core.js +++ b/modules/variables/variables-core.js @@ -160,57 +160,6 @@ function normalizeOpName(k) { return OP_MAP[String(k).toLowerCase().trim()] || null; } -function parseBumpSpec(payload) { - try { - if (payload && typeof payload === 'object' && payload.kind && Number.isFinite(Number(payload.value))) { - return { kind: String(payload.kind), value: Number(payload.value), raw: payload.raw }; - } - - if (typeof payload === 'number' && Number.isFinite(payload)) { - return { kind: 'delta', value: payload, raw: payload }; - } - - const raw = String(payload ?? '').trim(); - if (!raw) return null; - - const num = Number(raw); - if (Number.isFinite(num) && !/[*/^%]/.test(raw)) { - return { kind: 'delta', value: num, raw }; - } - - const mPct = raw.match(/^([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)\s*%$/); - if (mPct) return { kind: 'percent', value: Number(mPct[1]), raw }; - - const mOp = raw.match(/^([*/^])\s*([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)$/); - if (mOp) { - const op = mOp[1] === '*' ? 'mul' : (mOp[1] === '/' ? 'div' : 'pow'); - return { kind: op, value: Number(mOp[2]), raw }; - } - } catch {} - return null; -} - -function computeBumpResult(payload, currentValue) { - const spec = parseBumpSpec(payload); - if (!spec) return { ok: false, reason: 'delta-nan' }; - - const baseRaw = Number(currentValue); - const base = Number.isFinite(baseRaw) ? baseRaw : 0; - - let next; - if (spec.kind === 'delta') next = base + spec.value; - else if (spec.kind === 'percent') next = base + (base * (spec.value / 100)); - else if (spec.kind === 'mul') next = base * spec.value; - else if (spec.kind === 'div') { - if (!Number.isFinite(spec.value) || spec.value === 0) return { ok: false, reason: 'div-zero', spec }; - next = base / spec.value; - } else if (spec.kind === 'pow') next = Math.pow(base, spec.value); - else return { ok: false, reason: 'delta-nan', spec }; - - if (!Number.isFinite(next)) return { ok: false, reason: 'result-nan', spec }; - return { ok: true, base, next, delta: next - base, spec }; -} - /* ============= 应用签名追踪 ============= */ function getAppliedMap() { @@ -354,12 +303,10 @@ function parseBlock(innerText) { Array.isArray(value) ? arr.push(...value) : arr.push(value); }; const putBump = (top, path, delta) => { - const spec = parseBumpSpec(delta); - if (!spec) return; + const n = Number(String(delta).replace(/^\+/, '')); + if (!Number.isFinite(n)) return; ops.bump[top] ||= {}; - const list = (ops.bump[top][path] ||= []); - if (spec.kind === 'delta') list.push(spec.value); - else list.push(spec); + ops.bump[top][path] = (ops.bump[top][path] ?? 0) + n; }; const putDel = (top, path) => { ops.del[top] ||= []; @@ -790,7 +737,7 @@ function parseBlock(innerText) { const rel = rest.join('.'); if (curOp === 'set') putSet(top, rel, text); else if (curOp === 'push') putPush(top, rel, text); - else if (curOp === 'bump') putBump(top, rel, text); + else if (curOp === 'bump') putBump(top, rel, Number(text)); continue; } @@ -826,7 +773,7 @@ function parseBlock(innerText) { for (const item of arr) putDel(top, rel ? `${rel}.${item}` : item); } else if (curOp === 'bump') { - for (const item of arr) putBump(top, rel, item); + for (const item of arr) putBump(top, rel, Number(item)); } stack.pop(); handledList = true; @@ -865,7 +812,7 @@ function parseBlock(innerText) { putDel(top, target); } } else if (curOp === 'bump') { - putBump(top, rel, stripQ(rhs)); + putBump(top, rel, Number(stripQ(rhs))); } continue; } @@ -901,7 +848,7 @@ function parseBlock(innerText) { } else if (curOp === 'del') { putDel(top, rel ? `${rel}.${val}` : val); } else if (curOp === 'bump') { - putBump(top, rel, val); + putBump(top, rel, Number(val)); } } } @@ -1098,7 +1045,7 @@ function getEffectiveParentNode(p) { /** * 守护验证 */ -export function guardValidate(op, absPath, payload, currentOverride) { +export function guardValidate(op, absPath, payload) { if (guardianState.bypass) return { allow: true, value: payload }; const p = normalizePath(absPath); @@ -1179,19 +1126,10 @@ export function guardValidate(op, absPath, payload, currentOverride) { // 增量操作 if (op === 'bump') { - const effectiveCurrent = (currentOverride !== undefined) ? currentOverride : currentValue; - const computed = computeBumpResult(payload, effectiveCurrent); - if (!computed?.ok) { - if (xbLog.isEnabled?.()) { - try { xbLog.warn(MODULE_ID, `bump payload rejected: path=${p} reason=${computed?.reason || 'delta-nan'}`); } catch {} - } - return { allow: false, reason: computed?.reason || 'delta-nan' }; - } + let d = Number(payload); + if (!Number.isFinite(d)) return { allow: false, reason: 'delta-nan' }; - let d = computed.delta; - const base = computed.base; - - if (effectiveCurrent === undefined) { + if (currentValue === undefined) { if (parentPath) { const lastSeg = p.split('.').pop() || ''; const isIndex = /^\d+$/.test(lastSeg); @@ -1214,16 +1152,16 @@ export function guardValidate(op, absPath, payload, currentOverride) { if (d < -step) d = -step; } - const cur = Number(effectiveCurrent); + const cur = Number(currentValue); if (!Number.isFinite(cur)) { - const baseValue = 0 + d; - const cl = clampNumberWithConstraints(baseValue, node); + const base = 0 + d; + const cl = clampNumberWithConstraints(base, node); if (!cl.ok) return { allow: false, reason: 'number-constraint' }; - setTypeLockIfUnknown(p, cl.value); + setTypeLockIfUnknown(p, base); return { allow: true, value: cl.value }; } - const next = Number.isFinite(base) ? (base + d) : (cur + d); + const next = cur + d; const clamped = clampNumberWithConstraints(next, node); if (!clamped.ok) return { allow: false, reason: 'number-constraint' }; return { allow: true, value: clamped.value }; @@ -1391,7 +1329,11 @@ export function rulesLoadFromTree(valueTree, basePath) { String(t).trim().startsWith('$') ? String(t).trim() : ('$' + String(t).trim()) ); - const targetPath = curAbs ? `${curAbs}.${targetToken}` : targetToken; + const baseNorm = normalizePath(curAbs || ''); + const tokenNorm = normalizePath(targetToken); + const targetPath = (baseNorm && (tokenNorm === baseNorm || tokenNorm.startsWith(baseNorm + '.'))) + ? tokenNorm + : (curAbs ? `${curAbs}.${targetToken}` : targetToken); const absPath = normalizePath(targetPath); const delta = parseDirectivesTokenList(dirs); @@ -2094,15 +2036,19 @@ async function applyVariablesForMessage(messageId) { // BUMP 操作 else if (op.operation === 'bump') { for (const [k, delta] of Object.entries(op.data)) { + const num = Number(delta); + if (!Number.isFinite(num)) continue; + const localPath = joinPath(subPath, k); const absPath = localPath ? `${root}.${localPath}` : root; const stdPath = normalizePath(absPath); - const deltaList = Array.isArray(delta) ? delta : [delta]; - for (const entry of deltaList) { - const spec = parseBumpSpec(entry); - if (!spec) continue; + let allow = true; + let useDelta = num; + const res = guardValidate('bump', stdPath, num); + allow = !!res?.allow; + if (allow && 'value' in res && Number.isFinite(res.value)) { let curr; try { const pth = norm(localPath || ''); @@ -2116,39 +2062,17 @@ async function applyVariablesForMessage(messageId) { } } catch {} - const res = guardValidate('bump', stdPath, spec, curr); - const allow = !!res?.allow; - if (!allow) { - guardDenied++; - if (debugOn && guardDeniedSamples.length < 8) guardDeniedSamples.push({ op: 'bump', path: stdPath }); - continue; - } - const baseNum = Number(curr); const targetNum = Number(res.value); - const useDelta = Number.isFinite(targetNum) - ? (targetNum - (Number.isFinite(baseNum) ? baseNum : 0)) - : (spec.kind === 'delta' ? spec.value : NaN); - - if (!Number.isFinite(useDelta)) { - guardDenied++; - if (debugOn && guardDeniedSamples.length < 8) guardDeniedSamples.push({ op: 'bump', path: stdPath }); - continue; - } - - if (debugOn && spec.kind !== 'delta') { - try { - const baseVal = Number.isFinite(baseNum) ? baseNum : 0; - const nextVal = Number.isFinite(targetNum) ? targetNum : (baseVal + useDelta); - const expr = (spec.raw !== undefined && spec.raw !== null && String(spec.raw).trim() !== '') - ? String(spec.raw) - : `${spec.kind}:${spec.value}`; - xbLog.info(MODULE_ID, `plot-log bump-math path=${stdPath} base=${baseVal} expr=${expr} next=${nextVal}`); - } catch {} - } - - bumpAtPath(rec, norm(localPath || ''), useDelta); + useDelta = (Number.isFinite(targetNum) ? targetNum : num) - (Number.isFinite(baseNum) ? baseNum : 0); } + + if (!allow) { + guardDenied++; + if (debugOn && guardDeniedSamples.length < 8) guardDeniedSamples.push({ op: 'bump', path: stdPath }); + continue; + } + bumpAtPath(rec, norm(localPath || ''), useDelta); } } } @@ -2462,4 +2386,4 @@ export { rulesSetTable, rulesLoadFromMeta, rulesSaveToMeta, -}; +}; \ No newline at end of file