Fog of war: wider fades and a grainy edge instead of stepped lines (t17)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 12:39:13 -04:00
co-authored by Claude Opus 5.5
parent b4245871ae
commit ab61d6b904
8 changed files with 47 additions and 19 deletions
+2
View File
@@ -237,6 +237,8 @@ above, including:
- **Fog of war also reveals the ground around the bot (t17, 2026-10-24).** About 20 grains in every - **Fog of war also reveals the ground around the bot (t17, 2026-10-24).** About 20 grains in every
direction, through rock, fully clear with a soft edge. Line of sight alone hid everything inside direction, through rock, fully clear with a soft edge. Line of sight alone hid everything inside
the ground, so the owner couldn't see what was around them or where to dig. the ground, so the owner couldn't see what was around them or where to dig.
As tuned: clear to 12 grains, fading out by 24, with a grainy edge; the owner found the first
version's narrow fade too sharp.
## Ideas not built yet ## Ideas not built yet
+3 -3
View File
@@ -221,10 +221,10 @@ World state: the per-cell typed arrays, alloc(), ambient temperature by depth, w
- functions: emit, bagCap, discover, alloc, depthU, depthY, computeAmbient, fillNoise, wakeXY, wakeHeatXY, wakeIdx, wakeHeatIdx - functions: emit, bagCap, discover, alloc, depthU, depthY, computeAmbient, fillNoise, wakeXY, wakeHeatXY, wakeIdx, wakeHeatIdx
- variables: W, H, N, CW, CH, NCH, mat, temp, life, flags, stamp, part, mach, digp, noiseB, airS, netId, isPow, occ, surfY, dirtY, graniteY, basaltY, magmaY, roofY, AMB, worldSeed, surfBase, underD, shallowD, lapse, BW, BH, NB, blockP, blockV, ents, bgm, flyers, ghostBlocks, ghostMolds, awake, hot, hotLvl, tick, tickStamp, machines, molds, nets, pcells, powerDirty, beltL, beltR, tubeLists, brokenMachines, ghostMachines, ghostBlocked, EV, bot, bag, bagTotal, player, seen, ledger, stats - variables: W, H, N, CW, CH, NCH, mat, temp, life, flags, stamp, part, mach, digp, noiseB, airS, netId, isPow, occ, surfY, dirtY, graniteY, basaltY, magmaY, roofY, AMB, worldSeed, surfBase, underD, shallowD, lapse, BW, BH, NB, blockP, blockV, ents, bgm, flyers, ghostBlocks, ghostMolds, awake, hot, hotLvl, tick, tickStamp, machines, molds, nets, pcells, powerDirty, beltL, beltR, tubeLists, brokenMachines, ghostMachines, ghostBlocked, EV, bot, bag, bagTotal, player, seen, ledger, stats
### src/sim/fog.js (46 lines) ### src/sim/fog.js (51 lines)
Exploration only: never changes grains or the random stream. Exploration only: never changes grains or the random stream.
- functions: fogAlloc, fogMark, fogSeen, fogInit, fogLook, fogRevealAround, fogRevealAll - functions: fogAlloc, fogMark, fogSeen, fogInit, fogLook, fogRevealAround, fogRevealAll
- variables: FOG, FOG_SENSE, fog, FW, FH - variables: FOG, FOG_SENSE, FOG_SENSE_FADE, fog, FW, FH
### src/sim/cells.js (176 lines) ### src/sim/cells.js (176 lines)
Cell helpers (setCell, move, enter) and the falling-sand movement of powders, liquids and gases (simMove). Cell helpers (setCell, move, enter) and the falling-sand movement of powders, liquids and gases (simMove).
@@ -343,7 +343,7 @@ Drawing (src/render/): browser only. This file: the renderer state R, colour tab
Propagated coloured light from glowing and burning cells. Propagated coloured light from glowing and burning cells.
- functions: computeLight - functions: computeLight
### src/render/world.js (297 lines) ### src/render/world.js (299 lines)
Scale colour without changing the smooth-look class. Scale colour without changing the smooth-look class.
- functions: fogColor, drawWorld - functions: fogColor, drawWorld
- variables: TUBE_CLASS - variables: TUBE_CLASS
+12
View File
@@ -4,6 +4,18 @@ What agents have done, newest first. Add an entry for every piece of work: the d
note or request it was for, what changed, the files touched, the tests run, and anything left note or request it was for, what changed, the files touched, the tests run, and anything left
open or not verified. Keep each entry short; the git history has the details. open or not verified. Keep each entry short; the git history has the details.
## 2026-10-24: t17 follow-up: softer fog edges (Claude Code, Codex)
- The owner found the edges sharp and stepped. `src/sim/fog.js`: sense ring 24 grains with a
12-grain fade (`FOG_SENSE_FADE`), rays fade 8 grains into rock and over the last 16 of range,
surface fade 16, `fogRevealAround` gets a 6-grain soft edge. `src/render/world.js`: partly seen
grains get ±40 of per-grain noise (`noiseB`), so edges look grainy, not stepped.
- `test/t_fog.js`: a point in the fade band is between 0 and 128. `docs/systems/fog.md` updated.
Screenshots `shots/fog-edge-{before,after}{,-nolight}.png` compared.
- Tests: all pass except `t_smooth` half-resolution, which failed 1 of 3 runs with the change and
passed 2 of 2 without, at load 6–12; that check runs with fog off, so the drawing change does
not run there. Fog drawing 0.43 → 0.45 ms.
## 2026-10-24: t17 Fog of war reveals the ground around the bot (Claude Code, Codex) ## 2026-10-24: t17 Fog of war reveals the ground around the bot (Claude Code, Codex)
- `src/sim/fog.js`: `FOG_SENSE = 20`; `fogLook` also reveals every fog cell within 20 grains, - `src/sim/fog.js`: `FOG_SENSE = 20`; `fogLook` also reveals every fog cell within 20 grains,
+11 -7
View File
@@ -7,26 +7,30 @@ surface start seen. Exploration never changes the simulation.
`src/sim/fog.js` keeps a `Uint8Array` at half the grain resolution (`FOG = 2`). `src/sim/fog.js` keeps a `Uint8Array` at half the grain resolution (`FOG = 2`).
Values run from 0 (unseen) to 255 (seen) and only increase. `alloc` allocates it with Values run from 0 (unseen) to 255 (seen) and only increase. `alloc` allocates it with
the other world arrays. `fogInit` reveals above `surfY` and fades eight grains below it. the other world arrays. `fogInit` reveals above `surfY` and fades 16 grains below it.
Every four ticks, 256 rays from the bot's centre reveal up to 48 grains. The last eight Every four ticks, 256 rays from the bot's centre reveal up to 48 grains. The last 16
grains fade out. Solid and powder stop sight after a four-grain fade into their face; grains fade out. Solid and powder stop sight after an eight-grain fade into their face;
air, gas and liquid pass sight. Bars and machine voids do not block it. air, gas and liquid pass sight. Bars and machine voids do not block it.
Each look also senses the ground around the bot through rock: every fog cell within Each look also senses the ground around the bot through rock: every fog cell within
`FOG_SENSE` (20 grains) of the bot is revealed, fully clear out to 16 grains and fading to `FOG_SENSE` (24 grains) of the bot is revealed, fully clear out to 12 grains and fading over
nothing at 20, so the player can see ore and caves next to their tunnel and choose where to dig. the outer 12 grains to nothing at 24, so the player can see ore and caves next to their tunnel
and choose where to dig.
## Saving ## Saving
The fog buffer is saved and exported with the other typed arrays, without a save version The fog buffer is saved and exported with the other typed arrays, without a save version
change. Older saves rebuild the surface, reveal a radius of 12 grains around built blocks, change. Older saves rebuild the surface, reveal a radius of 12 grains around built blocks,
machine footprints and molds, then look from the bot. Loading and generating also look machine footprints and molds, fully clear inside six grains with a six-grain fade to the
radius edge, then look from the bot. Loading and generating also look
immediately from the bot. immediately from the bot.
## Drawing and setting ## Drawing and setting
Both looks and the thermal lens sample fog bilinearly. Seen grains use the usual drawing; Both looks and the thermal lens sample fog bilinearly. Partial values get stable per-grain
noise of about ±40, clamped to 1–255, to break up stepped edges. Unseen and fully seen
grains are unchanged. Seen grains use the usual drawing;
unseen grains use the out-of-world colour and smooth-look buffers, hiding their shapes. unseen grains use the out-of-world colour and smooth-look buffers, hiding their shapes.
Partial values dim foreground, background and trees. Bloom sources need at least 128; Partial values dim foreground, background and trees. Bloom sources need at least 128;
bars, flyers, rain and particles need at least 64. Hover info and the cursor label say bars, flyers, rain and particles need at least 64. Hover info and the cursor label say
+2
View File
@@ -49,6 +49,8 @@ function drawWorld() {
const a = fog[fr + j], b = fog[fr + j1], c = fog[fn + j], d = fog[fn + j1]; const a = fog[fr + j], b = fog[fr + j1], c = fog[fn + j], d = fog[fn + j1];
if ((a & b & c & d) !== 255) fv = (a + (b - a) * t) * (1 - ft) + (c + (d - c) * t) * ft; if ((a & b & c & d) !== 255) fv = (a + (b - a) * t) * (1 - ft) + (c + (d - c) * t) * ft;
if (!fv) { px[row + bx] = OUT; if (sm) { bpx[row + bx] = 0x000a0a0c; tpx[row + bx] = 0; fpx[row + bx] = 0xff; } continue; } if (!fv) { px[row + bx] = OUT; if (sm) { bpx[row + bx] = 0x000a0a0c; tpx[row + bx] = 0; fpx[row + bx] = 0xff; } continue; }
// Stable grain noise breaks up the half-resolution fog's stepped edges.
if (fv < 255) fv = Math.max(1, Math.min(255, fv + (noiseB[yw + x] - 128) * (40 / 128)));
} }
const i = yw + x, m = mat[i], nz = noiseB[i] - 128; const i = yw + x, m = mat[i], nz = noiseB[i] - 128;
const T = temp[i]; const T = temp[i];
+12 -7
View File
@@ -1,6 +1,7 @@
// Exploration only: never changes grains or the random stream. // Exploration only: never changes grains or the random stream.
const FOG = 2; const FOG = 2;
const FOG_SENSE = 20; // Reveal nearby ground through rock, in grains. const FOG_SENSE = 24; // Reveal nearby ground through rock, in grains.
const FOG_SENSE_FADE = 12;
let fog, FW, FH; let fog, FW, FH;
function fogAlloc() { FW = Math.ceil(W / FOG); FH = Math.ceil(H / FOG); fog = new Uint8Array(FW * FH); } function fogAlloc() { FW = Math.ceil(W / FOG); FH = Math.ceil(H / FOG); fog = new Uint8Array(FW * FH); }
function fogMark(x, y, v) { function fogMark(x, y, v) {
@@ -13,7 +14,7 @@ function fogSeen(x, y) {
return fog[Math.floor(y / FOG) * FW + Math.floor(x / FOG)]; return fog[Math.floor(y / FOG) * FW + Math.floor(x / FOG)];
} }
function fogInit() { function fogInit() {
for (let x = 0; x < W; x++) for (let y = 0; y < Math.min(H, surfY[x] + 8); y++) fogMark(x, y, y <= surfY[x] ? 255 : (surfY[x] + 8 - y) * 255 / 8); for (let x = 0; x < W; x++) for (let y = 0; y < Math.min(H, surfY[x] + 16); y++) fogMark(x, y, y <= surfY[x] ? 255 : (surfY[x] + 16 - y) * 255 / 16);
} }
function fogLook(cx, cy) { function fogLook(cx, cy) {
for (let a = 0; a < 256; a++) { for (let a = 0; a < 256; a++) {
@@ -24,23 +25,27 @@ function fogLook(cx, cy) {
if (x < 0 || y < 0 || x >= W || y >= H) break; if (x < 0 || y < 0 || x >= W || y >= H) break;
const ph = PH[mat[y * W + x]]; const ph = PH[mat[y * W + x]];
if (hit < 0 && (ph === PH_SOLID || ph === PH_POWDER)) hit = d; if (hit < 0 && (ph === PH_SOLID || ph === PH_POWDER)) hit = d;
const v = Math.min(255, (48 - d) * 255 / 8, hit < 0 ? 255 : (4 - d + hit) * 255 / 4); const v = Math.min(255, (48 - d) * 255 / 16, hit < 0 ? 255 : (8 - d + hit) * 255 / 8);
if (v <= 0) break; if (v <= 0) break;
fogMark(x, y, v); fogMark(x, y, v);
} }
} }
// one fog cell at a time, measured from its centre; full inside FOG_SENSE - 4, fading to the edge // one fog cell at a time, measured from its centre; full inside 12 grains, fading over the outer 12 grains
const R2 = FOG_SENSE * FOG_SENSE, in2 = (FOG_SENSE - 4) ** 2; const R2 = FOG_SENSE * FOG_SENSE, in2 = (FOG_SENSE - FOG_SENSE_FADE) ** 2;
const fy0 = Math.max(0, Math.floor((cy - FOG_SENSE) / FOG)), fy1 = Math.min(FH - 1, Math.floor((cy + FOG_SENSE) / FOG)); const fy0 = Math.max(0, Math.floor((cy - FOG_SENSE) / FOG)), fy1 = Math.min(FH - 1, Math.floor((cy + FOG_SENSE) / FOG));
const fx0 = Math.max(0, Math.floor((cx - FOG_SENSE) / FOG)), fx1 = Math.min(FW - 1, Math.floor((cx + FOG_SENSE) / FOG)); const fx0 = Math.max(0, Math.floor((cx - FOG_SENSE) / FOG)), fx1 = Math.min(FW - 1, Math.floor((cx + FOG_SENSE) / FOG));
for (let fy = fy0; fy <= fy1; fy++) for (let fx = fx0; fx <= fx1; fx++) { for (let fy = fy0; fy <= fy1; fy++) for (let fx = fx0; fx <= fx1; fx++) {
const dx = (fx + 0.5) * FOG - cx, dy = (fy + 0.5) * FOG - cy, d2 = dx * dx + dy * dy, i = fy * FW + fx; const dx = (fx + 0.5) * FOG - cx, dy = (fy + 0.5) * FOG - cy, d2 = dx * dx + dy * dy, i = fy * FW + fx;
if (d2 >= R2) continue; if (d2 >= R2) continue;
const v = d2 <= in2 ? 255 : (FOG_SENSE - Math.sqrt(d2)) * 255 / 4; const v = d2 <= in2 ? 255 : (FOG_SENSE - Math.sqrt(d2)) * 255 / FOG_SENSE_FADE;
if (v > fog[i]) fog[i] = v; if (v > fog[i]) fog[i] = v;
} }
} }
function fogRevealAround(x, y, r) { function fogRevealAround(x, y, r) {
for (let yy = Math.max(0, Math.floor(y - r)); yy <= Math.min(H - 1, y + r); yy++) for (let xx = Math.max(0, Math.floor(x - r)); xx <= Math.min(W - 1, x + r); xx++) if ((xx - x) ** 2 + (yy - y) ** 2 <= r * r) fogMark(xx, yy, 255); for (let yy = Math.max(0, Math.floor(y - r)); yy <= Math.min(H - 1, y + r); yy++) for (let xx = Math.max(0, Math.floor(x - r)); xx <= Math.min(W - 1, x + r); xx++) {
const d2 = (xx - x) ** 2 + (yy - y) ** 2;
if (d2 >= r * r) continue;
fogMark(xx, yy, r >= 6 && d2 <= (r - 6) ** 2 ? 255 : Math.min(255, (r - Math.sqrt(d2)) * 255 / 6));
}
} }
function fogRevealAll() { fog.fill(255); } function fogRevealAll() { fog.fill(255); }
+4 -2
View File
@@ -7,8 +7,8 @@ created: 2026-10-24
updated: 2026-10-24T16:29:07Z updated: 2026-10-24T16:29:07Z
title: fog of war changes title: fog of war changes
report: | report: |
(agent) The bot now sees into the ground around it, through rock: about 20 grains in every (agent) The bot now sees into the ground around it, through rock: fully clear out to 12 grains,
direction, fully clear, with a soft edge over the last few grains. It moves with the bot and then fading out softly by 24, with a grainy edge instead of a hard or stepped one. It moves with the bot and
stays revealed afterwards, like the rest of the fog. Line of sight down tunnels and into caves stays revealed afterwards, like the rest of the fog. Line of sight down tunnels and into caves
works as before (out to 48 grains). works as before (out to 48 grains).
To check: start or load a world, dig down, and look at the ore and caves showing in the rock To check: start or load a world, dig down, and look at the ore and caves showing in the rock
@@ -22,3 +22,5 @@ report: |
## Subtasks ## Subtasks
- [x] Reveal ground within about 20 grains of the bot, through rock, with a soft edge - [x] Reveal ground within about 20 grains of the bot, through rock, with a soft edge
- [x] Blend the fog edges more: they look sharp and stepped
report: The ring around the bot now fades over 12 grains (clear to 12, gone at 24) instead of 4, rays fade 8 grains into rock and over the last 16 of their range, the surface fades over 16 grains, and partly seen grains get a little per-grain noise so the edge looks grainy instead of stepped.
+1
View File
@@ -12,6 +12,7 @@ check('open pocket is visible', run('fogSeen(82, 104) >= 200'));
check('sense test points are rock', run('mat[117 * W + 62] === STONE && mat[122 * W + 62] === STONE')); check('sense test points are rock', run('mat[117 * W + 62] === STONE && mat[122 * W + 62] === STONE'));
check('nearby rock is clearly seen through the floor', run('fogSeen(62, 117) >= 200')); check('nearby rock is clearly seen through the floor', run('fogSeen(62, 117) >= 200'));
check('rock near the sense edge is seen', run('fogSeen(62, 122) > 0')); check('rock near the sense edge is seen', run('fogSeen(62, 122) > 0'));
check('outer sense band softly reveals rock', run('mat[124 * W + 70] === STONE && fogSeen(70, 124) > 0 && fogSeen(70, 124) < 128'));
check('rock hides separate pocket', run('fogSeen(62, 130) === 0')); check('rock hides separate pocket', run('fogSeen(62, 130) === 0'));
check('one look has limited range', run('fogSeen(124, 104) === 0')); check('one look has limited range', run('fogSeen(124, 104) === 0'));
check('fog only increases', run('(()=>{const f = fog.slice(); fogLook(90,104); return f.every((v,i)=>fog[i]>=v)})()')); check('fog only increases', run('(()=>{const f = fog.slice(); fogLook(90,104); return f.every((v,i)=>fog[i]>=v)})()'));