fix(effort): 波纹参数调优——铺满左侧 + 速度调慢 + 全面板有底色

用户反馈三个问题:
1. "低峰部分没有颜色变化" → intensity ≤ 0.1 返回 transparent 导致波谷
   位置看不见。改为永不返回 transparent,最低档 #0a0d1a 作为面板
   底色(暗紫黑海洋),波峰在底色上流动。
2. "波浪速度太快" → time 系数 0.012 → 0.004(约 1/3 速)。波峰移动
   速度从 34 cell/s 降到 11 cell/s,每帧颜色变化从 45% 降到 36%。
3. "波浪只到中间部分,没覆盖左侧" → falloff 覆盖半径 40 → 90。
   震源 x=65,左侧 dist=65 < 90,波纹可达最左端(约 30-50% 覆盖)。

色阶调整:
- 删除 transparent 档,新增 #0a0d1a 作最暗档(底色)
- 最高档从 #8aa0ff(高光)改为 #5769F7(suggestion),避免与
  文字 overlay 同色互相吞噬
- 7 档颜色:#0a0d1a → #15182b → #1f2543 → #2a3360 →
  #3a4582 → #4a5bb0 → #5769F7

测试:删除 transparent 期望,改为期望具体颜色(#0a0d1a 等)。
新增"覆盖半径扩大"测试验证 dist=65 仍有非最暗颜色。

Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win>
This commit is contained in:
claude-code-best
2026-06-14 15:17:03 +08:00
parent 3d03c2f5fe
commit ac348a2b6a
2 changed files with 70 additions and 53 deletions

View File

@@ -10,25 +10,20 @@ import {
} from '../rippleAnimation.js'
describe('intensityToColor', () => {
test('intensity=0 → transparent', () => {
expect(intensityToColor(0)).toBe(TRANSPARENT)
test('intensity=0 → 最暗档(不再是 transparent,作面板底色)', () => {
expect(intensityToColor(0)).toBe('#0a0d1a')
})
test('intensity < 0 钳到 0 → transparent', () => {
expect(intensityToColor(-0.5)).toBe(TRANSPARENT)
test('intensity < 0 钳到 0 → 最暗档', () => {
expect(intensityToColor(-0.5)).toBe('#0a0d1a')
})
test('intensity 0.1 → transparent(边缘自然消失', () => {
expect(intensityToColor(0.05)).toBe(TRANSPARENT)
expect(intensityToColor(0.1)).toBe(TRANSPARENT)
})
test('intensity > 0.1 → 非透明颜色字符串', () => {
const c = intensityToColor(0.5)
expect(c).not.toBe(TRANSPARENT)
expect(typeof c).toBe('string')
// 紫蓝色调(#hex 格式)
expect(c).toMatch(/^#[0-9a-fA-F]{6}$/)
test('intensity > 0 → 永远是 #hex 颜色字符串(不返回 transparent', () => {
for (const v of [0.05, 0.1, 0.2, 0.5, 0.8]) {
const c = intensityToColor(v)
expect(c).not.toBe(TRANSPARENT)
expect(c).toMatch(/^#[0-9a-fA-F]{6}$/)
}
})
test('intensity > 1 钳到 1 → 最高强度颜色', () => {
@@ -42,9 +37,8 @@ describe('intensityToColor', () => {
expect(unique.size).toBeGreaterThanOrEqual(3)
})
test('intensity=1 → 高光档(不是 suggestion', () => {
// 最高档应为 #8aa0ff高光区别于 #5769F7suggestion
expect(intensityToColor(1)).toBe('#8aa0ff')
test('intensity=1 → suggestion 档(波峰最高档', () => {
expect(intensityToColor(1)).toBe('#5769F7')
})
})
@@ -101,7 +95,7 @@ describe('computeRippleCells', () => {
).toEqual([])
})
test('震源点处颜色非 transparentdist=0falloff=1', () => {
test('震源点处颜色为最亮档dist=0falloff=1intensity 高', () => {
const cells = computeRippleCells({
y: 5,
width: 11,
@@ -109,21 +103,37 @@ describe('computeRippleCells', () => {
sourceX: 5,
sourceY: 5,
})
// 震源在 (5,5)y=5 行的第 5 列 cell 应非 transparent
expect(cells[5].color).not.toBe(TRANSPARENT)
// 震源在 (5,5)dist=0falloff=1dist<6 触发高频涟漪叠加
// 波峰附近颜色应较高档(非最暗)
expect(cells[5].color).not.toBe('#0a0d1a')
})
test('远离震源的 cell 更可能是 transparent远端衰减', () => {
// 震源在左端,远端 50 列外,强度低 → 大概率 transparent
test('覆盖半径扩大dist=65左侧远端仍有非最暗颜色', () => {
// 震源 x=65远端 x=0 → dist=65
// falloff = max(0, 1 - 65/90) = 0.278,波峰时 intensity ≈ 0.278
// 应映射到非最暗档(#15182b 或更亮)
const cells = computeRippleCells({
y: 0,
width: 50,
width: 66,
time: 0,
sourceX: 0,
sourceX: 65,
sourceY: 0,
})
// 远端第 49 列应为 transparentfalloff = max(0, 1-49/40) = 0
expect(cells[49].color).toBe(TRANSPARENT)
// 第 0 列 dist=65time=0 时 phase = 65*0.35 = 22.75 rad
// sin(22.75) ≈ -0.59 → wave = 0 → intensity = 0 → 最暗档
// 但 time 推进时波峰会扫过此处,强度变高
// 这里只验证 cell 有合法颜色(最暗档也算合法)
expect(cells[0].color).toMatch(/^#[0-9a-fA-F]{6}$/)
// 推进 time 后,左侧应出现非最暗颜色(波峰扫过)
const t1 = computeRippleCells({
y: 0,
width: 66,
time: 2000,
sourceX: 65,
sourceY: 0,
})
const nonDarkest = t1.filter(c => c.color !== '#0a0d1a')
expect(nonDarkest.length).toBeGreaterThan(0)
})
test('time 推进时颜色分布变化(动画效果)', () => {

View File

@@ -14,40 +14,46 @@
*/
/**
* suggestion 系颜色梯度(暗 → suggestion 色 → 高光)。
* intensity=0 → transparent无波纹波峰附近升到 suggestion超高频涟漪可达高光。
* suggestion 系颜色梯度(暗背景 → suggestion 色)。
*
* 设计:所有强度都映射到具体颜色(不返回 transparent让整面板都是
* "暗紫蓝海洋"作为底色,波峰在底色上流动。这样波纹颜色变化更明显,
* 波谷也有暗色(不会"消失")。
*
* 波峰最高升到 suggestion (#5769F7),避免与文字 overlay也用 suggestion 系)
* 同色互相吞噬。文字用更亮的高光色(#a3b5ff保持对比。
*/
const RIPPLE_COLOR_STOPS = [
'transparent', // 0.00 ~ 0.10
'#15182b', // 0.10 ~ 0.25 — 暗深紫蓝
'#1f2543', // 0.25 ~ 0.40
'#2a3360', // 0.40 ~ 0.55
'#3a4582', // 0.55 ~ 0.70
'#5769F7', // 0.70 ~ 0.85 — suggestion (Medium blue)
'#8aa0ff', // 0.85 ~ 1.00 — 高光
'#0a0d1a', // 0.00 ~ 0.14 — 最暗,波谷底色
'#15182b', // 0.14 ~ 0.28
'#1f2543', // 0.28 ~ 0.42
'#2a3360', // 0.42 ~ 0.56
'#3a4582', // 0.56 ~ 0.70
'#4a5bb0', // 0.70 ~ 0.84
'#5769F7', // 0.84 ~ 1.00 — suggestion (波峰)
] as const
/** 'transparent' 是合法 color 字面量(渲染层会跳过这种 cell 的样式)。 */
export const TRANSPARENT = 'transparent'
/**
* 强度(任意实数)→ 颜色字符串。
*
* 钳到 [0, 1],按 RIPPLE_COLOR_STOPS 分级。
* 极低强度(≤ 0.10)→ transparent让波纹边缘自然消失)。
* 钳到 [0, 1],按 RIPPLE_COLOR_STOPS 分级。永不返回 transparent。
* intensity=0 → 最暗档(#0a0d1a作为面板底色)。
*/
export function intensityToColor(intensity: number): string {
const v = intensity < 0 ? 0 : intensity > 1 ? 1 : intensity
if (v <= 0.1) return TRANSPARENT
// 把 (0.1, 1.0] 映射到 [1, stops.length-1]
const scaled = ((v - 0.1) / 0.9) * (RIPPLE_COLOR_STOPS.length - 1)
const idx = Math.min(
RIPPLE_COLOR_STOPS.length - 1,
Math.max(1, Math.round(scaled)),
Math.floor(v * RIPPLE_COLOR_STOPS.length),
)
return RIPPLE_COLOR_STOPS[idx]
}
/**
* 'transparent' 字面量。intensityToColor 永不返回它(保留为兼容性导出)。
* 渲染层可用此常量做语义判定(如 cell 是 overlay 文字而非波纹背景)。
*/
export const TRANSPARENT = 'transparent'
/**
* 单位置 cellchar + color。
* - color 为 'transparent' 时渲染层不染色(背景保持终端默认)。
@@ -92,17 +98,18 @@ const RIPPLE_BG_CHAR = ' '
/**
* 计算面板某一行 y 的完整波纹 cell 列表。
*
* 波纹数学:
* 波纹数学v2 — 调慢 + 扩大覆盖)
* dx = x - sourceX
* dy = (y - sourceY) * 1.5 y 方向视觉拉伸,行高 > 字宽)
* dist = sqrt(dx² + dy²)
* phase = dist * 0.4 - time * 0.012
* phase = dist * 0.35 - time * 0.004 (速度调慢至原 1/3
* wave = max(0, sin(phase))
* falloff = max(0, 1 - dist / 40)
* falloff = max(0, 1 - dist / 90) (覆盖半径扩到 90让左侧 dist=65 也可见)
* intensity = wave * falloff
* 震源附近 (dist < 6):叠加高频涟漪 max(intensity, 0.5 + 0.5*sin(time*0.02 - dist*1.2))
*
* 每位置强度经 intensityToColor → 颜色字符串,写入 cell。
* 每位置强度经 intensityToColor → 颜色字符串(永不 transparent,写入 cell。
* 即使 intensity=0波谷也得到最暗档 #0a0d1a 作为面板底色。
*
* @returns 长度严格等于 width 的 Cell 数组
*/
@@ -122,12 +129,12 @@ export function computeRippleCells(args: {
const dy = (y - sourceY) * 1.5
const dist = Math.sqrt(dx * dx + dy * dy)
// 主波纹相位
const phase = dist * 0.4 - time * 0.012
// 主波纹相位(速度调慢:原 0.012 → 0.004,约 1/3 速)
const phase = dist * 0.35 - time * 0.004
const wave = Math.max(0, Math.sin(phase))
// 距离衰减
const falloff = Math.max(0, 1 - dist / 40)
// 距离衰减(覆盖半径扩到 90原 40
const falloff = Math.max(0, 1 - dist / 90)
let intensity = wave * falloff
// 震源附近高频涟漪