Compare commits

..

3 Commits

Author SHA1 Message Date
e61c03f52e feat: style Planner HUD element
Some checks failed
CI / validate (pull_request) Failing after 5s
2026-03-30 02:24:25 +00:00
5cde3ea630 feat: add Planner log to HUD 2026-03-30 02:24:24 +00:00
34fb3f8d88 feat: implement Symbolic Planner (STRIPS-like) 2026-03-30 02:24:22 +00:00
3 changed files with 101 additions and 4 deletions

View File

@@ -210,6 +210,77 @@ class Blackboard {
}
}
// ═══ SYMBOLIC PLANNER (STRIPS-LIKE) ═══
class SymbolicPlanner {
constructor() {
this.actions = [];
this.currentPlan = [];
}
addAction(name, preconditions, effects) {
this.actions.push({ name, preconditions, effects });
}
findPlan(initialState, goalState) {
// Simple BFS for planning (for small state spaces)
let queue = [[initialState, []]];
let visited = new Set([JSON.stringify(initialState)]);
while (queue.length > 0) {
let [state, plan] = queue.shift();
if (this.isGoalReached(state, goalState)) {
return plan;
}
for (let action of this.actions) {
if (this.arePreconditionsMet(state, action.preconditions)) {
let nextState = { ...state, ...action.effects };
let stateStr = JSON.stringify(nextState);
if (!visited.has(stateStr)) {
visited.add(stateStr);
queue.push([nextState, [...plan, action.name]]);
}
}
}
}
return null;
}
isGoalReached(state, goal) {
for (let key in goal) {
if (state[key] !== goal[key]) return false;
}
return true;
}
arePreconditionsMet(state, preconditions) {
for (let key in preconditions) {
if (state[key] < preconditions[key]) return false;
}
return true;
}
logPlan(plan) {
this.currentPlan = plan;
const container = document.getElementById('planner-log-content');
if (container) {
container.innerHTML = '';
if (!plan || plan.length === 0) {
container.innerHTML = '<div class="planner-empty">NO ACTIVE PLAN</div>';
return;
}
plan.forEach((step, i) => {
const div = document.createElement('div');
div.className = 'planner-step';
div.innerHTML = `<span class="step-num">${i+1}.</span> ${step}`;
container.appendChild(div);
});
}
}
}
let symbolicPlanner;
let knowledgeGraph;
let blackboard;
let symbolicEngine;
@@ -225,9 +296,11 @@ async function init() {
knowledgeGraph = new KnowledgeGraph();
blackboard = new Blackboard();
symbolicEngine = new SymbolicEngine();
symbolicPlanner = new SymbolicPlanner();
setupKnowledgeBase();
setupSymbolicRules();
setupPlannerActions();
const canvas = document.getElementById('nexus-canvas');
renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
@@ -383,6 +456,12 @@ function setupSymbolicRules() {
agentFSMs['timmy'].addTransition('ALERT', 'IDLE', (facts) => facts.get('stability') >= 0.7);
}
function setupPlannerActions() {
symbolicPlanner.addAction('Divert Power', { energy: 20 }, { mode: 'RECOVERY' });
symbolicPlanner.addAction('Stabilize Matrix', { energy: 50, mode: 'RECOVERY' }, { stability: 1.0 });
symbolicPlanner.addAction('Open Portal', { energy: 80, stability: 1.0 }, { portals: 'online' });
}
function updateSymbolicAI(delta, elapsed) {
// Sync facts from world state
const terminal = batcaveTerminals.find(t => t.title === 'NEXUS COMMAND');
@@ -395,6 +474,14 @@ function updateSymbolicAI(delta, elapsed) {
// Update Blackboard
blackboard.write('nexus_energy', state.tower.energy, 'NEXUS_COMMAND');
blackboard.write('nexus_stability', state.matrix.stability, 'NEXUS_COMMAND');
// Run Planner if stability is low
if (state.matrix.stability < 0.5 && (!symbolicPlanner.currentPlan || symbolicPlanner.currentPlan.length === 0)) {
const initialState = { energy: state.tower.energy, stability: state.matrix.stability, mode: 'NORMAL' };
const goalState = { stability: 1.0 };
const plan = symbolicPlanner.findPlan(initialState, goalState);
symbolicPlanner.logPlan(plan);
}
}
// Run reasoning engine

View File

@@ -103,6 +103,10 @@
<div class="blackboard-log-header">BLACKBOARD (SHARED MEMORY)</div>
<div id="blackboard-log-content" class="blackboard-log-content"></div>
</div>
<div class="hud-planner-log" id="hud-planner-log" aria-label="Symbolic Planner">
<div class="planner-log-header">SYMBOLIC PLANNER (STRIPS)</div>
<div id="planner-log-content" class="planner-log-content"></div>
</div>
</div>
<!-- Bottom: Chat Interface -->

View File

@@ -568,7 +568,7 @@ canvas#nexus-canvas {
}
/* Agent Log HUD */
.hud-agent-log, .hud-symbolic-log, .hud-blackboard-log {
.hud-agent-log, .hud-symbolic-log, .hud-blackboard-log, .hud-planner-log {
position: absolute;
right: var(--space-3);
width: 280px;
@@ -583,8 +583,9 @@ canvas#nexus-canvas {
.hud-agent-log { top: var(--space-3); }
.hud-symbolic-log { top: 160px; border-left-color: var(--color-gold); }
.hud-blackboard-log { top: 320px; border-left-color: #7b5cff; }
.hud-planner-log { top: 480px; border-left-color: #ff4a4a; }
.agent-log-header, .symbolic-log-header, .blackboard-log-header {
.agent-log-header, .symbolic-log-header, .blackboard-log-header, .planner-log-header {
font-family: var(--font-display);
color: var(--color-primary);
letter-spacing: 0.1em;
@@ -594,14 +595,15 @@ canvas#nexus-canvas {
.symbolic-log-header { color: var(--color-gold); }
.blackboard-log-header { color: #7b5cff; }
.planner-log-header { color: #ff4a4a; }
.agent-log-content, .symbolic-log-content, .blackboard-log-content {
.agent-log-content, .symbolic-log-content, .blackboard-log-content, .planner-log-content {
display: flex;
flex-direction: column;
gap: 4px;
}
.symbolic-log-entry, .blackboard-entry {
.symbolic-log-entry, .blackboard-entry, .planner-step {
animation: log-fade-in 0.5s ease-out forwards;
opacity: 0;
display: flex;
@@ -618,6 +620,10 @@ canvas#nexus-canvas {
.bb-source { color: #7b5cff; font-weight: bold; font-size: 9px; }
.bb-key { color: #fff; opacity: 0.6; }
.bb-value { color: var(--color-primary); }
.planner-step { flex-direction: row; gap: 8px; align-items: center; }
.step-num { color: #ff4a4a; font-weight: bold; }
.planner-empty { color: rgba(255, 255, 255, 0.3); font-style: italic; text-align: center; padding: 10px; }
.agent-log-entry {
animation: log-fade-in 0.5s ease-out forwards;
opacity: 0;