world: always-on topology edges (V2)
The parent → child hierarchy was already drawn between structural nodes, but agent pawns had no visible tie to their team — you saw a swarm of free-floating disks with no legible affiliation. Two new persistent edge kinds: - Pawn → home team: every agent gets a thin line to their team node, so the workspace skeleton reads visually. Line stays whether the pawn is idle or working. - Pawn → active landmark: when a pawn is targeting a repo:<id> or loop:<id> orb (the soft-convergence state we shipped in V1 + the research pass), an edge appears too. Bridges the gap between world.touch bumps so the affinity stays visible during quiet periods. Focus mode culling honored — pawn/target edges only draw if both ends are in the visible subtree, so the Gource repo view still reads clean. All three edge types share the same LineBasicMaterial + draw call, so this is free at render time.
This commit is contained in:
@@ -759,8 +759,17 @@ export function WorldCanvas({ roots, selectedId, onSelect, expanded, onToggleExp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// edges — parent → child hierarchy. In focus mode only draw the
|
// edges — the always-on topology skeleton, drawn in two passes so
|
||||||
// subtree so the repo detail view reads as a clean file tree.
|
// the World reads as a system, not just a swarm:
|
||||||
|
//
|
||||||
|
// 1. Parent → child hierarchy (org → company → team → landmark).
|
||||||
|
// In focus mode we cull to the subtree so the repo detail view
|
||||||
|
// still reads as a clean file tree.
|
||||||
|
// 2. Pawn → home team (thin persistent line so every agent visibly
|
||||||
|
// "lives" in their team's neighborhood).
|
||||||
|
// 3. Pawn → its current landmark target (repo: or loop:) when the
|
||||||
|
// pawn is actively engaging one, so the agent↔project affinity
|
||||||
|
// stays visible between world.touch bumps.
|
||||||
const epos: number[] = [];
|
const epos: number[] = [];
|
||||||
for (const n of engine.nodes.values()) {
|
for (const n of engine.nodes.values()) {
|
||||||
if (!n.parentId) continue;
|
if (!n.parentId) continue;
|
||||||
@@ -771,6 +780,26 @@ export function WorldCanvas({ roots, selectedId, onSelect, expanded, onToggleExp
|
|||||||
if (visibleNodes && !visibleNodes.has(p.id)) continue;
|
if (visibleNodes && !visibleNodes.has(p.id)) continue;
|
||||||
epos.push(n.x, n.y, 0, p.x, p.y, 0);
|
epos.push(n.x, n.y, 0, p.x, p.y, 0);
|
||||||
}
|
}
|
||||||
|
// Pawn → home + pawn → active landmark. Kept in the same buffer so
|
||||||
|
// the whole skeleton renders as one draw call with a single opacity.
|
||||||
|
for (const p of engine.pawns.values()) {
|
||||||
|
const home = p.homeId ? engine.nodes.get(p.homeId) : undefined;
|
||||||
|
if (home && home.tier !== "root") {
|
||||||
|
if (!visibleNodes || (visibleNodes.has(p.id) && visibleNodes.has(home.id))) {
|
||||||
|
epos.push(p.x, p.y, 0, home.x, home.y, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (p.targetId) {
|
||||||
|
const t = engine.nodes.get(p.targetId);
|
||||||
|
if (
|
||||||
|
t &&
|
||||||
|
(t.tier === "repo" || t.tier === "loop") &&
|
||||||
|
(!visibleNodes || (visibleNodes.has(p.id) && visibleNodes.has(t.id)))
|
||||||
|
) {
|
||||||
|
epos.push(p.x, p.y, 0, t.x, t.y, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
edgeGeom.setAttribute("position", new THREE.Float32BufferAttribute(epos, 3));
|
edgeGeom.setAttribute("position", new THREE.Float32BufferAttribute(epos, 3));
|
||||||
edgeGeom.attributes.position.needsUpdate = true;
|
edgeGeom.attributes.position.needsUpdate = true;
|
||||||
edgeGeom.setDrawRange(0, epos.length / 3);
|
edgeGeom.setDrawRange(0, epos.length / 3);
|
||||||
|
|||||||
Reference in New Issue
Block a user