Vikingdude81 commited on
Commit
0e97e2c
·
verified ·
1 Parent(s): db4269f

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. README.md +54 -10
  2. index.html +603 -19
README.md CHANGED
@@ -1,10 +1,54 @@
1
- ---
2
- title: World Model Planner
3
- emoji: 💻
4
- colorFrom: pink
5
- colorTo: pink
6
- sdk: static
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Robust World Model Planner
3
+ emoji:
4
+ colorFrom: indigo
5
+ colorTo: blue
6
+ sdk: static
7
+ pinned: false
8
+ license: mit
9
+ short_description: Interactive world model planning simulation
10
+ ---
11
+
12
+ # ⚡ Robust World Model Planner
13
+
14
+ **An interactive visualization comparing different world model approaches for robot navigation and planning.**
15
+
16
+ ## What Is This?
17
+
18
+ This demo illustrates concepts from the paper *"Closing the Train-Test Gap for Gradient-Based World Model Planning"*. It shows how different world model training strategies affect an agent's ability to navigate obstacle-rich environments.
19
+
20
+ ## The Three Models
21
+
22
+ | Model | Description | Color |
23
+ |-------|-------------|-------|
24
+ | **STANDARD_DINO** | Baseline frozen encoder - noisy gradients | 🟣 Fuchsia |
25
+ | **ONLINE_WM** | Online fine-tuning - moderate improvement | 🔵 Blue |
26
+ | **ADVERSARIAL_WM** | Adversarial training - smooth, robust | 🔵 Cyan |
27
+
28
+ ## Key Concepts
29
+
30
+ - **Steering Gain**: How strongly the agent avoids obstacles
31
+ - **Scan Horizon**: How far ahead the agent "sees" (lookahead radius)
32
+ - **Loss Surface**: Adversarial training smooths the optimization landscape
33
+ - **Gradient Field**: Visualize the planning gradients across the map
34
+
35
+ ## How To Use
36
+
37
+ 1. **Select a Model** from the sidebar (try Adversarial for best results)
38
+ 2. **Choose an Environment** (Simple → Wall → Complex)
39
+ 3. **Click "Execute"** to start navigation
40
+ 4. **Click anywhere on the map** to set a new target
41
+ 5. **Toggle "Matrix_View"** to see the gradient field
42
+ 6. **Explore tabs**: Live Feed, Data Surface (loss landscape), Analytics (benchmarks)
43
+
44
+ ## Why Adversarial?
45
+
46
+ Standard world models have noisy, non-convex loss surfaces that make gradient-based planning unreliable. Adversarial training:
47
+ - ✅ Smooths the loss landscape
48
+ - ✅ Provides consistent gradients
49
+ - ✅ Enables reliable obstacle avoidance
50
+ - ✅ Closes the train-test gap
51
+
52
+ ---
53
+
54
+ *Based on research: [Closing the Train-Test Gap](https://github.com/qw3rtman/robust-world-model-planning) | Built with React + Recharts*
index.html CHANGED
@@ -1,19 +1,603 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Robust World Model Planner</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
9
+ <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
10
+ <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
11
+ <script src="https://unpkg.com/recharts@2.10.3/umd/Recharts.js"></script>
12
+ <style>
13
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
14
+ .animate-pulse { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
15
+ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
16
+ .animate-fade-in { animation: fadeIn 0.5s ease-out; }
17
+ </style>
18
+ </head>
19
+ <body class="bg-slate-950">
20
+ <div id="root"></div>
21
+
22
+ <script type="text/babel">
23
+ const { useState, useEffect, useRef, useCallback } = React;
24
+ const { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, BarChart, Bar } = Recharts;
25
+
26
+ // Inline SVG Icons
27
+ const Play = () => <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>;
28
+ const Pause = () => <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg>;
29
+ const RefreshCw = () => <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>;
30
+ const BarChart2 = () => <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="20" x2="18" y2="10"></line><line x1="12" y1="20" x2="12" y2="4"></line><line x1="6" y1="20" x2="6" y2="14"></line></svg>;
31
+ const Map = () => <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"></polygon><line x1="8" y1="2" x2="8" y2="18"></line><line x1="16" y1="6" x2="16" y2="22"></line></svg>;
32
+ const Zap = () => <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>;
33
+ const Layers = () => <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polygon points="12 2 2 7 12 12 22 7 12 2"></polygon><polyline points="2 17 12 22 22 17"></polyline><polyline points="2 12 12 17 22 12"></polyline></svg>;
34
+ const AlertTriangle = () => <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>;
35
+ const Sliders = () => <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="4" y1="21" x2="4" y2="14"></line><line x1="4" y1="10" x2="4" y2="3"></line><line x1="12" y1="21" x2="12" y2="12"></line><line x1="12" y1="8" x2="12" y2="3"></line><line x1="20" y1="21" x2="20" y2="16"></line><line x1="20" y1="12" x2="20" y2="3"></line><line x1="1" y1="14" x2="7" y2="14"></line><line x1="9" y1="8" x2="15" y2="8"></line><line x1="17" y1="16" x2="23" y2="16"></line></svg>;
36
+ const Database = () => <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path></svg>;
37
+ const Terminal = () => <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="4 17 10 11 4 5"></polyline><line x1="12" y1="19" x2="20" y2="19"></line></svg>;
38
+ const Grid = () => <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg>;
39
+ const ZapSmall = () => <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>;
40
+
41
+ const WorldModelDashboard = () => {
42
+ const [activeTab, setActiveTab] = useState('simulation');
43
+ const [isRunning, setIsRunning] = useState(false);
44
+ const [modelType, setModelType] = useState('adversarial');
45
+ const [plannerType, setPlannerType] = useState('gd');
46
+ const [mapType, setMapType] = useState('maze');
47
+ const [status, setStatus] = useState('Ready');
48
+ const [showVectors, setShowVectors] = useState(false);
49
+
50
+ const [params, setParams] = useState({
51
+ steerStrength: 3.0,
52
+ lookahead: 70,
53
+ noise: 0
54
+ });
55
+
56
+ const canvasRef = useRef(null);
57
+ const [agentPos, setAgentPos] = useState({ x: 50, y: 50 });
58
+ const [goalPos, setGoalPos] = useState({ x: 350, y: 250 });
59
+ const [trajectory, setTrajectory] = useState([]);
60
+ const [obstacles, setObstacles] = useState([]);
61
+
62
+ useEffect(() => {
63
+ if (plannerType === 'cem') {
64
+ setParams(p => ({ ...p, steerStrength: 2.0, noise: 2, lookahead: 60 }));
65
+ } else {
66
+ if (modelType === 'adversarial') {
67
+ setParams(p => ({ ...p, steerStrength: 3.5, noise: 0, lookahead: 80 }));
68
+ } else if (modelType === 'online') {
69
+ setParams(p => ({ ...p, steerStrength: 2.5, noise: 1.0, lookahead: 70 }));
70
+ } else {
71
+ setParams(p => ({ ...p, steerStrength: 0.5, noise: 5, lookahead: 50 }));
72
+ }
73
+ }
74
+ }, [modelType, plannerType]);
75
+
76
+ const performanceData = [
77
+ { name: 'PushT', GD: 56, CEM: 54, Online_GD: 34, Adversarial_GD: 56 },
78
+ { name: 'PointMaze', GD: 12, CEM: 24, Online_GD: 20, Adversarial_GD: 32 },
79
+ { name: 'Wall', GD: 2, CEM: 10, Online_GD: 16, Adversarial_GD: 32 },
80
+ ];
81
+
82
+ const lossLandscapeData = Array.from({ length: 50 }, (_, i) => {
83
+ const x = i / 5;
84
+ const dinoLoss = Math.sin(x * 3) * 0.5 + Math.random() * 1.5 + (x - 5) ** 2;
85
+ const onlineLoss = (x - 5) ** 2 + Math.random() * 0.5 + 1;
86
+ const advLoss = (x - 5) ** 2 + 0.5;
87
+ return { x, DINO: dinoLoss, Online: onlineLoss, Adversarial: advLoss };
88
+ });
89
+
90
+ useEffect(() => {
91
+ let newObstacles = [];
92
+ if (mapType === 'simple') {
93
+ newObstacles = [{ x: 200, y: 100, w: 20, h: 100 }];
94
+ } else if (mapType === 'wall') {
95
+ newObstacles = [{ x: 180, y: 50, w: 40, h: 200 }];
96
+ } else if (mapType === 'maze') {
97
+ newObstacles = [
98
+ { x: 120, y: 0, w: 20, h: 220 },
99
+ { x: 240, y: 80, w: 20, h: 220 },
100
+ { x: 320, y: 0, w: 20, h: 100 }
101
+ ];
102
+ }
103
+ setObstacles(newObstacles);
104
+ resetSim();
105
+ }, [mapType]);
106
+
107
+ useEffect(() => {
108
+ if (!isRunning) return;
109
+
110
+ const interval = setInterval(() => {
111
+ setAgentPos((prev) => {
112
+ const dx = goalPos.x - prev.x;
113
+ const dy = goalPos.y - prev.y;
114
+ const distToGoal = Math.sqrt(dx * dx + dy * dy);
115
+
116
+ if (distToGoal < 10) {
117
+ setIsRunning(false);
118
+ setStatus('Success');
119
+ return prev;
120
+ }
121
+
122
+ let vx = dx / (distToGoal || 1);
123
+ let vy = dy / (distToGoal || 1);
124
+
125
+ const avoidanceRadius = params.lookahead;
126
+ obstacles.forEach(obs => {
127
+ const closestX = Math.max(obs.x, Math.min(prev.x, obs.x + obs.w));
128
+ const closestY = Math.max(obs.y, Math.min(prev.y, obs.y + obs.h));
129
+ const distX = prev.x - closestX;
130
+ const distY = prev.y - closestY;
131
+ const dist = Math.sqrt(distX * distX + distY * distY);
132
+
133
+ if (dist < avoidanceRadius) {
134
+ let repX = distX / (dist || 1);
135
+ let repY = distY / (dist || 1);
136
+ const repulsionStrength = (avoidanceRadius - dist) / avoidanceRadius;
137
+ const steerFactor = params.steerStrength;
138
+ vx += repX * repulsionStrength * steerFactor;
139
+ vy += repY * repulsionStrength * steerFactor;
140
+ }
141
+ });
142
+
143
+ const margin = 20;
144
+ if (prev.x < margin) vx += (margin - prev.x) * 0.5;
145
+ if (prev.x > 400 - margin) vx -= (prev.x - (400 - margin)) * 0.5;
146
+ if (prev.y < margin) vy += (margin - prev.y) * 0.5;
147
+ if (prev.y > 300 - margin) vy -= (prev.y - (300 - margin)) * 0.5;
148
+
149
+ const speed = 5;
150
+ const finalMag = Math.sqrt(vx * vx + vy * vy);
151
+ vx = (vx / finalMag) * speed;
152
+ vy = (vy / finalMag) * speed;
153
+
154
+ if (params.noise > 0) {
155
+ vx += (Math.random() - 0.5) * params.noise;
156
+ vy += (Math.random() - 0.5) * params.noise;
157
+ }
158
+
159
+ const nextX = Math.max(5, Math.min(395, prev.x + vx));
160
+ const nextY = Math.max(5, Math.min(295, prev.y + vy));
161
+
162
+ const hitObstacle = obstacles.some(obs =>
163
+ nextX > obs.x - 5 && nextX < obs.x + obs.w + 5 &&
164
+ nextY > obs.y - 5 && nextY < obs.y + obs.h + 5
165
+ );
166
+
167
+ if (hitObstacle) {
168
+ setIsRunning(false);
169
+ setStatus('Crashed');
170
+ return prev;
171
+ }
172
+
173
+ setTrajectory(t => [...t, { x: nextX, y: nextY }]);
174
+ return { x: nextX, y: nextY };
175
+ });
176
+
177
+ }, 40);
178
+
179
+ return () => clearInterval(interval);
180
+ }, [isRunning, goalPos, obstacles, params]);
181
+
182
+ useEffect(() => {
183
+ const canvas = canvasRef.current;
184
+ if (!canvas) return;
185
+ const ctx = canvas.getContext('2d');
186
+
187
+ ctx.clearRect(0, 0, 400, 300);
188
+ ctx.fillStyle = '#0f172a';
189
+ ctx.fillRect(0, 0, 400, 300);
190
+
191
+ ctx.strokeStyle = '#1e293b';
192
+ ctx.lineWidth = 1;
193
+ for(let i=0; i<400; i+=40) { ctx.beginPath(); ctx.moveTo(i,0); ctx.lineTo(i,300); ctx.stroke(); }
194
+ for(let i=0; i<300; i+=40) { ctx.beginPath(); ctx.moveTo(0,i); ctx.lineTo(400,i); ctx.stroke(); }
195
+
196
+ if (showVectors) {
197
+ const gridSize = 25;
198
+ const arrowLen = 10;
199
+
200
+ for(let x = 10; x < 400; x += gridSize) {
201
+ for(let y = 10; y < 300; y += gridSize) {
202
+ const dx = goalPos.x - x;
203
+ const dy = goalPos.y - y;
204
+ const distToGoal = Math.sqrt(dx * dx + dy * dy);
205
+ let vx = dx / (distToGoal || 1);
206
+ let vy = dy / (distToGoal || 1);
207
+
208
+ let inObstacle = false;
209
+ obstacles.forEach(obs => {
210
+ if (x > obs.x && x < obs.x+obs.w && y > obs.y && y < obs.y+obs.h) inObstacle = true;
211
+
212
+ const closestX = Math.max(obs.x, Math.min(x, obs.x + obs.w));
213
+ const closestY = Math.max(obs.y, Math.min(y, obs.y + obs.h));
214
+ const distX = x - closestX;
215
+ const distY = y - closestY;
216
+ const dist = Math.sqrt(distX * distX + distY * distY);
217
+
218
+ if (dist < params.lookahead) {
219
+ let repX = distX / (dist || 1);
220
+ let repY = distY / (dist || 1);
221
+ const repulsionStrength = (params.lookahead - dist) / params.lookahead;
222
+ vx += repX * repulsionStrength * params.steerStrength;
223
+ vy += repY * repulsionStrength * params.steerStrength;
224
+ }
225
+ });
226
+
227
+ if (!inObstacle) {
228
+ if (params.noise > 0) {
229
+ vx += (Math.random() - 0.5) * params.noise * 0.8;
230
+ vy += (Math.random() - 0.5) * params.noise * 0.8;
231
+ }
232
+
233
+ const mag = Math.sqrt(vx*vx + vy*vy);
234
+ vx = (vx/mag) * arrowLen;
235
+ vy = (vy/mag) * arrowLen;
236
+
237
+ ctx.beginPath();
238
+ ctx.moveTo(x, y);
239
+ ctx.lineTo(x + vx, y + vy);
240
+
241
+ let color = 'rgba(217, 70, 239, 0.3)';
242
+ if (modelType === 'adversarial') color = 'rgba(34, 211, 238, 0.4)';
243
+ if (modelType === 'online') color = 'rgba(59, 130, 246, 0.4)';
244
+
245
+ ctx.strokeStyle = color;
246
+ ctx.lineWidth = 1;
247
+ ctx.stroke();
248
+
249
+ ctx.beginPath();
250
+ const headLen = 3;
251
+ const angle = Math.atan2(vy, vx);
252
+ ctx.moveTo(x + vx, y + vy);
253
+ ctx.lineTo(x + vx - headLen * Math.cos(angle - Math.PI / 6), y + vy - headLen * Math.sin(angle - Math.PI / 6));
254
+ ctx.lineTo(x + vx - headLen * Math.cos(angle + Math.PI / 6), y + vy - headLen * Math.sin(angle + Math.PI / 6));
255
+ ctx.fillStyle = color;
256
+ ctx.fill();
257
+ }
258
+ }
259
+ }
260
+ }
261
+
262
+ if (isRunning || status === 'Ready') {
263
+ ctx.beginPath();
264
+ ctx.arc(agentPos.x, agentPos.y, params.lookahead, 0, Math.PI * 2);
265
+ let color = 'rgba(217, 70, 239, 0.1)';
266
+ if (modelType === 'adversarial') color = 'rgba(34, 211, 238, 0.1)';
267
+ if (modelType === 'online') color = 'rgba(59, 130, 246, 0.1)';
268
+ ctx.fillStyle = color;
269
+ ctx.fill();
270
+ ctx.strokeStyle = color.replace('0.1', '0.3');
271
+ ctx.lineWidth = 1;
272
+ ctx.setLineDash([5, 5]);
273
+ ctx.stroke();
274
+ ctx.setLineDash([]);
275
+ }
276
+
277
+ obstacles.forEach(obs => {
278
+ ctx.fillStyle = '#1e293b';
279
+ ctx.fillRect(obs.x, obs.y, obs.w, obs.h);
280
+ ctx.shadowBlur = 10;
281
+ ctx.shadowColor = '#94a3b8';
282
+ ctx.strokeStyle = '#64748b';
283
+ ctx.lineWidth = 2;
284
+ ctx.strokeRect(obs.x, obs.y, obs.w, obs.h);
285
+ ctx.shadowBlur = 0;
286
+ });
287
+
288
+ ctx.beginPath();
289
+ let trajColor = '#d946ef';
290
+ if (modelType === 'adversarial') trajColor = '#22d3ee';
291
+ if (modelType === 'online') trajColor = '#3b82f6';
292
+
293
+ ctx.strokeStyle = trajColor;
294
+ ctx.lineWidth = 3;
295
+ ctx.lineCap = 'round';
296
+ ctx.lineJoin = 'round';
297
+ ctx.shadowBlur = 10;
298
+ ctx.shadowColor = trajColor;
299
+ trajectory.forEach((p, i) => {
300
+ if (i === 0) ctx.moveTo(p.x, p.y);
301
+ else ctx.lineTo(p.x, p.y);
302
+ });
303
+ ctx.stroke();
304
+ ctx.shadowBlur = 0;
305
+
306
+ ctx.beginPath();
307
+ ctx.arc(goalPos.x, goalPos.y, 8, 0, Math.PI * 2);
308
+ ctx.fillStyle = '#f472b6';
309
+ ctx.shadowBlur = 15;
310
+ ctx.shadowColor = '#f472b6';
311
+ ctx.fill();
312
+ ctx.shadowBlur = 0;
313
+ ctx.fillStyle = '#000';
314
+ ctx.font = 'bold 10px monospace';
315
+ ctx.fillText("G", goalPos.x - 4, goalPos.y + 3);
316
+
317
+ ctx.beginPath();
318
+ ctx.arc(agentPos.x, agentPos.y, 6, 0, Math.PI * 2);
319
+ ctx.fillStyle = '#fff';
320
+ ctx.shadowBlur = 10;
321
+ ctx.shadowColor = '#fff';
322
+ ctx.fill();
323
+ ctx.shadowBlur = 0;
324
+
325
+ }, [agentPos, goalPos, obstacles, trajectory, modelType, params.lookahead, isRunning, status, showVectors, params.steerStrength, params.noise]);
326
+
327
+ const resetSim = () => {
328
+ setAgentPos({ x: 50, y: 50 });
329
+ setTrajectory([]);
330
+ setIsRunning(false);
331
+ setStatus('Ready');
332
+ };
333
+
334
+ return (
335
+ <div className="flex flex-col h-screen bg-slate-950 text-cyan-50 font-mono">
336
+ {/* Header */}
337
+ <header className="bg-slate-900/80 backdrop-blur-md border-b border-slate-800 p-4 flex items-center justify-between z-10">
338
+ <div className="flex items-center space-x-3">
339
+ <div className="bg-cyan-500/10 border border-cyan-500/50 text-cyan-400 p-2 rounded shadow-[0_0_15px_rgba(6,182,212,0.3)]">
340
+ <Zap />
341
+ </div>
342
+ <div>
343
+ <h1 className="text-xl font-bold tracking-tight text-cyan-100 uppercase">Robust_World_Planner_v2.0</h1>
344
+ <p className="text-xs text-slate-400 font-medium">System: Online // Paper: "Closing the Train-Test Gap"</p>
345
+ </div>
346
+ </div>
347
+ <div className="flex items-center space-x-4">
348
+ {status === 'Crashed' && <span className="text-red-400 font-bold flex items-center bg-red-900/20 px-3 py-1 rounded border border-red-500/30"><AlertTriangle /><span className="ml-2">SYSTEM_FAILURE</span></span>}
349
+ {status === 'Success' && <span className="text-emerald-400 font-bold flex items-center bg-emerald-900/20 px-3 py-1 rounded border border-emerald-500/30"><ZapSmall /><span className="ml-2">TARGET_ACQUIRED</span></span>}
350
+ <a href="https://github.com/qw3rtman/robust-world-model-planning" target="_blank" rel="noreferrer" className="text-xs text-slate-500 hover:text-cyan-400 transition-colors uppercase tracking-widest">
351
+ Source_Code
352
+ </a>
353
+ </div>
354
+ </header>
355
+
356
+ {/* Main Content */}
357
+ <div className="flex flex-1 overflow-hidden">
358
+
359
+ {/* Sidebar Controls */}
360
+ <aside className="w-80 bg-slate-900 border-r border-slate-800 p-5 flex flex-col overflow-y-auto">
361
+ <div className="mb-6">
362
+ <h3 className="text-xs font-bold text-cyan-700 mb-3 uppercase tracking-widest flex items-center">
363
+ <Terminal /> <span className="ml-2">System_Config</span>
364
+ </h3>
365
+
366
+ <div className="space-y-4">
367
+ <div>
368
+ <label className="text-xs font-semibold text-slate-400 mb-2 block uppercase">Core Model</label>
369
+ <div className="grid grid-cols-1 gap-2">
370
+ <button
371
+ onClick={() => { setModelType('dino'); resetSim(); }}
372
+ className={`p-3 text-sm font-medium rounded border flex items-center transition-all ${modelType === 'dino' ? 'bg-fuchsia-900/20 border-fuchsia-500 text-fuchsia-400 shadow-[0_0_10px_rgba(217,70,239,0.2)]' : 'bg-slate-800 border-slate-700 text-slate-500 hover:bg-slate-800/80'}`}
373
+ >
374
+ <AlertTriangle /> <span className="ml-3">STANDARD_DINO</span>
375
+ </button>
376
+ <button
377
+ onClick={() => { setModelType('online'); resetSim(); }}
378
+ className={`p-3 text-sm font-medium rounded border flex items-center transition-all ${modelType === 'online' ? 'bg-blue-900/20 border-blue-500 text-blue-400 shadow-[0_0_10px_rgba(59,130,246,0.2)]' : 'bg-slate-800 border-slate-700 text-slate-500 hover:bg-slate-800/80'}`}
379
+ >
380
+ <Database /> <span className="ml-3">ONLINE_WM</span>
381
+ </button>
382
+ <button
383
+ onClick={() => { setModelType('adversarial'); resetSim(); }}
384
+ className={`p-3 text-sm font-medium rounded border flex items-center transition-all ${modelType === 'adversarial' ? 'bg-cyan-900/20 border-cyan-500 text-cyan-400 shadow-[0_0_10px_rgba(34,211,238,0.2)]' : 'bg-slate-800 border-slate-700 text-slate-500 hover:bg-slate-800/80'}`}
385
+ >
386
+ <ZapSmall /> <span className="ml-3">ADVERSARIAL_WM</span>
387
+ </button>
388
+ </div>
389
+ </div>
390
+
391
+ <div>
392
+ <label className="text-xs font-semibold text-slate-400 mb-2 block uppercase">Environment</label>
393
+ <select
394
+ className="w-full p-2.5 bg-slate-800 border border-slate-700 rounded text-sm text-cyan-50 focus:border-cyan-500 outline-none transition-all uppercase"
395
+ value={mapType}
396
+ onChange={(e) => setMapType(e.target.value)}
397
+ >
398
+ <option value="simple">Sector_A (Simple)</option>
399
+ <option value="wall">Sector_B (Wall)</option>
400
+ <option value="maze">Sector_C (Complex)</option>
401
+ </select>
402
+ </div>
403
+
404
+ <div>
405
+ <label className="text-xs font-semibold text-slate-400 mb-2 block uppercase">Planner Logic</label>
406
+ <select
407
+ className="w-full p-2.5 bg-slate-800 border border-slate-700 rounded text-sm text-cyan-50 focus:border-cyan-500 outline-none transition-all uppercase"
408
+ value={plannerType}
409
+ onChange={(e) => { setPlannerType(e.target.value); resetSim(); }}
410
+ >
411
+ <option value="gd">Gradient_Descent (Fast)</option>
412
+ <option value="cem">Cross_Entropy (Slow)</option>
413
+ </select>
414
+ </div>
415
+ </div>
416
+ </div>
417
+
418
+ <div className="mb-6 bg-slate-900/50 p-4 rounded border border-slate-800 relative overflow-hidden">
419
+ <div className="absolute top-0 left-0 w-1 h-full bg-cyan-500/50"></div>
420
+ <h3 className="text-xs font-bold text-slate-500 mb-3 uppercase tracking-widest flex items-center">
421
+ <Sliders /> <span className="ml-2">Telemetry</span>
422
+ </h3>
423
+ <div className="space-y-4">
424
+ <div>
425
+ <div className="flex justify-between mb-1">
426
+ <label className="text-xs font-semibold text-slate-400">STEERING_GAIN</label>
427
+ <span className="text-xs font-mono text-cyan-400">{params.steerStrength.toFixed(1)}</span>
428
+ </div>
429
+ <input
430
+ type="range" min="0" max="6" step="0.1"
431
+ value={params.steerStrength}
432
+ onChange={(e) => setParams({...params, steerStrength: parseFloat(e.target.value)})}
433
+ className="w-full h-1 bg-slate-700 rounded appearance-none cursor-pointer accent-cyan-500"
434
+ />
435
+ </div>
436
+ <div>
437
+ <div className="flex justify-between mb-1">
438
+ <label className="text-xs font-semibold text-slate-400">SCAN_HORIZON</label>
439
+ <span className="text-xs font-mono text-cyan-400">{params.lookahead}PX</span>
440
+ </div>
441
+ <input
442
+ type="range" min="10" max="150" step="5"
443
+ value={params.lookahead}
444
+ onChange={(e) => setParams({...params, lookahead: parseInt(e.target.value)})}
445
+ className="w-full h-1 bg-slate-700 rounded appearance-none cursor-pointer accent-cyan-500"
446
+ />
447
+ </div>
448
+ </div>
449
+ </div>
450
+
451
+ <div className="mb-6">
452
+ <div className="flex space-x-2">
453
+ <button
454
+ onClick={() => { setIsRunning(!isRunning); if(status !== 'Planning' && !isRunning) setStatus('Planning'); }}
455
+ disabled={status === 'Crashed' || status === 'Success'}
456
+ className={`flex-1 flex items-center justify-center p-3 rounded text-slate-900 font-bold uppercase tracking-wide transition-all ${
457
+ status === 'Crashed' || status === 'Success' ? 'bg-slate-700 cursor-not-allowed opacity-50' :
458
+ isRunning ? 'bg-fuchsia-500 hover:bg-fuchsia-400 shadow-[0_0_15px_rgba(217,70,239,0.5)]' : 'bg-cyan-500 hover:bg-cyan-400 shadow-[0_0_15px_rgba(6,182,212,0.5)]'}`}
459
+ >
460
+ {isRunning ? <><Pause /> <span className="ml-2">Halt</span></> : <><Play /> <span className="ml-2">Execute</span></>}
461
+ </button>
462
+ <button
463
+ onClick={resetSim}
464
+ className="p-3 border border-slate-700 rounded text-slate-400 hover:bg-slate-800 hover:text-cyan-400 transition-colors"
465
+ >
466
+ <RefreshCw />
467
+ </button>
468
+ </div>
469
+ </div>
470
+ </aside>
471
+
472
+ {/* Visualization Area */}
473
+ <main className="flex-1 flex flex-col bg-slate-950 overflow-y-auto">
474
+ {/* Tabs */}
475
+ <div className="bg-slate-900 border-b border-slate-800 px-8 pt-4 flex space-x-8 sticky top-0 z-10">
476
+ <button
477
+ onClick={() => setActiveTab('simulation')}
478
+ className={`pb-4 text-xs font-bold border-b-2 transition-colors uppercase tracking-widest ${activeTab === 'simulation' ? 'border-cyan-500 text-cyan-400' : 'border-transparent text-slate-500 hover:text-slate-300'}`}
479
+ >
480
+ <span className="flex items-center"><Map /> <span className="ml-2">Live_Feed</span></span>
481
+ </button>
482
+ <button
483
+ onClick={() => setActiveTab('landscape')}
484
+ className={`pb-4 text-xs font-bold border-b-2 transition-colors uppercase tracking-widest ${activeTab === 'landscape' ? 'border-cyan-500 text-cyan-400' : 'border-transparent text-slate-500 hover:text-slate-300'}`}
485
+ >
486
+ <span className="flex items-center"><Layers /> <span className="ml-2">Data_Surface</span></span>
487
+ </button>
488
+ <button
489
+ onClick={() => setActiveTab('benchmarks')}
490
+ className={`pb-4 text-xs font-bold border-b-2 transition-colors uppercase tracking-widest ${activeTab === 'benchmarks' ? 'border-cyan-500 text-cyan-400' : 'border-transparent text-slate-500 hover:text-slate-300'}`}
491
+ >
492
+ <span className="flex items-center"><BarChart2 /> <span className="ml-2">Analytics</span></span>
493
+ </button>
494
+ </div>
495
+
496
+ <div className="p-8 max-w-5xl mx-auto w-full">
497
+ {activeTab === 'simulation' && (
498
+ <div className="flex flex-col items-center animate-fade-in">
499
+ <div className="bg-slate-900 p-1 rounded-lg border border-slate-800 shadow-[0_0_30px_rgba(0,0,0,0.5)] mb-6 relative group">
500
+ <div className="absolute inset-0 bg-cyan-500/5 rounded-lg pointer-events-none"></div>
501
+ <canvas
502
+ ref={canvasRef}
503
+ width={400}
504
+ height={300}
505
+ className="rounded cursor-crosshair block"
506
+ onClick={(e) => {
507
+ const rect = canvasRef.current.getBoundingClientRect();
508
+ setGoalPos({ x: e.clientX - rect.left, y: e.clientY - rect.top });
509
+ resetSim();
510
+ }}
511
+ />
512
+
513
+ <div className="absolute top-4 left-4 flex flex-col space-y-2">
514
+ <button
515
+ onClick={() => setShowVectors(!showVectors)}
516
+ className={`px-3 py-1.5 rounded border text-[10px] font-bold uppercase tracking-wider backdrop-blur-md transition-all ${showVectors ? 'bg-cyan-500/20 border-cyan-500 text-cyan-400' : 'bg-slate-900/80 border-slate-700 text-slate-400 hover:bg-slate-800'}`}
517
+ >
518
+ <span className="flex items-center"><Grid /> <span className="ml-2">Matrix_View {showVectors ? '[ON]' : '[OFF]'}</span></span>
519
+ </button>
520
+ </div>
521
+
522
+ <div className="absolute top-4 right-4 bg-slate-950/80 backdrop-blur px-3 py-1.5 rounded border border-slate-700 text-[10px] font-medium text-cyan-400 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none uppercase">
523
+ Target_Designation_Mode
524
+ </div>
525
+ </div>
526
+
527
+ <div className="flex space-x-6 text-xs font-medium text-slate-400 bg-slate-900 px-6 py-3 rounded border border-slate-800 uppercase tracking-wide">
528
+ <div className="flex items-center"><span className="w-2 h-2 bg-white rounded-full mr-2 shadow-[0_0_8px_white]"></span> Agent</div>
529
+ <div className="flex items-center"><span className="w-2 h-2 bg-pink-500 rounded-full mr-2 shadow-[0_0_8px_magenta]"></span> Target</div>
530
+ <div className="flex items-center">
531
+ <span className={`w-2 h-2 rounded-full mr-2 shadow-[0_0_8px_currentColor] ${modelType === 'adversarial' ? 'text-cyan-400 bg-cyan-400' : modelType === 'online' ? 'text-blue-400 bg-blue-400' : 'text-fuchsia-400 bg-fuchsia-400'}`}></span> Path
532
+ </div>
533
+ <div className="flex items-center"><span className="w-2 h-2 border border-slate-500 border-dashed rounded-full mr-2"></span> Scanner</div>
534
+ </div>
535
+ </div>
536
+ )}
537
+
538
+ {activeTab === 'landscape' && (
539
+ <div className="bg-slate-900 p-6 rounded border border-slate-800 animate-fade-in">
540
+ <div className="mb-6">
541
+ <h3 className="text-lg font-bold text-cyan-100 uppercase tracking-wide">Loss Surface Topology</h3>
542
+ <p className="text-xs text-slate-500 mt-1">
543
+ Adversarial training (Cyan) smooths the optimization landscape. Standard DINO (Fuchsia) is volatile and non-convex.
544
+ </p>
545
+ </div>
546
+ <div className="h-72 w-full">
547
+ <ResponsiveContainer width="100%" height="100%">
548
+ <LineChart data={lossLandscapeData} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>
549
+ <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#1e293b" />
550
+ <XAxis dataKey="x" hide />
551
+ <YAxis stroke="#64748b" fontSize={10} tickLine={false} axisLine={false} />
552
+ <Tooltip
553
+ contentStyle={{ backgroundColor: '#0f172a', borderColor: '#334155', borderRadius: '4px', color: '#f1f5f9' }}
554
+ itemStyle={{ fontSize: '12px' }}
555
+ />
556
+ <Legend verticalAlign="top" height={36}/>
557
+ <Line type="monotone" dataKey="DINO" stroke="#d946ef" strokeWidth={2} dot={false} name="Standard (DINO)" activeDot={{ r: 6 }} />
558
+ <Line type="monotone" dataKey="Online" stroke="#3b82f6" strokeWidth={2} dot={false} name="Online WM" activeDot={{ r: 6 }} />
559
+ <Line type="monotone" dataKey="Adversarial" stroke="#22d3ee" strokeWidth={3} dot={false} name="Adversarial WM" activeDot={{ r: 8, stroke: '#fff', strokeWidth: 2 }} />
560
+ </LineChart>
561
+ </ResponsiveContainer>
562
+ </div>
563
+ </div>
564
+ )}
565
+
566
+ {activeTab === 'benchmarks' && (
567
+ <div className="bg-slate-900 p-6 rounded border border-slate-800 animate-fade-in">
568
+ <div className="mb-6">
569
+ <h3 className="text-lg font-bold text-cyan-100 uppercase tracking-wide">Performance Metrics</h3>
570
+ <p className="text-xs text-slate-500 mt-1">
571
+ Success rates (%) across control environments. Adversarial optimization provides superior stability.
572
+ </p>
573
+ </div>
574
+ <div className="h-72 w-full">
575
+ <ResponsiveContainer width="100%" height="100%">
576
+ <BarChart data={performanceData} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
577
+ <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#1e293b" />
578
+ <XAxis dataKey="name" stroke="#64748b" fontSize={10} tickLine={false} axisLine={false} />
579
+ <YAxis stroke="#64748b" fontSize={10} tickLine={false} axisLine={false} />
580
+ <Tooltip
581
+ cursor={{fill: '#1e293b'}}
582
+ contentStyle={{ backgroundColor: '#0f172a', borderColor: '#334155', borderRadius: '4px', color: '#f1f5f9' }}
583
+ />
584
+ <Legend verticalAlign="top" height={36} iconType="circle"/>
585
+ <Bar dataKey="GD" fill="#d946ef" name="Standard (DINO)" radius={[2, 2, 0, 0]} />
586
+ <Bar dataKey="Online_GD" fill="#3b82f6" name="Online WM" radius={[2, 2, 0, 0]} />
587
+ <Bar dataKey="Adversarial_GD" fill="#22d3ee" name="Adversarial WM" radius={[2, 2, 0, 0]} />
588
+ </BarChart>
589
+ </ResponsiveContainer>
590
+ </div>
591
+ </div>
592
+ )}
593
+ </div>
594
+ </main>
595
+ </div>
596
+ </div>
597
+ );
598
+ };
599
+
600
+ ReactDOM.createRoot(document.getElementById('root')).render(<WorldModelDashboard />);
601
+ </script>
602
+ </body>
603
+ </html>