Compare commits
3 Commits
gofai-know
...
gofai-symb
| Author | SHA1 | Date | |
|---|---|---|---|
| e61c03f52e | |||
| 5cde3ea630 | |||
| 34fb3f8d88 |
@@ -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
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user