world: per-file heat map — persistent touch-count residual (V4)
Files that see repeated touches decay back to visual silence within a couple of seconds because `n.heat` decays at 0.5/sec. Hard to tell a hot-spot file (touched 20 times over a run) from a virgin one; both look the same at rest. Adds a monotonically-increasing per-node `touchCount` on the engine and a log-scaled residual on the render that never decays. Files climbing the touch count grow subtly, tint whiter, and pull more emissive — even at heat=0. - GNode gains `touchCount`, initialized 0 on ensureNode and on the ROOT sentinel. - onTouch bumps `touchCount` only for `file:` prefix nodes so landmarks + structural orbs don't accumulate a hot halo just because agents converge on them. - WorldCanvas node loop computes `residual = min(0.7, log(1+count)/log(50))` and adds it into radius, whiten-lerp, and emissiveIntensity. Feel: hot files stay softly-warm indefinitely (residual stops climbing around ~50 touches). Cold files stay cold. Live activity still dominates via the transient `heat` bump; residual is the ghost trail.
This commit is contained in:
@@ -695,14 +695,16 @@ export function WorldCanvas({ roots, selectedId, onSelect, expanded, onToggleExp
|
|||||||
nodeMeshes.set(n.id, m);
|
nodeMeshes.set(n.id, m);
|
||||||
gourceGroup.add(m);
|
gourceGroup.add(m);
|
||||||
}
|
}
|
||||||
const r = n.r * (1 + n.heat * 0.4);
|
// touchCount → log-scaled residual heat that never decays.
|
||||||
|
const residual = Math.min(0.7, Math.log(1 + (n.touchCount ?? 0)) / Math.log(50));
|
||||||
|
const r = n.r * (1 + n.heat * 0.4 + residual * 0.15);
|
||||||
m.position.set(n.x, n.y, 0);
|
m.position.set(n.x, n.y, 0);
|
||||||
m.scale.set(r, r, r);
|
m.scale.set(r, r, r);
|
||||||
const mat = m.material as THREE.MeshStandardMaterial;
|
const mat = m.material as THREE.MeshStandardMaterial;
|
||||||
col.set(n.color).lerp(white, n.heat * 0.6);
|
col.set(n.color).lerp(white, n.heat * 0.6 + residual * 0.35);
|
||||||
mat.color.copy(col);
|
mat.color.copy(col);
|
||||||
mat.emissive.copy(col);
|
mat.emissive.copy(col);
|
||||||
mat.emissiveIntensity = 0.35 + n.heat * 0.6;
|
mat.emissiveIntensity = 0.35 + n.heat * 0.6 + residual * 0.4;
|
||||||
mat.opacity = n.alpha;
|
mat.opacity = n.alpha;
|
||||||
mat.transparent = n.alpha < 0.99; // solid when present; only fades on appear/disappear
|
mat.transparent = n.alpha < 0.99; // solid when present; only fades on appear/disappear
|
||||||
let g = nodeGlows.get(n.id);
|
let g = nodeGlows.get(n.id);
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ export interface GNode {
|
|||||||
alpha: number; // fade in/out (world nodes)
|
alpha: number; // fade in/out (world nodes)
|
||||||
lastSeen: number;
|
lastSeen: number;
|
||||||
fixed?: boolean;
|
fixed?: boolean;
|
||||||
|
/** Cumulative touch count (per session). Grows monotonically on every
|
||||||
|
* world.touch that lands here. Used by the render to compute a
|
||||||
|
* persistent "heat map" tint so files touched many times stay visibly
|
||||||
|
* warmer than virgin files even at heat=0. */
|
||||||
|
touchCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GPawn {
|
export interface GPawn {
|
||||||
@@ -144,6 +149,7 @@ export class WorldEngine {
|
|||||||
alpha: 1,
|
alpha: 1,
|
||||||
lastSeen: 0,
|
lastSeen: 0,
|
||||||
fixed: true,
|
fixed: true,
|
||||||
|
touchCount: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,6 +195,7 @@ export class WorldEngine {
|
|||||||
burst: 0,
|
burst: 0,
|
||||||
alpha: 1,
|
alpha: 1,
|
||||||
lastSeen: this.now,
|
lastSeen: this.now,
|
||||||
|
touchCount: 0,
|
||||||
};
|
};
|
||||||
this.nodes.set(id, n);
|
this.nodes.set(id, n);
|
||||||
}
|
}
|
||||||
@@ -291,6 +298,14 @@ export class WorldEngine {
|
|||||||
// permanent hot spot regardless of intent.
|
// permanent hot spot regardless of intent.
|
||||||
node.heat = Math.min(1, node.heat + w * 0.6);
|
node.heat = Math.min(1, node.heat + w * 0.6);
|
||||||
node.burst = Math.max(node.burst, w); // file ops (weight 1) → explosive burst
|
node.burst = Math.max(node.burst, w); // file ops (weight 1) → explosive burst
|
||||||
|
// Persistent per-file heat map — tracks TOTAL touches so hot files
|
||||||
|
// stay visibly warmer even after `heat` decays (see the render's
|
||||||
|
// touchCount → residual emissive intensity in WorldCanvas). We only
|
||||||
|
// count file: touches; landmarks and structural nodes shouldn't
|
||||||
|
// accumulate a "hot" halo just because agents converge on them.
|
||||||
|
if (e.nodeId.startsWith("file:")) {
|
||||||
|
node.touchCount = (node.touchCount ?? 0) + 1;
|
||||||
|
}
|
||||||
// activity type → signal colour: file=coral, tool=cyan, run=green,
|
// activity type → signal colour: file=coral, tool=cyan, run=green,
|
||||||
// repo=sky-blue, loop=amber
|
// repo=sky-blue, loop=amber
|
||||||
if (e.nodeId.startsWith("tool:")) p.fireColor = w >= 0.9 ? "#ff6f61" : "#5ec8d8";
|
if (e.nodeId.startsWith("tool:")) p.fireColor = w >= 0.9 ? "#ff6f61" : "#5ec8d8";
|
||||||
|
|||||||
Reference in New Issue
Block a user