@andespindola/brainlink 0.1.0-beta.108 → 0.1.0-beta.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -603,6 +603,7 @@ The graph UI shows:
603
603
  - adaptive CPU safeguards for large graphs: idle frame pacing, throttled background physics updates and cached viewport dimensions to reduce redraw/layout overhead while preserving interaction responsiveness
604
604
  - hierarchical hot-path optimizations reduce per-frame allocations and repeated scans during layered cluster expansion and edge projection
605
605
  - hierarchical edge projection now caches hub membership and node-to-cluster resolution per frame to keep large recursive subgraph rendering smooth during continuous zoom and pan
606
+ - hierarchical projection now uses stronger perspective yaw/pitch and depth-based render ordering so layered subgraphs read as a true 3D field instead of a flat expansion
606
607
  - WebGL node and edge acceleration when supported, falling back to Canvas 2D without changing graph behavior
607
608
  - compact macro-to-micro density progression so reset keeps the graph mass oriented and zoom-in separates local neighborhoods progressively
608
609
  - graph camera treats hub-centered navigation as structural only when the hub is dominant; diffuse stress graphs reset and zoom around the full graph mass
@@ -35,6 +35,10 @@ const ecosystemDepthNear = 80
35
35
  const ecosystemDepthFar = 2600
36
36
  const ecosystemDepthPerspective = 620
37
37
  const ecosystemDepthTiltY = 0.24
38
+ const ecosystemDepthYaw = 0.22
39
+ const ecosystemDepthPitch = 0.16
40
+ const ecosystemDepthRadialGain = 0.09
41
+ const ecosystemDepthOrbitalMaxOffset = 160
38
42
  const ecosystemDepthMinScale = 0.24
39
43
  const ecosystemDepthOpacityFloor = 0.2
40
44
  const zoomRecoveryGuardMs = 4200
@@ -1207,12 +1211,23 @@ const ecosystemDepthForCluster = (cluster, levelIndexMap) => {
1207
1211
 
1208
1212
  const projectEcosystemPoint = (x, y, depth, anchor) => {
1209
1213
  const safeDepth = Math.max(0, depth)
1210
- const factor = ecosystemDepthPerspective / (ecosystemDepthPerspective + safeDepth)
1211
- const verticalTilt = safeDepth * ecosystemDepthTiltY
1214
+ const dx = x - anchor.x
1215
+ const dy = y - anchor.y
1216
+ const yawSin = Math.sin(ecosystemDepthYaw)
1217
+ const yawCos = Math.cos(ecosystemDepthYaw)
1218
+ const pitchSin = Math.sin(ecosystemDepthPitch)
1219
+ const pitchCos = Math.cos(ecosystemDepthPitch)
1220
+ const rotatedX = dx * yawCos + safeDepth * yawSin
1221
+ const rotatedZ = Math.max(0, safeDepth * yawCos - dx * yawSin)
1222
+ const rotatedY = dy * pitchCos - rotatedZ * pitchSin
1223
+ const projectedDepth = Math.max(0, rotatedZ + Math.max(0, dy * pitchSin))
1224
+ const factor = ecosystemDepthPerspective / (ecosystemDepthPerspective + projectedDepth)
1225
+ const verticalTilt = projectedDepth * ecosystemDepthTiltY
1212
1226
  return {
1213
- x: anchor.x + (x - anchor.x) * factor,
1214
- y: anchor.y + (y - anchor.y) * factor - verticalTilt,
1215
- factor
1227
+ x: anchor.x + rotatedX * factor,
1228
+ y: anchor.y + rotatedY * factor - verticalTilt,
1229
+ factor,
1230
+ projectedDepth
1216
1231
  }
1217
1232
  }
1218
1233
 
@@ -1223,7 +1238,13 @@ const applyEcosystemDepthProjection = (clusters, edges, anchor) => {
1223
1238
 
1224
1239
  for (let index = 0; index < clusters.length; index += 1) {
1225
1240
  const cluster = clusters[index]
1226
- const depth = ecosystemDepthForCluster(cluster, levelIndexMap)
1241
+ const baseDepth = ecosystemDepthForCluster(cluster, levelIndexMap)
1242
+ const radialDistance = Math.hypot(cluster.x - anchor.x, cluster.y - anchor.y)
1243
+ const radialOffset = cluster.isHub ? 0 : Math.min(320, radialDistance * ecosystemDepthRadialGain)
1244
+ const orbitalOffset = cluster.isHub
1245
+ ? 0
1246
+ : Math.sin(Math.atan2(cluster.y - anchor.y, cluster.x - anchor.x) * 2.2) * ecosystemDepthOrbitalMaxOffset
1247
+ const depth = Math.max(0, baseDepth + radialOffset + orbitalOffset)
1227
1248
  const projected = projectEcosystemPoint(cluster.x, cluster.y, depth, anchor)
1228
1249
  const baseOpacity = Number.isFinite(cluster.lodOpacity) ? cluster.lodOpacity : 1
1229
1250
  const depthScale = ecosystemDepthMinScale + (1 - ecosystemDepthMinScale) * projected.factor
@@ -1236,7 +1257,7 @@ const applyEcosystemDepthProjection = (clusters, edges, anchor) => {
1236
1257
  x: projected.x,
1237
1258
  y: projected.y,
1238
1259
  lodOpacity: baseOpacity * depthOpacity,
1239
- depth,
1260
+ depth: projected.projectedDepth,
1240
1261
  depthScale
1241
1262
  }
1242
1263
  projectedClusters.push(projectedCluster)
@@ -2981,6 +3002,9 @@ const clusterRadiusPx = cluster => {
2981
3002
  const clusterOpacity = cluster =>
2982
3003
  Math.max(0, Math.min(1, Number.isFinite(cluster.lodOpacity) ? cluster.lodOpacity : 1))
2983
3004
 
3005
+ const clusterDepth = cluster => Number.isFinite(cluster.depth) ? cluster.depth : ecosystemDepthNear
3006
+ const clusterDepthScale = cluster => Number.isFinite(cluster.depthScale) ? cluster.depthScale : 1
3007
+
2984
3008
  const worldViewportBounds = () => {
2985
3009
  const width = Math.max(state.viewport.width, 320)
2986
3010
  const height = Math.max(state.viewport.height, 320)
@@ -3400,6 +3424,8 @@ const render = now => {
3400
3424
  ctx.save()
3401
3425
  ctx.translate(state.transform.x, state.transform.y)
3402
3426
  ctx.scale(state.transform.scale, state.transform.scale)
3427
+ const orderedClusters = [...state.renderClusters]
3428
+ .sort((left, right) => clusterDepth(right) - clusterDepth(left))
3403
3429
  const safeScale = Math.max(state.transform.scale, 0.0001)
3404
3430
  if (state.renderClusterEdges.length > 0) {
3405
3431
  for (let index = 0; index < state.renderClusterEdges.length; index += 1) {
@@ -3408,15 +3434,17 @@ const render = now => {
3408
3434
  if (edgeOpacity <= 0.01) {
3409
3435
  continue
3410
3436
  }
3437
+ const depthScale = Math.min(clusterDepthScale(edge.sourceCluster), clusterDepthScale(edge.targetCluster))
3438
+ const widthScale = 0.6 + depthScale * 0.9
3411
3439
  ctx.beginPath()
3412
3440
  ctx.moveTo(edge.sourceCluster.x, edge.sourceCluster.y)
3413
3441
  ctx.lineTo(edge.targetCluster.x, edge.targetCluster.y)
3414
- ctx.lineWidth = 1.2 / safeScale
3442
+ ctx.lineWidth = (1.2 * widthScale) / safeScale
3415
3443
  ctx.strokeStyle = 'rgba(153, 165, 181, ' + (edge.inferred ? 0.14 : 0.22) * edgeOpacity + ')'
3416
3444
  ctx.stroke()
3417
3445
  }
3418
3446
  }
3419
- state.renderClusters.forEach(cluster => {
3447
+ orderedClusters.forEach(cluster => {
3420
3448
  const isMacro = cluster.id === 'macro-galaxy'
3421
3449
  const isEcosystem = String(cluster.id).startsWith('ecosystem-')
3422
3450
  const isHub = Boolean(cluster.isHub)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.108",
3
+ "version": "0.1.0-beta.109",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",