Skip to content

Commit 1d1b61b

Browse files
committed
Merge branch 'master' into split/deps
2 parents 4e4676e + e2d99a9 commit 1d1b61b

61 files changed

Lines changed: 650 additions & 859 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

imgs/icon_anime/AGENTS.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Process-wide singleton used by `fount logo`, the CLI log viewer, and the foregro
1919
| `intro` | Enter animation, then background `hold` (no park) |
2020
| `start` / `dismiss` | log_viewer while waiting for the server (`start` no-op if already running; `dismiss` when connected); server `dismiss`es when `init` returns `started` |
2121
| `farewell` | On `on_shutdown` (safe mid-intro, e.g. `already_running`) |
22-
| `signal` / `abort` | User abort of this icon session (Ctrl+C or hold Esc ≥4s). `dismiss` does not touch it |
22+
| `signal` / `abort` | User abort of this icon session (Ctrl+C or hold Esc ≥4s, one-shot). `dismiss` does not touch it. Further ESC repeats / Ctrl+C are ignored until the next `player.start` so farewell exit can finish. |
2323

2424
Hosts own process-exit signaling and must wire `icon.signal` into it (log_viewer and server do). Non-TTY / no VT is gated only in `player.mjs` — session APIs stay callable (play paths no-op). On the alternate screen, `player` `block`/`unblock`s the global virtual console so console output is deferred; frame paint writes the native `targetStream` so the animation itself is not deferred.
2525

@@ -31,7 +31,7 @@ fount logo watch # deno run --watch
3131
fount test icon_anime --no-parallel
3232
```
3333

34-
Controls: Ctrl+C or hold Esc ≥4s exits (icon teardown, then quit). Left quick-click → ripple; left hold/drag → cool spotlight (ambient dims). Right-drag → stroke wind; right long-still → tornado vortex (can suspend rain and lift free-liquid puddles). Alt-screen (`1049h`/`1049l`) restores prior scrollback on exit.
34+
Controls: Ctrl+C or hold Esc ≥4s exits (teardown plays farewell exit, then quit). Left quick-click → ripple; left hold/drag → cool spotlight (ambient dims). Right-drag → stroke wind; right long-still → tornado vortex (can suspend rain and lift free-liquid puddles). Alt-screen (`1049h`/`1049l`) restores prior scrollback on exit.
3535

3636
## Modules
3737

@@ -75,8 +75,10 @@ Open-stage: ungrown base columns do not splash — rain falls through until it h
7575
- **One pressure language** / **one density language** — see [physics-notes.md](physics-notes.md). Do not invent parallel hydro models.
7676
- **Viscosity ladder** is the sole branch knob: `≤ VISC_INERTIAL` → inertial gas velocity; `< VISC_SOLID` → Stokes mass flux; `≥ VISC_SOLID` → frozen.
7777
- Water mass = `liq + moisture + condense + particles` (`totalWorldWater`); melt is separate. Closed transfers conserve; intentional sinks are world-edge / down-edge wipe / BODY impact. Particle expiry deposits back.
78+
- Soil condense hangs on the gravity-down face; when ĝ leaves that open underside it reabsorbs into moisture. Drip glyphs follow `condenseDripSource` (ĝ), not screen-down.
7879
- Terrain is **pedestal-anchored**; ungrown base keeps `HORIZON` until `POOL`/`SLOPE_*` overwrite. Resize shifts retained dynamics with the icon.
7980
- Gravity is a continuous unit vector everywhere (particles + grid). Depth = projection on ĝ; neighbor transfer uses weights `max(0, d̂·ĝ)`.
8081
- Four edges hold fractional roles `sink/source/wrap` from `n̂·ĝ` (sum to 1). Lava onset is **exposure work** `exposure[e] = max(0, exposure[e] + n̂·ĝ)` with decay when flipped (≥ `LAVA_ONSET_EXPOSURE`); 45° → two edges each need ~13·√2 s.
8182
- Gravity acquire: `document` → browser APIs; Termux → `termux-sensor` (pretty JSON indent=2, `parseSensorStdout`); else no-op. path CLI installs `termux-api` on `fount logo` / `log` / `server` when missing.
8283
- Rain edges weighted by `source`; side wrap by `wrap`. Meltdown absorb records absorb-time ĝ; regurgitate when `ĝ·absorbDir` drops.
84+
- Composition bottom (pedestal / lava edge) never rains — even when inverted ĝ makes it a physical sky; quiet until sink-edge exposure yields lava.

imgs/icon_anime/compose.mjs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
*/
66

77
import {
8-
MAT, LIQ_DRAW, COND_DRAW, BUBBLE_MIN_CELLS,
9-
isLiquidBarrier, isSoilMat, waterChar, liquidChar, dripChar, lavaChar,
8+
MAT, LIQ_DRAW, BUBBLE_MIN_CELLS,
9+
isLiquidBarrier, waterChar, liquidChar, dripChar, lavaChar,
10+
condenseDripSource,
1011
} from './fluid/index.mjs'
1112
import { sampleLight, RIPPLE_SPEED, RIPPLE_WIDTH, torchEase } from './gesture/light.mjs'
1213
import { ICON_W, ICON_BODY_H, PILLARS, BODY_DIST, maxBodyD } from './icon.mjs'
@@ -477,9 +478,9 @@ export const composeFrame = (state) => {
477478
fg[i] = FG_BUBBLE
478479
}
479480
else {
480-
const above = vy > 0 ? (vy - 1) * W + wx : -1
481-
if (above >= 0 && isSoilMat(mat[above]) && condense[above] >= COND_DRAW) {
482-
ch[i] = dripChar(condense[above], wx + frame)
481+
const dripSoil = condenseDripSource(world, wx, vy)
482+
if (dripSoil >= 0) {
483+
ch[i] = dripChar(condense[dripSoil], wx + frame)
483484
fg[i] = FG_SPLASH
484485
}
485486
else if (solid[wi] && vy === surface[wx]) {

imgs/icon_anime/fluid/index.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export {
5757
} from './gas.mjs'
5858

5959
/** 土壤湿度 / 凝结 / 滴落。 */
60-
export { stepSoil } from './soil.mjs'
60+
export { stepSoil, condenseDripSource } from './soil.mjs'
6161

6262
/** 热力与相变。 */
6363
export { stepThermal, cellRho, meltVisc } from './thermal.mjs'

imgs/icon_anime/fluid/soil.mjs

Lines changed: 139 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
* 土壤湿度 / 凝结 / 滴落。
33
*
44
* 吸收上方自由液体、土壤间渗流、天花板凝结、Matthew 邻格转移、COND_DRIP 滴落。
5-
* 由 `stepLiquid` 在水力均衡前调用。
5+
* 凝结膜挂在重力下沿;ĝ 转离开放下沿时收回水分。由 `stepLiquid` 在水力均衡前调用。
66
*/
77

88
import {
99
MAT, SOIL_CAP,
1010
SOIL_ABSORB_RATE, SOIL_SIDE_FRAC, SOIL_DOWN_FRAC, SOIL_CONDENSE_FRAC,
11-
COND_DRIP, COND_MATTHEW_RATE, COND_MATTHEW_NOISE,
11+
COND_DRAW, COND_DRIP, COND_MATTHEW_RATE, COND_MATTHEW_NOISE,
1212
isSoilMat, isLiquidBarrier, soilAbsorbFactor,
1313
} from './mat.mjs'
1414
import {
@@ -41,6 +41,14 @@ const soilQueues = {
4141
feedN: 0,
4242
}
4343

44+
/** 正交邻格,用于收回后溢流。 */
45+
const ORTHOGONAL_OFFSETS = [
46+
[1, 0],
47+
[-1, 0],
48+
[0, 1],
49+
[0, -1],
50+
]
51+
4452
/**
4553
* 将 `moisture[cell]` 钳在 `[0, SOIL_CAP]`。
4654
* @param {Float32Array} moisture 土壤湿度场
@@ -90,7 +98,89 @@ const pushFeed = (world, queues, from, amount) => {
9098
}
9199

92100
/**
93-
* 土壤渗流:吸收自由液体、共享湿度、供给凝结、Matthew 转移、滴落。
101+
* 空气格是否应显示来自重力上方土壤的悬挂凝结。
102+
* @param {FluidWorld} world 世界
103+
* @param {number} x 列
104+
* @param {number} y 行
105+
* @returns {number} 土壤格索引,无则 -1
106+
*/
107+
export const condenseDripSource = (world, x, y) => {
108+
const { mat, condense, worldW } = world
109+
const up = gravityUpWeights(world)
110+
for (let offsetIndex = 0; offsetIndex < up.n; offsetIndex++) {
111+
const soilX = x + up.dx[offsetIndex]
112+
const soilY = y + up.dy[offsetIndex]
113+
if (!inWorld(world, soilX, soilY)) continue
114+
const soil = soilY * worldW + soilX
115+
if (isSoilMat(mat[soil]) && condense[soil] >= COND_DRAW) return soil
116+
}
117+
return -1
118+
}
119+
120+
/**
121+
* 重力下沿是否仍为开放空气(悬挂凝结的附着面)。
122+
* @param {FluidWorld} world 世界
123+
* @param {number} x 列
124+
* @param {number} y 行
125+
* @param {{ dx: number[], dy: number[], w: number[], n: number }} down 下向权重
126+
* @returns {boolean} 是否开放
127+
*/
128+
const hasOpenUnderside = (world, x, y, down) => {
129+
const { mat, worldW } = world
130+
for (let offsetIndex = 0; offsetIndex < down.n; offsetIndex++) {
131+
const belowX = x + down.dx[offsetIndex]
132+
const belowY = y + down.dy[offsetIndex]
133+
if (!inWorld(world, belowX, belowY)) continue
134+
if (mat[belowY * worldW + belowX] === MAT.AIR) return true
135+
}
136+
return false
137+
}
138+
139+
/**
140+
* 将多余质量溢到邻接空气(按下向权重分配,再任意正交)。
141+
* @param {FluidWorld} world 世界
142+
* @param {number} x 列
143+
* @param {number} y 行
144+
* @param {number} amount 质量
145+
* @param {{ dx: number[], dy: number[], w: number[], n: number }} down 下向权重
146+
* @returns {number} 实际写入量
147+
*/
148+
const spillToAir = (world, x, y, amount, down) => {
149+
if (amount <= 1e-8) return 0
150+
let written = 0
151+
let weightSum = 0
152+
for (let offsetIndex = 0; offsetIndex < down.n; offsetIndex++) {
153+
const belowX = x + down.dx[offsetIndex]
154+
const belowY = y + down.dy[offsetIndex]
155+
if (!inWorld(world, belowX, belowY)) continue
156+
if (world.mat[belowY * world.worldW + belowX] !== MAT.AIR) continue
157+
weightSum += down.w[offsetIndex]
158+
}
159+
if (weightSum > 1e-8)
160+
for (let offsetIndex = 0; offsetIndex < down.n; offsetIndex++) {
161+
const belowX = x + down.dx[offsetIndex]
162+
const belowY = y + down.dy[offsetIndex]
163+
if (!inWorld(world, belowX, belowY)) continue
164+
if (world.mat[belowY * world.worldW + belowX] !== MAT.AIR) continue
165+
written += addLiquid(world, belowX, belowY, amount * (down.w[offsetIndex] / weightSum))
166+
}
167+
let rest = amount - written
168+
if (rest <= 1e-8) return written
169+
for (const [offsetX, offsetY] of ORTHOGONAL_OFFSETS) {
170+
const neighborX = x + offsetX
171+
const neighborY = y + offsetY
172+
if (!inWorld(world, neighborX, neighborY)) continue
173+
if (world.mat[neighborY * world.worldW + neighborX] !== MAT.AIR) continue
174+
const got = addLiquid(world, neighborX, neighborY, rest)
175+
written += got
176+
rest -= got
177+
if (rest <= 1e-8) break
178+
}
179+
return written
180+
}
181+
182+
/**
183+
* 土壤渗流:吸收自由液体、共享湿度、供给凝结、Matthew 转移、滴落、收回。
94184
* @param {FluidWorld} world 流体世界
95185
*/
96186
export const stepSoil = (world) => {
@@ -244,22 +334,27 @@ export const stepSoil = (world) => {
244334
applyDelta(moveTargets, moveCount)
245335
applyDelta(feedFrom, feedN)
246336

337+
// Matthew along gravity-perpendicular (sideB is the +1 direction of the pair).
338+
const sideDx = sideB.dx
339+
const sideDy = sideB.dy
247340
for (let y = 0; y < H; y++)
248-
for (let x = 0; x < W - 1; x++) {
341+
for (let x = 0; x < W; x++) {
342+
const nx = x + sideDx
343+
const ny = y + sideDy
344+
if (!inWorld(world, nx, ny)) continue
249345
const cell = y * W + x
250-
const right = cell + 1
251-
if (!isSoilMat(mat[cell]) || !isSoilMat(mat[right])) continue
346+
const other = ny * W + nx
347+
if (!isSoilMat(mat[cell]) || !isSoilMat(mat[other])) continue
252348
const leftCondense = condense[cell]
253-
const rightCondense = condense[right]
349+
const rightCondense = condense[other]
254350
if (leftCondense < 1e-8 || rightCondense < 1e-8) continue
255351
const mass = leftCondense + rightCondense
256-
// Cheap LCG — Matthew only needs jitter, not cryptographic randomness.
257-
const noise = ((((cell * 374761393) ^ (right * 668265263) ^ (step * 1274126177)) >>> 0) / 4294967296 - 0.5)
352+
const noise = ((((cell * 374761393) ^ (other * 668265263) ^ (step * 1274126177)) >>> 0) / 4294967296 - 0.5)
258353
* COND_MATTHEW_NOISE * mass
259354
const bias = (leftCondense - rightCondense) + noise
260355
if (Math.abs(bias) < 1e-8) continue
261-
const rich = bias > 0 ? cell : right
262-
const poor = bias > 0 ? right : cell
356+
const rich = bias > 0 ? cell : other
357+
const poor = bias > 0 ? other : cell
263358
const take = Math.min(condense[poor] * COND_MATTHEW_RATE, Math.abs(bias) * COND_MATTHEW_RATE)
264359
if (take <= 1e-8) continue
265360
condense[poor] -= take
@@ -270,17 +365,39 @@ export const stepSoil = (world) => {
270365
for (let x = 0; x < W; x++) {
271366
const cell = y * W + x
272367
if (!isSoilMat(mat[cell]) || condense[cell] < COND_DRIP) continue
273-
for (let i = 0; i < down.n; i++) {
274-
if (down.w[i] < 0.5) continue
275-
const bx = x + down.dx[i]
276-
const by = y + down.dy[i]
277-
if (!inWorld(world, bx, by)) continue
278-
const below = by * W + bx
279-
if (mat[below] !== MAT.AIR) continue
280-
const amount = condense[cell]
281-
const added = addLiquid(world, bx, by, amount)
282-
condense[cell] = amount - added
283-
break
368+
let weightSum = 0
369+
for (let offsetIndex = 0; offsetIndex < down.n; offsetIndex++) {
370+
const belowX = x + down.dx[offsetIndex]
371+
const belowY = y + down.dy[offsetIndex]
372+
if (!inWorld(world, belowX, belowY)) continue
373+
if (mat[belowY * W + belowX] !== MAT.AIR) continue
374+
weightSum += down.w[offsetIndex]
284375
}
376+
if (weightSum <= 1e-8) continue
377+
const amount = condense[cell]
378+
let written = 0
379+
for (let offsetIndex = 0; offsetIndex < down.n; offsetIndex++) {
380+
const belowX = x + down.dx[offsetIndex]
381+
const belowY = y + down.dy[offsetIndex]
382+
if (!inWorld(world, belowX, belowY)) continue
383+
if (mat[belowY * W + belowX] !== MAT.AIR) continue
384+
written += addLiquid(world, belowX, belowY, amount * (down.w[offsetIndex] / weightSum))
385+
}
386+
condense[cell] = amount - written
387+
}
388+
389+
// ĝ 转离开放下沿 → 悬挂凝结收回(回潮,满则溢到邻接空气;溢不完留在 condense)。
390+
for (let y = 0; y < H; y++)
391+
for (let x = 0; x < W; x++) {
392+
const cell = y * W + x
393+
if (!isSoilMat(mat[cell]) || condense[cell] <= 1e-8) continue
394+
if (hasOpenUnderside(world, x, y, down)) continue
395+
const back = condense[cell]
396+
const room = Math.max(0, SOIL_CAP - moisture[cell])
397+
const toMoist = Math.min(back, room)
398+
moisture[cell] += toMoist
399+
const rest = back - toMoist
400+
const spilled = rest > 1e-8 ? spillToAir(world, x, y, rest, down) : 0
401+
condense[cell] = rest - spilled
285402
}
286403
}

imgs/icon_anime/physics-notes.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Day-to-day map / hosting: [AGENTS.md](AGENTS.md). Read this when changing fluid,
2727
- Weighted ortho neighbors: `w = max(0, d̂·ĝ)` down / `d̂·(−ĝ)` up. Settle / buoyancy / bubbles / soil / free-surface follow these.
2828
- Edge roles (`edgeRoles`): for outward normal n̂, `sink=max(0,n̂·ĝ)`, `source=max(0,−n̂·ĝ)`, `wrap=1−|n̂·ĝ|`.
2929
- Exposure work: each tick `exposure[e] = max(0, exposure[e] + n̂_e·ĝ)`. Lava when `exposure[e] ≥ LAVA_ONSET_EXPOSURE` (312 under pure down = 13s@24fps). At 45°, two edges each accumulate cos45/frame → onset ≈ 13·√2 s.
30-
- Rain spawn uses `source` weights (gravity-down edge never rains). Side wrap uses `wrap`; particles pick wrap vs out with `hash01`.
30+
- Rain spawn uses `source` weights (gravity-down edge never rains). Composition bottom is never a rain sky (pedestal/lava edge) — inverted ĝ yields no rain there, then lava on the sink edge after exposure. Side wrap uses `wrap`; particles pick wrap vs out with `hash01`.
3131
- Absorb on source-weighted edges records `absorbGx/Gy`; regurgitate when `ĝ·absorbDir < threshold`, ejecting on current source edges.
3232

3333
## Terrain
@@ -62,7 +62,7 @@ Day-to-day map / hosting: [AGENTS.md](AGENTS.md). Read this when changing fluid,
6262
- `liqVx`/`liqVy`: EMA from mass transfers each `stepLiquid` — drives free-liquid glyphs.
6363
- Communicating vessels: relax `φ = P/(ρg) - depth` along the liquid graph (BFS from lowest-φ surface — no teleport across disconnected air).
6464
- `POOL` retains fill and spills/leaks; `BODY` is splash-only barrier; pillars are not materials.
65-
- Soil: absorb diminishes as cell wets (`soilAbsorbFactor`); rain hits sink only `SOIL_HIT_ABSORB_FRAC`. Seepage slow enough for surface puddles. Sideways share + prefer below (gravity-weighted); air below → underside `condense`; Matthew transfer between condensation cells; `COND_DRAW` / `COND_DRIP` thresholds. Heating evaporates moisture before melt.
65+
- Soil: absorb diminishes as cell wets (`soilAbsorbFactor`); rain hits sink only `SOIL_HIT_ABSORB_FRAC`. Seepage slow enough for surface puddles. Sideways share + prefer below (gravity-weighted); air below → underside `condense`; Matthew along ĝ⊥; `COND_DRAW` / `COND_DRIP` thresholds. When ĝ leaves an open underside, condense reabsorbs into moisture (excess spills to ortho air). Compose drips via `condenseDripSource` (gravity-up soil), not screen-Y. Heating evaporates moisture before melt.
6666
- Material rebuild clears labels only; `releaseNonSoilWater` dumps moisture/condense from non-soil into free liquid so `POOL` overwrite does not erase water.
6767
- `exit` stops when the icon is gone — no rain/liquid drain wait.
6868

imgs/icon_anime/player.mjs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,28 +53,37 @@ export const ESC_HOLD_GAP_MS = 500
5353

5454
/**
5555
* ESC 长按计时:靠终端键重复确认仍在按住;无松开事件。
56+
* 触发一次后保持闩锁,避免退场动画播放期间键重复再次 abort。
5657
* @param {number} [holdMs] 触发中止的持续时长
5758
* @param {number} [gapMs] 允许的最大重复间隔
58-
* @returns {{ note: (now: number) => boolean, reset: () => void }} note 在达到 holdMs 时返回 true
59+
* @returns {{ note: (now: number) => boolean, reset: () => void }} note 在达到 holdMs 时返回 true(仅一次)
5960
*/
6061
export function createEscHold(holdMs = ESC_HOLD_MS, gapMs = ESC_HOLD_GAP_MS) {
6162
/** @type {number | null} */
6263
let start = null
6364
let last = 0
65+
let fired = false
6466
return {
6567
/**
6668
* @param {number} now 单调时钟(ms)
67-
* @returns {boolean} 是否已连续按住满 holdMs
69+
* @returns {boolean} 是否刚达到 holdMs(已触发过则恒 false)
6870
*/
6971
note(now) {
72+
if (fired) return false
7073
if (start == null || now - last > gapMs) start = now
7174
last = now
72-
return now - start >= holdMs
75+
if (now - start < holdMs) return false
76+
fired = true
77+
return true
7378
},
74-
/** @returns {void} */
79+
/**
80+
* 清除 start、last、fired,重新允许一次触发。
81+
* @returns {void}
82+
*/
7583
reset() {
7684
start = null
7785
last = 0
86+
fired = false
7887
},
7988
}
8089
}
@@ -200,23 +209,30 @@ export function start({ onResize, onPointer, onUserAbort } = {}) {
200209
process.stdin.setRawMode(true)
201210
process.stdin.resume()
202211
const escHold = createEscHold()
212+
/** 首次用户中止后闩上,避免 farewell 退场被 ESC 重复 / 连按 Ctrl+C 掐断。 */
213+
let userAbortArmed = true
214+
/**
215+
* 触发一次用户中止(幂等)。
216+
* @returns {void}
217+
*/
218+
const tripUserAbort = () => {
219+
if (!userAbortArmed) return
220+
userAbortArmed = false
221+
abort()
222+
onUserAbort?.()
223+
}
203224
/**
204225
* Ctrl+C / 长按 ESC 中止;SGR 鼠标 → onPointer。
205226
* @param {Buffer} buf stdin 块
206227
* @returns {void}
207228
*/
208229
onData = (buf) => {
209230
stdinCarry = consumeStdin(stdinCarry, buf, {
210-
/** Ctrl+C:中止播放并通知会话。 */
211-
abort: () => {
212-
abort()
213-
onUserAbort?.()
214-
},
231+
abort: tripUserAbort,
215232
/** ESC 键重复:连续按住满 ESC_HOLD_MS 则中止。 */
216233
onEsc: () => {
217234
if (!escHold.note(performance.now())) return
218-
abort()
219-
onUserAbort?.()
235+
tripUserAbort()
220236
},
221237
onPointer,
222238
})

imgs/icon_anime/scene.mjs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,8 @@ const onParticleHit = (world, x, y, m, particle, wet, state) => {
526526

527527
/**
528528
* 四边出雨权重(source 角色)。导出供测试。
529+
* 组成底边(地形/岩浆侧)永不作出雨天:倒置时 ĝ 穿入底边会把雨从基座往上喷,
530+
* 应静等 sink 边曝露后出岩浆,而不是底边冒雨。
529531
* @param {number} gx 单位重力 x
530532
* @param {number} gy 单位重力 y
531533
* @returns {{ nx: number, ny: number, w: number }[]} 边权重
@@ -542,9 +544,10 @@ export const rainEdgeWeights = (gx, gy) => {
542544
// source = max(0, −n̂·ĝ); sink edge never rains
543545
e.w = Math.max(0, -dot)
544546
}
547+
// Pedestal / lava edge of the composition — never a rain sky.
548+
edges[1].w = 0
545549
// Side edges get a small base so left/right can randomly rain under default g.
546-
const top = edges.find(e => e.ny < 0)
547-
if (top && top.w > 0.5) {
550+
if (edges[0].w > 0.5) {
548551
edges[2].w = Math.max(edges[2].w, 0.12)
549552
edges[3].w = Math.max(edges[3].w, 0.12)
550553
}

0 commit comments

Comments
 (0)