Refactor: update coefficient mapping to a1=-1-r, a2=r+delta and remove 0.5 step

This commit is contained in:
pchang718
2026-05-16 18:17:13 +08:00
parent 9b504b3f8f
commit c717278dee
2 changed files with 34 additions and 61 deletions
+11 -11
View File
@@ -451,11 +451,11 @@
<div class="grid grid-cols-2 gap-2 mb-4">
<div class="min-w-0 bg-white dark:bg-gray-800 border border-slate-100 dark:border-gray-700 rounded-lg p-2 text-center shadow-sm">
<span class="text-[9px] text-slate-400 block font-bold mb-1">r = (a2 - a1 - 1) / 2</span>
<span class="text-[9px] text-slate-400 block font-bold mb-1">r = -1 - a1</span>
<span class="block max-w-full truncate text-sm font-mono font-bold role-text-secondary" :title="formatFineCoeff(currentR)">{{ formatFineCoeff(currentR) }}</span>
</div>
<div class="min-w-0 bg-white dark:bg-gray-800 border border-slate-100 dark:border-gray-700 rounded-lg p-2 text-center shadow-sm">
<span class="text-[9px] text-slate-400 block font-bold mb-1">δ = (a1 + a2 + 1) / 2</span>
<span class="text-[9px] text-slate-400 block font-bold mb-1">δ = a1 + a2 + 1</span>
<span class="block max-w-full truncate text-sm font-mono font-bold role-text-secondary" :title="formatFineCoeff(currentDelta)">{{ formatFineCoeff(currentDelta) }}</span>
</div>
</div>
@@ -463,7 +463,7 @@
<div class="role-bg-warning-soft border role-border-warning-soft rounded-lg p-3">
<p class="text-[10px] role-text-warning leading-relaxed">
<span class="font-bold">運算說明</span><br>
δ = (a1 + a2 + 1) / 2r = (a2 - a1 - 1) / 2更改數值會自動更新圖表桌機可用 ± shift + 滾輪觸控時按住數值面板上下拖曳
a1 = -1 - r, a2 = r + δ更改數值會自動更新圖表桌機可用 ± shift + 滾輪觸控時按住數值面板上下拖曳
</p>
</div>
</div>
@@ -605,21 +605,21 @@
<span class="block max-w-full truncate text-base font-mono font-bold role-text-secondary" :title="formatFixedFineCoeff(fixedCurrentDelta)">{{ formatFixedFineCoeff(fixedCurrentDelta) }}</span>
</button>
<article v-if="!isTouchInput" class="block" :class="{'opacity-40 pointer-events-none': fixedAFineStep === 0.5}">
<article v-if="!isTouchInput" class="block">
<span class="text-[9px] text-slate-400 block font-bold mb-1">r</span>
<div class="grid grid-cols-[2rem_1fr_2rem] gap-1">
<button type="button" @pointerdown.prevent="startFixedA1A2Repeat('r', -1)" @pointerup="stopFixedA1A2Repeat" @pointerleave="stopFixedA1A2Repeat" @pointercancel="stopFixedA1A2Repeat"
class="stepper-button w-full h-[42px] flex items-center justify-center rounded-full role-bg-primary-soft role-hover-primary-soft role-text-primary transition-all duration-150 active:scale-90 text-lg font-bold shadow-sm" :disabled="fixedAFineStep === 0.5">-</button>
class="stepper-button w-full h-[42px] flex items-center justify-center rounded-full role-bg-primary-soft role-hover-primary-soft role-text-primary transition-all duration-150 active:scale-90 text-lg font-bold shadow-sm">-</button>
<input type="number" :value="formatFixedFineCoeff(fixedCurrentR)" :step="fixedAFineStep"
@change="setFixedAFineDirect('r', $event.target.value)" @keydown="onlyNumberKey"
@wheel="handleFixedAFineWheel('r', $event)"
class="min-w-0 w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-xs font-mono text-center text-slate-700 dark:text-gray-300" :disabled="fixedAFineStep === 0.5">
class="min-w-0 w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-xs font-mono text-center text-slate-700 dark:text-gray-300">
<button type="button" @pointerdown.prevent="startFixedA1A2Repeat('r', 1)" @pointerup="stopFixedA1A2Repeat" @pointerleave="stopFixedA1A2Repeat" @pointercancel="stopFixedA1A2Repeat"
class="stepper-button w-full h-[42px] flex items-center justify-center rounded-full role-bg-primary-soft role-hover-primary-soft role-text-primary transition-all duration-150 active:scale-90 text-lg font-bold shadow-sm" :disabled="fixedAFineStep === 0.5">+</button>
class="stepper-button w-full h-[42px] flex items-center justify-center rounded-full role-bg-primary-soft role-hover-primary-soft role-text-primary transition-all duration-150 active:scale-90 text-lg font-bold shadow-sm">+</button>
</div>
</article>
<button v-else type="button" @pointerdown.prevent="startFixedAFineDrag('r', $event)"
class="touch-none select-none w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-center active:border-slate-400 active:ring-2 active:ring-slate-400/20" :class="{'opacity-40 pointer-events-none': fixedAFineStep === 0.5}">
class="touch-none select-none w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-center active:border-slate-400 active:ring-2 active:ring-slate-400/20">
<span class="text-[9px] text-slate-400 block font-bold mb-1">r</span>
<span class="block max-w-full truncate text-base font-mono font-bold text-slate-700 dark:text-gray-300" :title="formatFixedFineCoeff(fixedCurrentR)">{{ formatFixedFineCoeff(fixedCurrentR) }}</span>
</button>
@@ -627,18 +627,18 @@
</div>
<div class="grid grid-cols-2 gap-2 mb-4">
<div class="min-w-0 bg-white dark:bg-gray-800 border border-slate-100 dark:border-gray-700 rounded-lg p-2 text-center shadow-sm">
<span class="text-[9px] text-slate-400 block font-bold mb-1">r = (a2 - a1 - a0) / 2</span>
<span class="text-[9px] text-slate-400 block font-bold mb-1">r = -a0 - a1</span>
<span class="block max-w-full truncate text-sm font-mono font-bold role-text-secondary" :title="formatFixedFineCoeff(fixedCurrentR)">{{ formatFixedFineCoeff(fixedCurrentR) }}</span>
</div>
<div class="min-w-0 bg-white dark:bg-gray-800 border border-slate-100 dark:border-gray-700 rounded-lg p-2 text-center shadow-sm">
<span class="text-[9px] text-slate-400 block font-bold mb-1">δ = (a1 + a2 + a0) / 2</span>
<span class="text-[9px] text-slate-400 block font-bold mb-1">δ = a1 + a2 + a0</span>
<span class="block max-w-full truncate text-sm font-mono font-bold role-text-secondary" :title="formatFixedFineCoeff(fixedCurrentDelta)">{{ formatFixedFineCoeff(fixedCurrentDelta) }}</span>
</div>
</div>
<div class="role-bg-warning-soft border role-border-warning-soft rounded-lg p-3">
<p class="text-[10px] role-text-warning leading-relaxed">
<span class="font-bold">運算說明</span><br>
a0 = 2^{{ shiftBitsA }}δ = (a1 + a2 + a0) / 2r = (a2 - a1 - a0) / 2
a0 = 2^{{ shiftBitsA }}a1 = -a0 - r, a2 = r + δ
</p>
</div>
</div>
+23 -50
View File
@@ -58,7 +58,7 @@ export default {
shiftBitRepeatDelayTimer: null,
shiftBitRepeatTimer: null,
shiftBitDrag: null,
fixedAFineStep: 0.5,
fixedAFineStep: 1,
fixedAFineDrag: null,
fixedAFineRepeatDelayTimer: null,
fixedAFineRepeatTimer: null,
@@ -171,10 +171,13 @@ export default {
return a[2] || 0;
},
currentDelta() {
return (this.currentA1 + this.currentA2 + 1) / 2;
// New Model: a1 = -1 - r, a2 = r + delta
// => delta = a1 + a2 + 1
return this.currentA1 + this.currentA2 + 1;
},
currentR() {
return (this.currentA2 - this.currentA1 - 1) / 2;
// New Model: a1 = -1 - r => r = -1 - a1
return -1 - this.currentA1;
},
currentA1A2Diff() {
return this.currentA1 - this.currentA2;
@@ -192,10 +195,13 @@ export default {
return this.fixedPointCoeffs.a[2]?.val || 0;
},
fixedCurrentDelta() {
return (this.fixedCurrentA1 + this.fixedCurrentA2 + this.fixedA0Int) / 2;
// New Model: a1 = -a0 - r, a2 = r + delta
// => delta = a1 + a2 + a0
return this.fixedCurrentA1 + this.fixedCurrentA2 + this.fixedA0Int;
},
fixedCurrentR() {
return (this.fixedCurrentA2 - this.fixedCurrentA1 - this.fixedA0Int) / 2;
// New Model: a1 = -a0 - r => r = -a0 - a1
return -this.fixedA0Int - this.fixedCurrentA1;
},
fixedCurrentA1A2Diff() {
return this.fixedCurrentA1 - this.fixedCurrentA2;
@@ -337,7 +343,7 @@ export default {
this.aFineStep = Number(clampedStep.toPrecision(12));
},
adjustFixedAFineStep(direction) {
const steps = [0.5, 1, 2, 4, 8, 32, 256, 1024, 4096, 16384, 65536];
const steps = [1, 2, 4, 8, 32, 256, 1024, 4096, 16384, 65536];
let currentIdx = steps.indexOf(this.fixedAFineStep);
// 如果當前值不在數列中,找最接近的一個
@@ -358,7 +364,8 @@ export default {
const nextDelta = Number(delta);
const nextR = Number(r);
if (!Number.isFinite(nextDelta) || !Number.isFinite(nextR)) return;
this.setA1A2(-1 - nextR + nextDelta, nextR + nextDelta);
// New Model: a1 = -1 - r, a2 = r + delta
this.setA1A2(-1 - nextR, nextR + nextDelta);
},
setAFineDirect(target, rawValue) {
const nextValue = parseFloat(rawValue);
@@ -413,20 +420,14 @@ export default {
window.removeEventListener('pointercancel', this.stopAFineDrag);
},
setFixedDeltaR(delta, r) {
// 使用「兩倍值」空間進行整數運算
let S = Math.round(delta * 2);
let D = Math.round(r * 2);
const nextDelta = Math.round(delta);
const nextR = Math.round(r);
const a0 = this.fixedA0Int;
// 核心邏輯:a1, a2 為整數的充要條件是 S 與 D 的奇偶性相同
// 這裡採用「優先滿足目標值」的策略:如果奇偶不同,則微調其中一個
// 由於此函數由 UI 直接調用,我們無法判斷使用者剛才改了誰
// 所以我們預設採用 Math.round 來尋找最接近的整數組合
const a2 = Math.round((S + D) / 2);
const a1 = a2 - D - a0;
this.fixedOverrides.a[1] = a1;
this.fixedOverrides.a[2] = a2;
// New Model: a1 = -a0 - r, a2 = r + delta
// If r and delta are integers, a1 and a2 are guaranteed to be integers.
this.fixedOverrides.a[1] = -a0 - nextR;
this.fixedOverrides.a[2] = nextR + nextDelta;
this.debouncedUpdateBode();
},
setFixedAFineDirect(target, rawValue) {
@@ -439,40 +440,12 @@ export default {
}
},
adjustFixedAFineValue(target, steps) {
let S = Math.round(this.fixedCurrentDelta * 2);
let D = Math.round(this.fixedCurrentR * 2);
const dVal = Math.round(steps * this.fixedAFineStep * 2);
const a0 = this.fixedA0Int;
const dVal = Math.round(steps * this.fixedAFineStep);
if (target === 'delta') {
// 優先保證 delta 變動到位
S += dVal;
// 檢查奇偶性是否匹配
if ((S % 2 + 2) % 2 !== (D % 2 + 2) % 2) {
// 核心:震盪平衡邏輯。
// 如果 S 變為偶數,D 應該變為最近的偶數;如果 S 變為奇數,D 應該變為最近的奇數。
// 我們判斷目前 D 是在 .5 還是 .0,然後向反方向跳轉,防止單向漂移。
if (D % 2 !== 0) {
D -= 1; // 10.5 -> 10.0
} else {
D += 1; // 10.0 -> 10.5
}
}
this.setFixedDeltaR(this.fixedCurrentDelta + dVal, this.fixedCurrentR);
} else if (target === 'r') {
// 如果 r 沒被禁用,則優先保證 r 變動到位
D += dVal;
if ((S % 2 + 2) % 2 !== (D % 2 + 2) % 2) {
if (S % 2 !== 0) S -= 1;
else S += 1;
}
this.setFixedDeltaR(this.fixedCurrentDelta, this.fixedCurrentR + dVal);
}
const a2 = (S + D) / 2;
const a1 = a2 - D - a0;
this.fixedOverrides.a[1] = a1;
this.fixedOverrides.a[2] = a2;
this.debouncedUpdateBode();
},
handleFixedAFineWheel(target, event) {
if (!event.shiftKey) {