Skip to content

feat: add RADAR governance visualisation with Vela NPC - #258

Draft
ktkorpela-prog wants to merge 20 commits into
pixel-agents-hq:mainfrom
ktkorpela-prog:feature/radar-governance
Draft

feat: add RADAR governance visualisation with Vela NPC#258
ktkorpela-prog wants to merge 20 commits into
pixel-agents-hq:mainfrom
ktkorpela-prog:feature/radar-governance

Conversation

@ktkorpela-prog

Copy link
Copy Markdown

Summary

Adds visual governance to Pixel Agents: when a Claude Code agent calls the radar_assess MCP tool, the agent character walks to a Risk Assessment desk where Vela — the first NPC in the system — delivers a verdict (PROCEED / HOLD / DENY) with a colour-coded animation.

⚠️ Draft — testing in progress, animation pipeline verified end-to-end.

Demo flow

  1. User places a radar_desk in the layout editor → Vela auto-spawns behind the desk
  2. User places any chair adjacent to the desk → it becomes the visitor seat
  3. Agent calls radar_assess → walks to visitor seat
  4. Vela animates a stamp; verdict badge appears above the agent + stamp mark on the desk
  5. After 3s, agent walks back to their own desk

What's included

Core

  • Parser detection of mcp__radar__radar_assess (extracts verdict, tier, riskScore, triggerReason, recommended)
  • CONSULT character state + walk arrival detection
  • FIFO radarQueue.ts with deduplication and seat management
  • NPC manager (npcManager.ts) — Vela lifecycle, stamp animation, T1/T2 visual distinction
  • Canvas-drawn verdict overlays (stamp mark + badge + bold text label)
  • Permanent "RISK ASSESSMENT" banner above the desk
  • Hover tooltip: "Vela / Risk Architect"

Bonus fixes for upstream

  • esbuild.js was pointing to providers/file/hooks/claude-hook.ts but the file is at providers/hook/claude/hooks/claude-hook.ts — fixed. The hook script was silently failing to build, causing Cannot find module errors when Claude Code invoked hooks.

Dependencies

Component Required for Notes
radar_desk placement Vela visible Furniture-driven; no radar_desk = no Vela = feature dormant
@essentianlabs/radar-mcp Verdict animations Without it, the feature is fully dormant — no errors, no warnings
LLM API key Optional T2 deep assessment Rules-engine assessment works without one

Tests

  • 29 new unit tests (153 total, all passing)
  • Coverage: extractRadarVerdict (18 tests), radarQueue (11 tests)
  • Manual test verified end-to-end with real radar_assess calls

Placeholder assets

vela.png and RADAR_DESK_FRONT.png are placeholders — designed for drop-in replacement. A pixel artist contribution is welcome (no code changes needed).

Test plan

  • Place radar_desk and adjacent chair in layout
  • Verify Vela spawns and "RISK ASSESSMENT" banner appears
  • Hover Vela → tooltip shows "Vela / Risk Architect"
  • Trigger radar_assess from a Claude Code agent
  • Agent walks to visitor seat, Vela animates, verdict displays, agent returns
  • Without radar_desk placed: feature fully dormant, no errors
  • Without @essentianlabs/radar-mcp installed: feature fully dormant, no errors

Backward compatibility

Fully backward compatible — all changes additive. New CONSULT state, NPC system, message types, and furniture entry don't affect existing behaviour.

🤖 Generated with Claude Code

Karina Korpela and others added 20 commits April 19, 2026 21:36
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Detects mcp__radar__radar_assess in JSONL tool_use blocks and emits
agentRadarStart/agentRadarVerdict messages. Extracts verdict (PROCEED/
HOLD/DENY), tier (1=rules, 2=LLM), riskScore, triggerReason, and
recommended from tool_result. Parse failure defaults to HOLD.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Simple queue managing visitor seat access at the RADAR desk.
Supports enqueue (with duplicate detection), dequeue, remove
(terminal close), and clear (desk removed). Pure functions,
no side effects — integration in OfficeState follows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Vela is the first NPC — a permanent character tied to the radar_desk
furniture. She spawns when radar_desk exists, despawns when removed.

Stamp animation: stamp_up → stamp_down → stamp_hold → idle.
T2 (LLM) assessments hold stamp_up longer (0.6s vs 0.4s) so Vela
visibly "thinks" before stamping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New CharacterState.CONSULT: agent sits at RADAR desk visitor seat
using typing animation while awaiting verdict. Walk state detects
arrival at visitor seat and transitions to CONSULT. Active repath
skipped during radar consultation to prevent seat redirect.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rkle

Canvas-drawn overlays for RADAR verdicts:
- Stamp mark (8×8): circle (PROCEED), pause bars (HOLD), X (DENY)
- Verdict badge (6×6): same shapes above agent's head
- T2 sparkle: white diamond indicator when verdict used LLM assessment
  (vs T1 rules-only). Positioned top-right of stamp/badge.
Both fade over 0.5s via alpha interpolation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wires together all RADAR governance modules:
- NPC manager synced on layout rebuild (Vela spawn/despawn)
- Radar queue with visitor seat detection
- handleRadarStart: enqueue agent, walk to desk or wait nearby
- handleRadarVerdict: deliver verdict, start countdown
- Verdict timer in update loop: return-to-seat + queue promotion
- Vela stamp triggered when queue agent arrives at CONSULT state
- Queue cleanup on removeAgent and radar_desk removal
- getCharactersForRender includes Vela in render list

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integration commit connecting all RADAR modules:
- renderer.ts: verdict badge overlay on characters, CONSULT sitting offset
- useExtensionMessages.ts: handle agentRadarStart, agentRadarVerdict,
  npcSpritesLoaded messages
- spriteData.ts: NPC palette (-1) loads Vela sprites, amber hue fallback
- assetLoader.ts: loadVelaSprite() for vela.png with graceful fallback
- PixelAgentsViewProvider.ts: send Vela sprites after character sprites

All 135 existing tests pass. No regressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Add RADAR_DESK_FRONT furniture asset (manifest.json + placeholder
   32x32 PNG with amber/dark colour scheme, backgroundTiles=1)
2. Add vela.png placeholder sprite (copy of char_0.png — drop-in
   replacement designed, no code changes needed for final art)
3. Connect getCharactersForRender() in OfficeCanvas so Vela renders
4. Wire drawStampMark into renderer pipeline via StampMarkState
   passed through renderFrame → renderScene
5. Fix tier timing: Vela starts stamping without tier, tier updated
   via setTier() when verdict arrives (tier comes with verdict, not
   with start)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
extractRadarVerdict (18 tests):
- Verdict extraction: status field, verdict fallback, preference, unknown values
- Tier extraction: T1, T2, missing, wrong type
- Extra fields: riskScore, triggerReason, recommended with type validation
- Error handling: invalid JSON, empty text, null/missing content, string content

radarQueue (11 tests):
- FIFO enqueue/dequeue ordering
- Deduplication (current + waiting)
- Remove from queue (current promotes next, waiting splice, unknown returns false)
- Clear returns all evicted agent IDs
- isInQueue membership check

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
First placeholder had a 4px dark bar at the top of the desk that
covered Vela's face when she sat at the desk. Reduced to a thin 2px
back lip so it still hints at 3D depth without blocking the character.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Design fix for real-world layout: users only need to place ONE chair
adjacent to the radar desk (the visitor seat). Vela now positions
herself at the back row of the desk (the backgroundTiles area) —
no chair required. Fixes: previously Vela claimed the only chair,
leaving no visitor seat, and handleRadarStart silently returned.

Changes:
- npcManager: syncWithLayout positions Vela at desk back, no seat
- npcManager: exports VELA_NAME and VELA_ROLE constants
- officeState.getCharacterAt: includes Vela in hit detection
- officeState.handleRadarStart: logs warning when no visitor seat
- ToolOverlay: shows "Vela — Risk Architect" on hover
- findVisitorSeat: accepts any adjacent seat (not just unassigned)

All 153 tests still pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Vela uses IDLE state (standing pose) when not stamping — prevents
  "typing in midair" visual when no chair is under her
- Vela switches to TYPE state during stamp animation for proper
  stamp_up/stamp_down frame rendering
- ToolOverlay uses new getCharacterById() that finds NPCs too —
  fixes broken hover tooltip on Vela
- Added permanent "RISK ASSESSMENT" label above the radar desk,
  rendered in ToolOverlay with pixel-panel styling

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously her feet were 8px above the desk top, which looked like
she was floating. Now her feet are at the desk top line so she
visually stands right behind the desk. Z-sort still keeps her
behind the desk's solid front face.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Vela pixel y positioned so her charZY < desk zY, rendering her
  behind the desk's solid front face. Her head and shoulders visible
  above the back lip, legs hidden behind the desk.
- RISK ASSESSMENT banner moved from 20px above desk top to 44px
  above, clearing Vela's head.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Chair tiles are blocked for all characters except their own assigned
seat. This prevented agents from pathfinding to Vela's visitor chair
(findPath returned empty because destination was blocked).

Now temporarily unblocks the visitor seat tile during:
- Outbound walk (handleRadarStart → walkAgentToVisitorSeat)
- Return walk (verdict timer expires → walk back to own seat, agent
  is currently ON the blocked visitor seat)

This lets BFS find a valid path in both directions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logs at parser detection, webview message receipt, and
handleRadarStart entry to help diagnose why the animation
isn't firing when radar_assess is called.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Stamp mark: 8x8 -> 14x14 with white outline circle for contrast
- Verdict badge: 6x6 -> 14x14 with white outline circle
- New verdict text label (PROCEED/HOLD/DENY) above agent during
  the verdict window — bold, coloured, much more visible
- Fix timer bug: timer was decrementing during walk, often expiring
  before agent reached CONSULT. Now timer only starts when agent
  enters CONSULT state with a verdict already set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Build was looking for hook source at providers/file/hooks/ but the
actual location is providers/hook/claude/hooks/. The mismatch caused
buildHooks() to silently skip, leaving dist/hooks/claude-hook.js
missing — which led to "Cannot find module" errors when Claude Code
tried to invoke the hooks installed in settings.json.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ktkorpela-prog ktkorpela-prog changed the title add RADAR governance visualisation with Vela NPCFeature/radar governance feat: add RADAR governance visualisation with Vela NPC May 3, 2026
@florintimbuc

Copy link
Copy Markdown
Collaborator

Hey @ktkorpela-prog, this is genuinely exciting work :D
We just merged a sizeable architecture refactor to main (for the v1.4 release) that changes how agents communicate and how state is managed - AgentRuntime + AgentStateStore + an AsyncAPI-defined WebSocket protocol, plus the four-package split (core/, server/, adapters/vscode/, webview-ui/).

That's going to cause meaningful rebase conflicts here, especially around the parser, character state, the hook-script paths etc. I'd really like to help you rebase this and work through the conflicts together!

I'll also spend some time thinking about how we can integrate this as a plugin/extension surface rather than a closed built-in feature, NPCs and governance overlays feel like exactly the kind of thing that should live alongside HookProvider, TeamProvider, Client, Adapter as a composable extension point (something like NPCProvider or OverlayProvider).

That way you keep ownership of Vela and the core stays lean. Let me sync after I take a closer look at the diff :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants