# Dashboard Entity Visualization (Memory Zone)

How entities from the knowledge graph are visualized in the Aquarium v4
observability dashboard -- the memory zone at the bottom of the canvas.

---

## 1. Memory Zone Overview

The memory zone renders extracted knowledge graph entities as glowing dots
in a sediment layer beneath the creature aquarium. It answers the question:
*what does the system remember?*

**Toggle:** Press `M` or click the Memory button.

### Split-screen layout

When toggled on, the canvas divides using `SPLIT_RATIO = 0.62`: organisms
compress into the top 62%, the memory zone fills the bottom 28%, and a 3%
gap holds the sediment line and column headers.

### splitAlpha transition

The transition is not instant. `EngineState.splitAlpha` lerps 0 to 1:

```typescript
const targetAlpha = state.entityViewMode === 'split' ? 1 : 0;
state.splitAlpha += (targetAlpha - state.splitAlpha) * 0.06;
```

`drawMemoryZone()` applies this as `ctx.globalAlpha`, producing a smooth
fade. When `splitAlpha <= 0`, the function returns immediately (zero draw
cost when hidden). Entity layout updates also skip when alpha is 0.

### Sediment line

An amber horizontal gradient at `h * 0.65` marks the boundary. A spaced
"M E M O R Y" label sits 8px above the line. The gradient fades at both
edges for a soft vignette effect.

**Source:** `services/context-engine/dashboard/src/canvas/memoryZone.ts`

---

## 2. Entity Type Columns

Eight swim-lane columns divide the memory zone, one per extraction type:

| Col | Type       | Color                        | Visual      |
|-----|------------|------------------------------|-------------|
| 0   | **Person** | `rgba(220, 90, 50, 0.8)`    | Burnt orange |
| 1   | **Place**  | `rgba(200, 150, 50, 0.8)`   | Amber gold   |
| 2   | **Topic**  | `rgba(50, 200, 100, 0.8)`   | Emerald      |
| 3   | **Promise**| `rgba(0, 200, 200, 0.8)`    | Cyan         |
| 4   | **Event**  | `rgba(80, 130, 220, 0.8)`   | Steel blue   |
| 5   | **Emotion**| `rgba(200, 80, 180, 0.8)`   | Magenta      |
| 6   | **Decision**| `rgba(0, 200, 180, 0.8)`   | Teal         |
| 7   | **Habit**  | `rgba(220, 160, 50, 0.8)`   | Gold         |

Headers render at `sedimentY + 18px` with a colored dot and label. Usable
width excludes the 150px navigator panel: `usableW = w - 150`.

Within each column, entities arrange in a square grid:
`cols = ceil(sqrt(totalOfType))`, spread = `columnWidth * 0.4`. Entities
are sorted by name for stable positioning across poll cycles.

**Source:** `services/context-engine/dashboard/src/entities/types.ts`

---

## 3. Tier Depth Bands (L0 / L1 / L2)

Three horizontal bands below the column headers represent memory tiers:

| Tier | Label              | Band Alpha | Meaning                       |
|------|--------------------|------------|-------------------------------|
| L0   | `recent`           | 0.04       | Working memory, recent convos |
| L1   | `consolidated`     | 0.05       | Survived multiple sessions    |
| L2   | `deep`             | 0.06       | Core knowledge, rarely changes|

Band heights are **proportional** to entity count per tier. If 60% of
entities are L0, the L0 band gets 60% of the vertical space. Dashed amber
dividers (`setLineDash([4, 6])`) separate tiers. Each label shows a live
count: `L0 · recent (12)`.

The entity zone occupies `h * 0.28` starting at `sedimentY + 28px`.

---

## 4. EntityNode Visual Design

Each entity is an `EntityNode` instance ported from v3 (lines 1634-1822).

### Sizing

Radius scales with salience (0-1):

```
baseRadius = 3 + salience * 5   // range: 3px to 8px
```

### Draw layers (bottom to top)

1. **Outer glow** -- Radial gradient, entity color at `salience * 0.3` alpha,
   fading to transparent at `radius * 3`.
2. **Core dot** -- Solid circle at `radius` with `salience * 0.7` alpha.
3. **Promotion trail** -- Downward glow streak on tier transitions (2s fade).
   Narrow trapezoid with linear gradient, length = `25 * (promotionTrail / 2)`.
4. **Evergreen halo** -- Thin white ring (`rgba(255,255,255, salience * 0.2)`,
   lineWidth 0.5) at `radius + 2` for Person/Place types (never decay, ADR-007).

### Beams

When an organism activates an entity, a beam draws from organism to entity dot.
Fades over 2 seconds. Beams render in a separate pass **behind** dots.

### Drift

Each node has a random `driftPhase`. Per frame:

```typescript
x += Math.sin(globalTime * 0.3 + driftPhase) * 0.1
y += Math.cos(globalTime * 0.2 + driftPhase * 1.3) * 0.05
```

Gentle underwater motion without displacing nodes from their grid positions.

**Source:** `services/context-engine/dashboard/src/entities/node.ts`

---

## 5. Interaction Model

### Hit testing

`EntityNode.hitTest(mx, my)` uses a generous circular test:
`distance(mouse, center) < radius * 3 + 5px`.

### Current state (v4)

Entity click handling is **not yet wired**. The canvas click handler in
`main.ts` tests organisms and portals but does not delegate to entity nodes.

### v3 reference (target behavior)

In v3, clicking an entity opened a context card showing: entity name/type/tier,
salience score, episode count, evergreen status, up to 5 timestamped events,
and a chat thread. Card positioned near the clicked entity, constrained to
canvas bounds. This is Phase D/F work in the v4 roadmap.

---

## 6. Thought Bubbles System

Thought bubbles are rising bioluminescent spheres emitted when observability
events fire. They rise from organisms in world-space, carrying brief event
summaries.

### ThoughtBubble class

Each bubble has:

| Property | Normal | Milestone (error/metric) |
|----------|--------|--------------------------|
| Radius | 6px | 16px |
| Lifetime | 4s | 8s |
| Summary text | Hidden | Shown when alpha > 0.3 |

Physics: rise upward (`vy = -(20..35) px/s`) with horizontal wobble that
damps at 0.98/frame. Alpha fades linearly over lifetime.

Draw layers: radial glow gradient, core dot, optional summary text (first
40 chars of `event.reasoning`, milestone only).

**Source:** `services/context-engine/dashboard/src/bubbles/bubble.ts`

### BubbleManager

The manager controls the bubble pool:

| Parameter | Value | Purpose |
|-----------|-------|---------|
| `MAX_BUBBLES` | 20 | FIFO eviction when pool full |
| `MIN_EMIT_INTERVAL` | 0.5s | Rate limit per creature |
| `emittedIds` | Set | Dedup -- prevents double-emission |

Lifecycle:
1. `emit(event, organisms)` -- checks dedup + rate limit, finds source
   organism, creates bubble at `(orgX +/- 10px, orgY - radius)`
2. `update(dt)` -- advances physics, prunes dead bubbles
3. `draw(ctx)` -- renders in world-space (engine draw order step 5.6)

Also exposes: `hitTest(mx, my)` for future click interaction, `clear()` for
mode switches, `size` getter for active count.

**Source:** `services/context-engine/dashboard/src/bubbles/manager.ts`

---

## 7. Animation Summary

| Animation | Trigger | Duration | Visual |
|-----------|---------|----------|--------|
| **Pulse** | New entity or update | 1.5s | Radius oscillates +/-3px at 8 Hz |
| **Promotion trail** | Tier change (L0->L1, etc.) | 2.0s | Downward glow streak, shrinking |
| **Evergreen halo** | Person/Place type | Permanent | Thin white ring at radius+2 |
| **Decay dimming** | Nightly phase (v3 only) | Gradual | targetGlowAlpha reduces 30% |
| **Gentle drift** | Always | Continuous | Sinusoidal x/y perturbation |
| **Glow lerp** | Salience change | `dt * 2` rate | Smooth brightness transition |
| **Position lerp** | Mode switch / layout | 0.05 (enter) / 0.08 (exit) | Float into place |
| **Beam** | Organism activates entity | 2.0s | Line fading from org to dot |
| **Bubble rise** | Observability event | 4s / 8s | Rising sphere from organism |

---

## 8. Engine Integration

### Tick loop (`engine.ts`)

Three stages per frame: (1) mode transition detection -- re-layout organisms,
start/stop polling (lines 176-187); (2) splitAlpha lerp (lines 189-192);
(3) entity + bubble update when `splitAlpha > 0` -- each node runs
`layout()` then `update()`, bubble manager runs `update(dt)` (lines 207-216).

### Draw order

The memory zone draws in **screen-space** (after organisms in world-space):

```typescript
// engine.ts render() — step 3.2
applyScreenTransform(ctx, dpr);
drawMemoryZone(ctx, w, h, state.splitAlpha, entityNodes, tierCounts);
```

Entity dots do not zoom/pan with the organism viewport -- they stay fixed at
the bottom of the screen. Thought bubbles draw in **world-space** (step 5.6)
so they zoom with organisms.

### Keyboard shortcut

| Key | Action |
|-----|--------|
| M | Toggle `entityViewMode` between `hidden` and `split` |

**Source:** `services/context-engine/dashboard/src/canvas/engine.ts`,
`services/context-engine/dashboard/src/main.ts` (lines 503-505)

---

## 9. Implementation Status vs v3

| Feature                          | v3 Prototype | v4 Dashboard | Status |
|----------------------------------|:---:|:---:|--------|
| Sediment line + label            | Yes | Yes | Done |
| 8-column type headers            | Yes | Yes | Done |
| 3-tier depth bands (proportional)| Yes | Yes | Done |
| Entity dot (glow + core)        | Yes | Yes | Done |
| Promotion trail animation        | Yes | Yes | Done |
| Evergreen halo (Person/Place)    | Yes | Yes | Done |
| Drift oscillation                | Yes | Yes | Done |
| Pulse on creation/update         | Yes | Yes | Done |
| Organism-entity beams            | Yes | Yes | Done |
| Entity hit testing               | Yes | Yes | Method exists |
| Click entity -> context card     | Yes | No  | Phase D/F |
| Entity-entity connection lines   | Yes | No  | Needs backend relationship data |
| Nightly decay dimming            | Yes | No  | Planned |
| Dynamic tier promotions (replay) | Yes | Partial | Backend-driven only |
| Thought bubbles                  | Yes | Yes | Done (BubbleManager) |
| Split-screen alpha fade          | Instant | Lerp | Improved in v4 |
| DPR-aware rendering              | No  | Yes | Improved in v4 |
| REST polling data source         | No  | Yes | v3 was static/hardcoded |
| TypeScript + unit tests          | No  | Yes | 78 tests |

---

## 10. Data Source: SSE Events + REST Entities

### Real-time events (SSE)

Observability events flow through SSE for organism activation and thought
bubbles:

```
GET /v1/events/stream?x_internal_token=<token>
```

`EventSource2` connects with auto-reconnect. On connect, the backend sends
the last 50 events as catch-up (only events newer than 30s trigger creature
activation). Events drive `BubbleManager.emit()` and organism glow -- they
do not directly update entity dots.

### Entity data (REST polling)

`EntityStore` polls `GET /v1/entities` (with `X-Internal-Token` header) every
30 seconds when the memory zone is visible. Response: `{ entities: EntityData[] }`
with fields: `entity_id`, `type`, `name`, `confidence`, `properties`,
`sensitivity`, `first_seen`, `last_seen`, `tier`, `salience`, `episode_count`.

### Reconciliation

`EntityStore.reconcile()` diffs fetched data against existing nodes: new
entities get an `EntityNode` with pulse, updated entities merge fields (and
trigger `promote()` on tier change), removed entities are deleted. After
reconciliation, `reindex()` recomputes per-type layout indices (name-sorted)
and tier counts. Polling stops when hidden. A future Phase D enhancement may
add entity-specific SSE events for instant dot appearance.

**Source:** `services/context-engine/dashboard/src/entities/store.ts`,
`services/context-engine/dashboard/src/events/source.ts`
