feat: add initial interactive compiler and ds-stream demos

This commit is contained in:
enzotar 2026-03-11 18:25:18 -07:00
parent a65094c0d2
commit fbbdeb0bc4
2 changed files with 1405 additions and 0 deletions

781
compiler/demo/index.html Normal file
View file

@ -0,0 +1,781 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DreamStack Compiler v1.0 — Interactive Demo</title>
<meta name="description" content="Interactive demo of the DreamStack compiler pipeline: Parse → Analyze → TypeCheck → Codegen → Layout → Diagnostics → Build">
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#0a0a0f;--surface:#12121a;--surface2:#1a1a28;--surface3:#242438;
--border:#2a2a40;--border-glow:#6366f122;
--text:#e2e8f0;--text-muted:#94a3b8;--text-dim:#64748b;
--accent:#818cf8;--accent2:#a78bfa;--accent3:#c084fc;
--green:#34d399;--yellow:#fbbf24;--red:#f87171;--cyan:#22d3ee;--pink:#f472b6;
--gradient:linear-gradient(135deg,#818cf8,#a78bfa,#c084fc);
--font:'Inter',system-ui,sans-serif;--mono:'JetBrains Mono',monospace;
}
html{font-size:15px}
body{background:var(--bg);color:var(--text);font-family:var(--font);min-height:100vh;overflow-x:hidden}
::selection{background:#818cf855;color:#fff}
::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:#333;border-radius:3px}
/* ── Hero ── */
.hero{text-align:center;padding:3rem 2rem 2rem;position:relative}
.hero::before{content:'';position:absolute;inset:0;background:radial-gradient(ellipse 80% 60% at 50% 0%,#818cf815,transparent);pointer-events:none}
.hero h1{font-size:3.2rem;font-weight:900;background:var(--gradient);-webkit-background-clip:text;-webkit-text-fill-color:transparent;letter-spacing:-0.03em;margin-bottom:.4rem}
.hero .version{display:inline-block;background:var(--gradient);color:#fff;font-size:.75rem;font-weight:700;padding:.2rem .7rem;border-radius:999px;margin-bottom:.8rem;letter-spacing:.04em}
.hero p{color:var(--text-muted);font-size:1.05rem;max-width:600px;margin:0 auto}
/* ── Stats Bar ── */
.stats{display:flex;justify-content:center;gap:2rem;padding:1rem 2rem;flex-wrap:wrap}
.stat{text-align:center}
.stat-val{font-size:2rem;font-weight:800;background:var(--gradient);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
.stat-label{font-size:.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:.08em}
/* ── Layout ── */
.app{display:grid;grid-template-columns:1fr 1fr;gap:1px;padding:0 1.5rem 2rem;max-width:1400px;margin:0 auto}
@media(max-width:900px){.app{grid-template-columns:1fr}}
/* ── Panels ── */
.panel{background:var(--surface);border:1px solid var(--border);border-radius:12px;overflow:hidden;display:flex;flex-direction:column}
.panel-header{display:flex;align-items:center;gap:.5rem;padding:.6rem 1rem;background:var(--surface2);border-bottom:1px solid var(--border);font-size:.75rem;font-weight:600;color:var(--text-muted)}
.panel-header .dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.panel-body{padding:0;flex:1;overflow:auto;position:relative}
/* ── Editor ── */
#editor{width:100%;min-height:420px;background:transparent;border:none;color:var(--text);font-family:var(--mono);font-size:.82rem;line-height:1.7;padding:1rem;resize:none;outline:none;tab-size:2}
#editor::placeholder{color:var(--text-dim)}
/* ── Output ── */
.output{font-family:var(--mono);font-size:.78rem;line-height:1.6;padding:1rem;white-space:pre-wrap;min-height:420px}
/* ── Pipeline ── */
.pipeline{display:flex;gap:2px;padding:1rem 1.5rem;max-width:1400px;margin:0 auto;flex-wrap:wrap}
.pipe-stage{flex:1;min-width:120px;background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:.8rem;text-align:center;cursor:pointer;transition:all .25s;position:relative;overflow:hidden}
.pipe-stage::before{content:'';position:absolute;inset:0;background:var(--gradient);opacity:0;transition:opacity .3s}
.pipe-stage:hover::before,.pipe-stage.active::before{opacity:.08}
.pipe-stage.active{border-color:var(--accent);box-shadow:0 0 20px #818cf822}
.pipe-stage .icon{font-size:1.4rem;margin-bottom:.3rem}
.pipe-stage .name{font-size:.7rem;font-weight:700;color:var(--text);text-transform:uppercase;letter-spacing:.06em;position:relative}
.pipe-stage .time{font-size:.6rem;color:var(--text-dim);margin-top:.2rem;position:relative}
.pipe-stage .status{position:absolute;top:6px;right:6px;width:6px;height:6px;border-radius:50%;background:var(--green)}
.pipe-arrow{display:flex;align-items:center;color:var(--text-dim);font-size:.8rem;padding:0 2px}
/* ── Diagnostic ── */
.diag-line{padding:2px 0;display:flex;gap:.5rem;align-items:flex-start}
.diag-sev{font-size:.65rem;font-weight:700;padding:1px 5px;border-radius:3px;flex-shrink:0;margin-top:2px}
.diag-error{background:#f8717122;color:var(--red)}
.diag-warn{background:#fbbf2422;color:var(--yellow)}
.diag-info{background:#818cf822;color:var(--accent)}
.diag-hint{background:#34d39922;color:var(--green)}
.diag-msg{color:var(--text-muted)}
/* ── AST Tree ── */
.ast-node{padding-left:1rem;border-left:1px solid var(--border)}
.ast-node-name{color:var(--accent);cursor:pointer;padding:1px 0;display:inline-flex;align-items:center;gap:4px}
.ast-node-name:hover{color:var(--accent2)}
.ast-leaf{color:var(--green);padding:1px 0}
.ast-attr{color:var(--text-dim);font-style:italic}
/* ── Type annotations ── */
.type-tag{display:inline-block;padding:0 5px;border-radius:3px;margin:0 2px;font-size:.72rem}
.type-int{background:#34d39915;color:var(--green)}
.type-str{background:#fbbf2415;color:var(--yellow)}
.type-fn{background:#818cf815;color:var(--accent)}
.type-bool{background:#f4728615;color:var(--pink)}
.type-generic{background:#c084fc15;color:var(--accent3)}
.type-async{background:#22d3ee15;color:var(--cyan)}
/* ── Animations ── */
@keyframes fadeIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
@keyframes glow{0%,100%{box-shadow:0 0 5px #818cf822}50%{box-shadow:0 0 20px #818cf844}}
.fade-in{animation:fadeIn .3s ease}
.pulse{animation:pulse 1.5s infinite}
/* ── Bottom bar ── */
.bottom-bar{padding:.8rem 1.5rem;max-width:1400px;margin:0 auto;display:flex;gap:.8rem;flex-wrap:wrap}
.btn{font-family:var(--font);font-size:.75rem;font-weight:600;padding:.5rem 1.2rem;border:1px solid var(--border);border-radius:8px;background:var(--surface);color:var(--text);cursor:pointer;transition:all .2s;display:inline-flex;align-items:center;gap:.4rem}
.btn:hover{background:var(--surface2);border-color:var(--accent)}
.btn-primary{background:var(--gradient);border:none;color:#fff}
.btn-primary:hover{filter:brightness(1.15);transform:translateY(-1px)}
/* ── Footer ── */
footer{text-align:center;padding:2rem;color:var(--text-dim);font-size:.7rem}
footer a{color:var(--accent);text-decoration:none}
</style>
</head>
<body>
<div class="hero">
<div class="version">v1.0.0 STABLE</div>
<h1>DreamStack Compiler</h1>
<p>Interactive compiler explorer — write code and watch it flow through 7 compilation stages in real-time</p>
</div>
<div class="stats" id="stats">
<div class="stat"><div class="stat-val" id="stat-tests">511</div><div class="stat-label">Tests Passing</div></div>
<div class="stat"><div class="stat-val" id="stat-packages">7</div><div class="stat-label">Packages</div></div>
<div class="stat"><div class="stat-val" id="stat-features">0</div><div class="stat-label">Features</div></div>
<div class="stat"><div class="stat-val" id="stat-time">0ms</div><div class="stat-label">Compile Time</div></div>
</div>
<div class="pipeline" id="pipeline"></div>
<div class="app">
<div class="panel">
<div class="panel-header">
<div class="dot" style="background:var(--red)"></div>
<div class="dot" style="background:var(--yellow)"></div>
<div class="dot" style="background:var(--green)"></div>
<span style="margin-left:.3rem">editor.ds</span>
<span style="margin-left:auto;color:var(--text-dim)" id="cursor-pos">Ln 1, Col 1</span>
</div>
<div class="panel-body">
<textarea id="editor" spellcheck="false" placeholder="// Start typing DreamStack code..."></textarea>
</div>
</div>
<div class="panel">
<div class="panel-header">
<div class="dot" style="background:var(--accent)"></div>
<div class="dot" style="background:var(--accent2)"></div>
<div class="dot" style="background:var(--accent3)"></div>
<span style="margin-left:.3rem" id="output-title">AST Explorer</span>
<span style="margin-left:auto;font-size:.65rem" id="output-meta"></span>
</div>
<div class="panel-body">
<div class="output" id="output"></div>
</div>
</div>
</div>
<div class="bottom-bar">
<button class="btn btn-primary" onclick="runPipeline()">▶ Compile</button>
<button class="btn" onclick="loadExample('hello')">📦 Hello World</button>
<button class="btn" onclick="loadExample('async')">⚡ Async/Effects</button>
<button class="btn" onclick="loadExample('generics')">🧬 Generics</button>
<button class="btn" onclick="loadExample('layout')">🎨 Layout</button>
<button class="btn" onclick="loadExample('fullstack')">🚀 Full Stack</button>
<button class="btn" onclick="loadExample('types')">🔬 Type System</button>
</div>
<footer>
<p>DreamStack Compiler v1.0.0 — 7 packages · 511 tests · Built from first principles</p>
<p style="margin-top:.3rem">ds-parser · ds-analyzer · ds-codegen · ds-layout · ds-types · ds-diagnostic · ds-incremental</p>
</footer>
<script>
// ─── Pipeline Stages ───
const stages = [
{ id:'parser', icon:'📝', name:'Parse', pkg:'ds-parser', tests:94, features:['AST','Match','Import','Generics','Traits','Async','Effects','Pipeline','ErrorRecovery','Namespaces','Pragmas','Literals'] },
{ id:'types', icon:'🔬', name:'Types', pkg:'ds-types', tests:95, features:['Checker','Patterns','Generics','Traits','Async','Intersection','Branded','Inference','Unification','Subtyping','HKT','TypeClasses'] },
{ id:'analyzer', icon:'🔍', name:'Analyze', pkg:'ds-analyzer', tests:70, features:['Signals','Cycles','Memo','HotPaths','Purity','Coverage','CallGraph','DeadCode','BorrowCheck','Vectorize'] },
{ id:'codegen', icon:'⚙️', name:'Codegen', pkg:'ds-codegen', tests:80, features:['JSEmit','DCE','Inline','Minify','Async','Pipeline','Chunks','WASM','SSR','Hydration','CSSModules','SIMD'] },
{ id:'layout', icon:'🎨', name:'Layout', pkg:'ds-layout', tests:58, features:['Cassowary','Grid','Flex','Scroll','Sticky','Animation','Text','MediaQuery','Gradient','Filter','Clamp'] },
{ id:'diagnostic', icon:'🩺', name:'Diag', pkg:'ds-diagnostic', tests:57, features:['Errors','LSP','Batch','Pipeline','Tags','SARIF','CodeFrames','Budgets','Baselines','Trending','Formatters'] },
{ id:'incremental', icon:'🔄', name:'Build', pkg:'ds-incremental', tests:57, features:['Cache','Watch','Profiles','Workers','BuildGraph','Plugins','Hermetic','Signing','HealthCheck'] },
];
// Build pipeline UI
const pipeEl = document.getElementById('pipeline');
stages.forEach((s, i) => {
if (i > 0) pipeEl.insertAdjacentHTML('beforeend', '<div class="pipe-arrow"></div>');
pipeEl.insertAdjacentHTML('beforeend', `
<div class="pipe-stage" id="stage-${s.id}" onclick="showStage('${s.id}')">
<div class="status"></div>
<div class="icon">${s.icon}</div>
<div class="name">${s.name}</div>
<div class="time">${s.tests} tests</div>
</div>
`);
});
let activeStage = 'parser';
let lastResult = null;
// ─── Examples ───
const examples = {
hello: `// 🌟 Hello DreamStack!
component App {
signal count = 0
signal name = "World"
fn increment() {
count += 1
}
render {
<div class="app">
<h1>"Hello, {name}!"</h1>
<p>"Count: {count}"</p>
<button @click=increment>
"Click me"
</button>
</div>
}
}`,
async: `// ⚡ Async & Effect System
effect Logger {
log(msg: string)
warn(msg: string)
}
effect Http {
fetch(url: string) -> Response
}
async fn loadUser(id: int) -> Result<User, Error> {
let response = await Http.fetch("/api/users/{id}")
try {
let user = await response.json()
Logger.log("Loaded user: {user.name}")
Ok(user)
} catch e {
Logger.warn("Failed: {e.message}")
Err(e)
}
}
// Pipeline operator
let result = userId
|> loadUser
|> validate
|> transform
|> render`,
generics: `// 🧬 Generics & Trait System
trait Drawable {
fn draw(self) -> Canvas
fn bounds(self) -> Rect
}
trait Serializable<T> where T: Clone {
fn serialize(self) -> Vec<u8>
fn deserialize(data: Vec<u8>) -> T
}
struct Circle<T: Numeric> {
center: Point<T>
radius: T
}
impl Drawable for Circle<f64> {
fn draw(self) -> Canvas {
Canvas.arc(self.center, self.radius)
}
}
impl<T> Serializable<Circle<T>> for Circle<T>
where T: Numeric + Clone {
fn serialize(self) -> Vec<u8> {
encode(self.center, self.radius)
}
}`,
layout: `// 🎨 Layout & Styling
component Dashboard {
layout {
display: grid
grid_template: "header header" 60px
"sidebar main" 1fr
/ 280px 1fr
gap: 16px
padding: 24px
}
@media (max-width: 768px) {
layout {
grid_template: "header" 50px
"main" 1fr
/ 1fr
}
}
@keyframes fadeIn {
0% { opacity: 0; transform: translateY(20px) }
100% { opacity: 1; transform: translateY(0) }
}
style card {
background: gradient(135deg, #667eea, #764ba2)
border_radius: 16px
shadow: 0 8px 32px rgba(0,0,0,0.3)
backdrop_filter: blur(12px)
transition: transform 300ms ease
}
}`,
fullstack: `// 🚀 Full Stack Application
@deprecated("use v2")
#[inline]
async fn apiHandler(req: Request) -> Response {
/// Handle incoming API requests with auth
let token = req.headers.get("Authorization")
let user = await authenticate(token)
match req.method {
"GET" => {
let data = await db.query("SELECT * FROM items")
let items = data
|> filter(_.active)
|> map(serialize)
|> take(50)
Response.json(items)
}
"POST" => {
try {
let body = await req.json()
let item = await db.insert(body)
Response.created(item)
} catch e {
Response.error(400, e.message)
}
}
_ => Response.notFound()
}
}
// SSR + Hydration
component Page {
signal items: Vec<Item> = []
async fn onMount() {
items = await apiHandler(Request.get("/api"))
}
render {
<!--ds-hydrate:page-->
<main>
for item in items {
<Card data=item />
}
</main>
}
}`,
types: `// 🔬 Advanced Type System
type UserId = Branded<string, "UserId">
type Email = Branded<string, "Email">
type Result<T, E> = Ok(T) | Err(E)
type Option<T> = Some(T) | None
// Conditional types
type Flatten<T> = T extends Array<infer U> ? U : T
// Mapped types
type Readonly<T> = { [K in keyof T]: readonly T[K] }
type Partial<T> = { [K in keyof T]?: T[K] }
// Higher-kinded types
trait Functor<F<_>> {
fn map<A, B>(fa: F<A>, f: A -> B) -> F<B>
}
trait Monad<M<_>> extends Functor<M> {
fn pure<A>(a: A) -> M<A>
fn flatMap<A, B>(ma: M<A>, f: A -> M<B>) -> M<B>
}
// Type inference
let x = [1, 2, 3] // Vec<int>
let y = x |> map(_ * 2) // Vec<int>
let z = { name: "DS" } // { name: string }
let w = z satisfies Record // type-checked`
};
// ─── Simulated Compiler ───
function tokenize(code) {
const tokens = [];
const patterns = [
[/^\/\/[^\n]*/,'comment'],[/^\/\*[\s\S]*?\*\//,'comment'],
[/^"(?:[^"\\]|\\.)*"/,'string'],[/^`(?:[^`\\]|\\.)*`/,'template'],
[/^(?:fn|let|const|if|else|match|for|while|return|async|await|try|catch|component|signal|render|trait|impl|struct|enum|type|effect|handle|import|export|pub|priv|mod|where|in|yield|break|continue)\b/,'keyword'],
[/^(?:true|false|None|Ok|Err|Some|Self)\b/,'literal'],
[/^@\w+/,'decorator'],[/^#\[[^\]]+\]/,'pragma'],
[/^(?:->|=>|\|>|::|\.\.\.|&&|\|\||[+\-*\/%=<>!&|^~?:;,.{}()\[\]])/,'operator'],
[/^0[xX][0-9a-fA-F]+/,'number'],[/^0[bB][01]+/,'number'],[/^\d+\.?\d*(?:[eE][+-]?\d+)?(?:u\d+|i\d+|f\d+)?/,'number'],
[/^[a-zA-Z_]\w*/,'identifier'],[/^\s+/,'whitespace'],
];
let pos = 0;
while (pos < code.length) {
let matched = false;
for (const [re, type] of patterns) {
const m = code.slice(pos).match(re);
if (m) { tokens.push({ type, value: m[0], pos }); pos += m[0].length; matched = true; break; }
}
if (!matched) { tokens.push({ type:'unknown', value:code[pos], pos }); pos++; }
}
return tokens;
}
function parseAST(tokens) {
const nodes = [];
let i = 0;
const meaningful = tokens.filter(t => t.type !== 'whitespace');
while (i < meaningful.length) {
const t = meaningful[i];
if (t.type === 'keyword') {
const kind = t.value;
if (kind === 'component' || kind === 'struct' || kind === 'trait' || kind === 'effect') {
const name = meaningful[i+1]?.value || '?';
const children = [];
let depth = 0, j = i + 2;
if (meaningful[j]?.value === '{') { depth = 1; j++; }
while (j < meaningful.length && depth > 0) {
if (meaningful[j].value === '{') depth++;
if (meaningful[j].value === '}') depth--;
if (depth > 0 && meaningful[j].type === 'keyword') {
children.push({ type:'Member', name:meaningful[j].value + ' ' + (meaningful[j+1]?.value||''), children:[] });
}
j++;
}
nodes.push({ type:kind.charAt(0).toUpperCase()+kind.slice(1)+'Decl', name, children }); i = j;
} else if (kind === 'fn' || kind === 'async') {
const isAsync = kind === 'async';
const fnIdx = isAsync ? i+1 : i;
const name = meaningful[fnIdx+1]?.value || '?';
const params = [];
let j = fnIdx + 2;
if (meaningful[j]?.value === '(') {
j++;
while (j < meaningful.length && meaningful[j].value !== ')') {
if (meaningful[j].type === 'identifier') params.push(meaningful[j].value);
j++;
}
j++;
}
let retType = null;
if (meaningful[j]?.value === '->') { retType = meaningful[j+1]?.value; j += 2; }
nodes.push({ type: isAsync ? 'AsyncFnDecl' : 'FnDecl', name, children: params.map(p=>({type:'Param',name:p,children:[]})), retType });
while (j < meaningful.length && meaningful[j].value !== '}') j++;
i = j + 1;
} else if (kind === 'type') {
const name = meaningful[i+1]?.value || '?';
nodes.push({ type:'TypeAlias', name, children:[] });
while (i < meaningful.length && meaningful[i].value !== '\n' && meaningful[i].type !== 'keyword') i++;
} else if (kind === 'let' || kind === 'const' || kind === 'signal') {
const name = meaningful[i+1]?.value || '?';
nodes.push({ type: kind === 'signal' ? 'SignalDecl' : 'VarDecl', name, children:[] }); i += 2;
} else if (kind === 'import') {
const what = meaningful[i+1]?.value || '?';
nodes.push({ type:'ImportDecl', name:what, children:[] }); i += 2;
} else { i++; }
} else if (t.type === 'decorator') {
nodes.push({ type:'Decorator', name:t.value, children:[] }); i++;
} else if (t.type === 'pragma') {
nodes.push({ type:'Pragma', name:t.value, children:[] }); i++;
} else if (t.type === 'comment') {
if (t.value.startsWith('///')) nodes.push({ type:'DocComment', name:t.value.slice(3).trim(), children:[] });
i++;
} else { i++; }
}
return { type:'Module', name:'editor.ds', children:nodes };
}
function analyzeSignals(ast) {
const signals = [], effects = [], asyncBounds = [], complexity = { branches:0, loops:0, fns:0 };
function walk(node) {
if (node.type === 'SignalDecl') signals.push(node.name);
if (node.type === 'AsyncFnDecl') asyncBounds.push(node.name);
if (node.type === 'FnDecl' || node.type === 'AsyncFnDecl') complexity.fns++;
if (node.children) node.children.forEach(walk);
}
walk(ast);
return { signals, effects, asyncBounds, complexity, deps: signals.map(s => ({ signal:s, deps:[], hot:Math.random()>.5 })) };
}
function typeCheck(ast, tokens) {
const types = [];
function infer(node) {
if (node.type === 'VarDecl' || node.type === 'SignalDecl') {
const t = node.name.startsWith('is') ? 'bool' : node.name.match(/count|num|id|size|len|idx/) ? 'int' : node.name.match(/name|title|msg|text|str|url|path/) ? 'string' : 'any';
types.push({ name:node.name, type:t, scope:'module' });
}
if (node.type === 'FnDecl' || node.type === 'AsyncFnDecl') {
const ret = node.retType || 'void';
const prefix = node.type === 'AsyncFnDecl' ? 'async ' : '';
types.push({ name:node.name, type:`${prefix}(${(node.children||[]).map(c=>c.name+':any').join(', ')}) -> ${ret}`, scope:'module' });
}
if (node.type === 'TypeAlias') types.push({ name:node.name, type:'type', scope:'module' });
if (node.children) node.children.forEach(infer);
}
infer(ast);
return { types, errors:[], inferred:types.length, unified:Math.floor(types.length*.8) };
}
function generateCode(ast) {
let js = '// Generated by DreamStack Compiler v1.0.0\n"use strict";\n\n';
function emit(node, indent='') {
switch(node.type) {
case 'ComponentDecl':
js += `${indent}class ${node.name} extends DSComponent {\n`;
node.children.forEach(c => emit(c, indent+' '));
js += `${indent}}\n\n`;
break;
case 'FnDecl':
js += `${indent}function ${node.name}(${(node.children||[]).map(c=>c.name).join(', ')}) {\n${indent} /* ... */\n${indent}}\n\n`;
break;
case 'AsyncFnDecl':
js += `${indent}async function ${node.name}(${(node.children||[]).map(c=>c.name).join(', ')}) {\n${indent} /* ... */\n${indent}}\n\n`;
break;
case 'SignalDecl':
js += `${indent}const [${node.name}, set_${node.name}] = __signal();\n`;
break;
case 'VarDecl':
js += `${indent}let ${node.name};\n`;
break;
case 'TraitDecl':
js += `${indent}// trait ${node.name}\n${indent}const ${node.name}_vtable = {};\n\n`;
break;
case 'EffectDecl':
js += `${indent}const ${node.name} = __createEffect("${node.name}");\n\n`;
break;
case 'StructDecl':
js += `${indent}class ${node.name} {\n${indent} constructor(props) { Object.assign(this, props); }\n${indent}}\n\n`;
break;
case 'TypeAlias':
js += `${indent}/* type ${node.name} */\n`;
break;
case 'Decorator':
js += `${indent}/* ${node.name} */\n`;
break;
default:
if (node.children) node.children.forEach(c => emit(c, indent));
}
}
if (ast.children) ast.children.forEach(c => emit(c));
js += '\n// Source map: editor.ds -> editor.js';
return js;
}
function computeLayout(ast) {
const nodes = [];
let y = 0;
function walk(node, depth) {
nodes.push({ name:node.name||node.type, x:depth*20, y:y*28, w:200-depth*10, h:24, type:node.type });
y++;
if (node.children) node.children.forEach(c => walk(c, depth+1));
}
walk(ast, 0);
return { nodes, totalH: y*28, constraints:nodes.length };
}
function diagnose(code, tokens, ast) {
const diags = [];
tokens.forEach(t => {
if (t.type === 'unknown') diags.push({ severity:'error', line:code.slice(0,t.pos).split('\n').length, msg:`Unexpected character '${t.value}'`, code:'E001' });
});
if (code.includes('var ')) diags.push({ severity:'warn', line:code.slice(0,code.indexOf('var ')).split('\n').length, msg:'Use `let` or `const` instead of `var`', code:'W001' });
const lines = code.split('\n');
lines.forEach((l, i) => {
if (l.length > 120) diags.push({ severity:'info', line:i+1, msg:`Line exceeds 120 characters (${l.length})`, code:'I001' });
if (l.match(/\bconsole\.\w+/)) diags.push({ severity:'hint', line:i+1, msg:'Consider using Logger effect instead of console', code:'H001' });
});
if (!code.includes('///') && ast.children?.length > 0) diags.push({ severity:'hint', line:1, msg:'Consider adding doc comments (///) to public declarations', code:'H002' });
return diags;
}
// ─── Rendering ───
function renderAST(node, depth=0) {
const indent = ' '.repeat(depth);
let html = '';
const typeColor = node.type.match(/Fn|Async/) ? 'fn' : node.type.match(/Signal|Var/) ? 'int' : node.type.match(/Type|Trait|Struct/) ? 'generic' : node.type.match(/Component/) ? 'async' : node.type.match(/String|Doc/) ? 'str' : '';
html += `<div class="ast-node fade-in" style="animation-delay:${depth*30}ms">`
html += `<span class="ast-node-name">${node.type}</span>`;
if (node.name && node.name !== node.type) html += ` <span class="type-tag type-${typeColor||'int'}">${escHtml(node.name)}</span>`;
if (node.retType) html += ` <span class="ast-attr">→ ${node.retType}</span>`;
html += '\n';
if (node.children && node.children.length > 0) {
node.children.forEach(c => { html += renderAST(c, depth+1); });
}
html += '</div>';
return html;
}
function escHtml(s) { return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
function renderDiagnostics(diags) {
if (!diags.length) return '<span style="color:var(--green)">✓ No issues found</span>\n';
return diags.map(d => {
const cls = d.severity === 'error' ? 'error' : d.severity === 'warn' ? 'warn' : d.severity === 'info' ? 'info' : 'hint';
return `<div class="diag-line fade-in"><span class="diag-sev diag-${cls}">${d.severity.toUpperCase()}</span><span class="diag-msg">[${d.code}] Ln ${d.line}: ${escHtml(d.msg)}</span></div>`;
}).join('');
}
function renderTypes(types) {
return types.types.map(t => {
const cls = t.type.match(/int|float|number/) ? 'int' : t.type.match(/string/) ? 'str' : t.type.match(/bool/) ? 'bool' : t.type.match(/fn|async|\(/) ? 'fn' : t.type === 'type' ? 'generic' : 'async';
return `<div class="fade-in"><span style="color:var(--text)">${t.name}</span> <span class="ast-attr">:</span> <span class="type-tag type-${cls}">${escHtml(t.type)}</span></div>`;
}).join('') + `\n<span class="ast-attr">${types.inferred} inferred · ${types.unified} unified</span>`;
}
function renderAnalysis(analysis) {
let html = '<div class="fade-in">';
html += `<div style="color:var(--accent);font-weight:600;margin-bottom:.5rem">Signal Graph</div>`;
analysis.deps.forEach(d => {
html += `<div> ${d.hot?'🔥':' '} <span style="color:var(--green)">${d.signal}</span> <span class="ast-attr">← [${d.deps.join(', ')||'root'}]</span></div>`;
});
html += `\n<div style="color:var(--accent);font-weight:600;margin:.5rem 0">Metrics</div>`;
html += `<div> Functions: <span class="type-tag type-fn">${analysis.complexity.fns}</span></div>`;
html += `<div> Async boundaries: <span class="type-tag type-async">${analysis.asyncBounds.length}</span></div>`;
html += `<div> Signals: <span class="type-tag type-int">${analysis.signals.length}</span></div>`;
html += '</div>';
return html;
}
function renderLayout(layout) {
let html = `<div class="fade-in"><div style="color:var(--accent);font-weight:600;margin-bottom:.5rem">Layout Tree — ${layout.constraints} constraints</div>`;
layout.nodes.forEach(n => {
const bar = '█'.repeat(Math.max(1, Math.floor(n.w / 12)));
const color = n.type.match(/Comp|Module/) ? 'var(--accent)' : n.type.match(/Fn|Async/) ? 'var(--green)' : n.type.match(/Signal|Var/) ? 'var(--yellow)' : 'var(--text-dim)';
html += `<div> ${' '.repeat(Math.floor(n.x/20))}<span style="color:${color}">${bar}</span> <span class="ast-attr">${n.name} (${n.w}×${n.h})</span></div>`;
});
html += `\n<div class="ast-attr">Total height: ${layout.totalH}px</div></div>`;
return html;
}
function renderBuild(code) {
const hash = code.split('').reduce((a,c) => ((a<<5)-a+c.charCodeAt(0))|0, 0).toString(16).replace('-','');
const size = new Blob([code]).size;
let html = `<div class="fade-in"><div style="color:var(--accent);font-weight:600;margin-bottom:.5rem">Build Report</div>
Profile: <span class="type-tag type-fn">Release</span>
Strategy: <span class="type-tag type-async">Incremental</span>
Workers: <span class="type-tag type-int">4</span>
Hermetic: <span class="type-tag type-bool">true</span>
<div style="color:var(--accent);font-weight:600;margin:.5rem 0">Artifacts</div>
editor.ds → editor.js <span class="ast-attr">(${size} bytes)</span>
editor.ds → editor.css <span class="ast-attr">(extracted)</span>
editor.ds → editor.js.map <span class="ast-attr">(source map)</span>
<div style="color:var(--accent);font-weight:600;margin:.5rem 0">Cache</div>
Fingerprint: <span class="type-tag type-generic">${hash.slice(0,8)}</span>
Cache hit: <span style="color:var(--green)">✓ warm</span>
Signed: <span class="type-tag type-int">${hash.slice(0,12)}</span>
<div style="color:var(--accent);font-weight:600;margin:.5rem 0">Plugins</div>
✓ minifier ✓ tree-shaker ✓ css-extractor ✓ source-maps</div>`;
return html;
}
// ─── Main Pipeline ───
function runPipeline() {
const code = document.getElementById('editor').value;
if (!code.trim()) return;
const t0 = performance.now();
const tokens = tokenize(code);
const ast = parseAST(tokens);
const analysis = analyzeSignals(ast);
const types = typeCheck(ast, tokens);
const jsCode = generateCode(ast);
const layout = computeLayout(ast);
const diags = diagnose(code, tokens, ast);
const elapsed = (performance.now() - t0).toFixed(1);
lastResult = { tokens, ast, analysis, types, jsCode, layout, diags, code };
// Update stats
document.getElementById('stat-features').textContent = ast.children?.length || 0;
document.getElementById('stat-time').textContent = elapsed + 'ms';
// Animate pipeline
stages.forEach((s, i) => {
const el = document.getElementById('stage-'+s.id);
setTimeout(() => {
el.classList.add('active');
el.querySelector('.time').textContent = (parseFloat(elapsed) / 7 * (i+1)).toFixed(1) + 'ms';
}, i * 80);
});
showStage(activeStage);
}
function showStage(id) {
activeStage = id;
document.querySelectorAll('.pipe-stage').forEach(el => el.classList.toggle('active', el.id === 'stage-'+id));
const out = document.getElementById('output');
const title = document.getElementById('output-title');
const meta = document.getElementById('output-meta');
if (!lastResult) { out.innerHTML = '<span class="ast-attr">Press Compile or select an example to start</span>'; return; }
const { tokens, ast, analysis, types, jsCode, layout, diags, code } = lastResult;
switch(id) {
case 'parser':
title.textContent = 'AST Explorer'; meta.textContent = `${tokens.length} tokens · ${ast.children?.length||0} nodes`;
out.innerHTML = renderAST(ast);
break;
case 'types':
title.textContent = 'Type Checker'; meta.textContent = `${types.types.length} bindings`;
out.innerHTML = renderTypes(types);
break;
case 'analyzer':
title.textContent = 'Signal Analysis'; meta.textContent = `${analysis.signals.length} signals`;
out.innerHTML = renderAnalysis(analysis);
break;
case 'codegen':
title.textContent = 'JS Output'; meta.textContent = `${jsCode.split('\n').length} lines`;
out.innerHTML = `<span style="color:var(--text-muted)">${escHtml(jsCode)}</span>`;
break;
case 'layout':
title.textContent = 'Layout Solver'; meta.textContent = `${layout.constraints} constraints`;
out.innerHTML = renderLayout(layout);
break;
case 'diagnostic':
title.textContent = 'Diagnostics'; meta.textContent = `${diags.length} issues`;
out.innerHTML = renderDiagnostics(diags);
break;
case 'incremental':
title.textContent = 'Build System'; meta.textContent = 'incremental';
out.innerHTML = renderBuild(code);
break;
}
}
function loadExample(name) {
const editor = document.getElementById('editor');
editor.value = examples[name];
runPipeline();
}
// ─── Editor Events ───
const editor = document.getElementById('editor');
editor.addEventListener('input', () => { clearTimeout(editor._timer); editor._timer = setTimeout(runPipeline, 400); });
editor.addEventListener('keyup', () => {
const pos = editor.selectionStart;
const lines = editor.value.slice(0, pos).split('\n');
document.getElementById('cursor-pos').textContent = `Ln ${lines.length}, Col ${lines[lines.length-1].length+1}`;
});
editor.addEventListener('keydown', e => {
if (e.key === 'Tab') { e.preventDefault(); const s=editor.selectionStart; editor.value = editor.value.slice(0,s)+' '+editor.value.slice(editor.selectionEnd); editor.selectionStart=editor.selectionEnd=s+2; }
});
// ─── Boot ───
loadExample('hello');
// Animate stat counters
function animateCounter(id, target, suffix='') {
const el = document.getElementById(id);
let current = 0;
const step = Math.ceil(target / 30);
const timer = setInterval(() => {
current += step;
if (current >= target) { current = target; clearInterval(timer); }
el.textContent = current + suffix;
}, 20);
}
setTimeout(() => { animateCounter('stat-tests', 511); animateCounter('stat-packages', 7); }, 300);
</script>
</body>
</html>

View file

@ -0,0 +1,624 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DreamStack Stream — Interactive Receiver</title>
<meta name="description" content="Live interactive demo of the DreamStack Universal Bitstream Protocol — pixel streaming, delta compression, signal sync, and bidirectional input">
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#050510;--surface:#0c0c1a;--surface2:#141428;--surface3:#1e1e36;
--border:#252545;--glow:#818cf822;
--text:#e2e8f0;--muted:#94a3b8;--dim:#64748b;
--accent:#818cf8;--accent2:#a78bfa;--green:#34d399;--yellow:#fbbf24;
--red:#f87171;--cyan:#22d3ee;--pink:#f472b6;--orange:#fb923c;
--gradient:linear-gradient(135deg,#818cf8,#6366f1);
--font:'Inter',system-ui,sans-serif;--mono:'JetBrains Mono',monospace;
}
html{font-size:14px}
body{background:var(--bg);color:var(--text);font-family:var(--font);min-height:100vh;overflow-x:hidden}
::selection{background:#818cf855}
::-webkit-scrollbar{width:5px}::-webkit-scrollbar-thumb{background:#333;border-radius:3px}
.hero{text-align:center;padding:2rem 2rem 1rem;position:relative}
.hero::before{content:'';position:absolute;inset:0;background:radial-gradient(ellipse 70% 50% at 50% 0%,#6366f112,transparent);pointer-events:none}
.hero h1{font-size:2.4rem;font-weight:900;background:linear-gradient(135deg,#22d3ee,#818cf8,#a78bfa);-webkit-background-clip:text;-webkit-text-fill-color:transparent;letter-spacing:-0.03em}
.badge{display:inline-block;background:linear-gradient(135deg,#22d3ee,#6366f1);color:#fff;font-size:.65rem;font-weight:700;padding:.15rem .6rem;border-radius:999px;margin-bottom:.5rem;letter-spacing:.05em}
.hero p{color:var(--muted);font-size:.9rem;max-width:620px;margin:0 auto}
/* Grid */
.grid{display:grid;grid-template-columns:1fr 340px;gap:1px;padding:1rem;max-width:1400px;margin:0 auto}
@media(max-width:900px){.grid{grid-template-columns:1fr}}
/* Panels */
.panel{background:var(--surface);border:1px solid var(--border);border-radius:10px;overflow:hidden;display:flex;flex-direction:column}
.panel-h{display:flex;align-items:center;gap:.4rem;padding:.5rem .8rem;background:var(--surface2);border-bottom:1px solid var(--border);font-size:.7rem;font-weight:600;color:var(--muted)}
.panel-h .dot{width:7px;height:7px;border-radius:50%}
.panel-b{flex:1;overflow:auto;position:relative}
/* Canvas area */
.canvas-wrap{position:relative;background:#000;aspect-ratio:16/10;cursor:crosshair}
.canvas-wrap canvas{display:block;width:100%;height:100%;image-rendering:pixelated}
.canvas-overlay{position:absolute;top:8px;left:8px;display:flex;gap:6px;pointer-events:none}
.canvas-badge{background:#00000099;backdrop-filter:blur(8px);border:1px solid #ffffff15;border-radius:6px;padding:3px 8px;font-family:var(--mono);font-size:.6rem;color:#fff}
.canvas-badge.live{border-color:var(--red);color:var(--red)}
.mouse-pos{position:absolute;bottom:8px;right:8px;background:#00000099;backdrop-filter:blur(8px);border-radius:6px;padding:3px 8px;font-family:var(--mono);font-size:.6rem;color:var(--muted)}
/* Stats bar */
.stats-bar{display:grid;grid-template-columns:repeat(6,1fr);gap:1px;padding:.5rem .8rem;background:var(--surface2);border-top:1px solid var(--border)}
.stat-box{text-align:center}
.stat-v{font-size:.95rem;font-weight:800;font-family:var(--mono)}
.stat-l{font-size:.55rem;color:var(--dim);text-transform:uppercase;letter-spacing:.06em}
/* Sidebar */
.sidebar{display:flex;flex-direction:column;gap:1px}
/* Protocol inspector */
.proto-scroll{max-height:260px;overflow-y:auto;font-family:var(--mono);font-size:.68rem;padding:.5rem}
.proto-msg{display:flex;gap:6px;padding:3px 0;border-bottom:1px solid #ffffff06;align-items:center;animation:fadeSlide .2s ease}
.proto-dir{font-size:.55rem;font-weight:700;padding:1px 4px;border-radius:3px;flex-shrink:0}
.proto-out{background:#818cf822;color:var(--accent)}
.proto-in{background:#34d39922;color:var(--green)}
.proto-type{color:var(--cyan);min-width:85px}
.proto-seq{color:var(--dim)}
.proto-size{color:var(--muted);margin-left:auto}
@keyframes fadeSlide{from{opacity:0;transform:translateX(-6px)}to{opacity:1;transform:translateX(0)}}
/* Hex viewer */
.hex-view{font-family:var(--mono);font-size:.62rem;padding:.5rem;color:var(--muted);line-height:1.5;max-height:140px;overflow-y:auto}
.hex-offset{color:var(--dim)}
.hex-byte{color:var(--accent)}
.hex-header{color:var(--cyan)}
.hex-payload{color:var(--green)}
/* Controls */
.controls{padding:.6rem .8rem;display:flex;flex-wrap:wrap;gap:.4rem}
.btn{font-family:var(--font);font-size:.68rem;font-weight:600;padding:.4rem .8rem;border:1px solid var(--border);border-radius:6px;background:var(--surface2);color:var(--text);cursor:pointer;transition:all .15s;display:inline-flex;align-items:center;gap:.3rem}
.btn:hover{background:var(--surface3);border-color:var(--accent)}
.btn.active{background:var(--accent);border-color:var(--accent);color:#fff}
.btn-sm{padding:.25rem .5rem;font-size:.6rem}
/* Quality indicator */
.quality{display:flex;gap:4px;padding:.5rem .8rem;background:var(--surface2);border-top:1px solid var(--border)}
.q-bar{flex:1;height:6px;border-radius:3px;background:var(--surface3)}
.q-fill{height:100%;border-radius:3px;transition:width .3s}
/* Header diagram */
.header-dia{padding:.6rem;font-family:var(--mono);font-size:.62rem;line-height:1.6;background:var(--surface2);border-top:1px solid var(--border)}
.hdr-field{display:inline-block;padding:1px 5px;border-radius:3px;margin:0 1px}
.hdr-type{background:#22d3ee18;color:var(--cyan)}
.hdr-flags{background:#fbbf2418;color:var(--yellow)}
.hdr-seq{background:#818cf818;color:var(--accent)}
.hdr-ts{background:#f4728618;color:var(--pink)}
.hdr-dim{background:#34d39918;color:var(--green)}
.hdr-len{background:#fb923c18;color:var(--orange)}
/* Compression chart */
.comp-chart{display:flex;align-items:flex-end;gap:2px;height:60px;padding:.5rem .8rem}
.comp-bar{flex:1;background:var(--accent);border-radius:2px 2px 0 0;min-height:2px;transition:height .2s;position:relative}
.comp-bar::after{content:attr(data-ratio);position:absolute;top:-14px;left:50%;transform:translateX(-50%);font-size:.5rem;color:var(--muted);white-space:nowrap}
/* Signal graph */
.signal-list{padding:.5rem .8rem;font-family:var(--mono);font-size:.68rem}
.sig-row{display:flex;justify-content:space-between;padding:2px 0;border-bottom:1px solid #ffffff06}
.sig-name{color:var(--cyan)}
.sig-val{color:var(--green)}
.sig-changed{animation:sigPulse .4s ease}
@keyframes sigPulse{0%{color:var(--yellow)}100%{color:var(--green)}}
footer{text-align:center;padding:1.5rem;color:var(--dim);font-size:.65rem}
</style>
</head>
<body>
<div class="hero">
<div class="badge">UNIVERSAL BITSTREAM PROTOCOL v12</div>
<h1>ds-stream Interactive Receiver</h1>
<p>Live pixel streaming with XOR+RLE delta compression, signal sync, adaptive quality, and bidirectional input — all in the browser</p>
</div>
<div class="grid">
<!-- Main: Canvas + Stats -->
<div style="display:flex;flex-direction:column;gap:1px">
<div class="panel">
<div class="panel-h">
<div class="dot" style="background:var(--red)"></div>
<div class="dot" style="background:var(--yellow)"></div>
<div class="dot" style="background:var(--green)"></div>
<span>receiver viewport</span>
<span style="margin-left:auto" id="resolution">320×200</span>
<span id="stream-status" style="color:var(--green)">● LIVE</span>
</div>
<div class="panel-b">
<div class="canvas-wrap" id="canvas-wrap">
<canvas id="display" width="320" height="200"></canvas>
<div class="canvas-overlay">
<div class="canvas-badge live" id="badge-live">● LIVE</div>
<div class="canvas-badge" id="badge-fps">0 FPS</div>
<div class="canvas-badge" id="badge-frame">K #0</div>
</div>
<div class="mouse-pos" id="mouse-pos">0, 0</div>
</div>
</div>
<div class="stats-bar">
<div class="stat-box"><div class="stat-v" id="s-fps" style="color:var(--green)">0</div><div class="stat-l">FPS</div></div>
<div class="stat-box"><div class="stat-v" id="s-frames" style="color:var(--cyan)">0</div><div class="stat-l">Frames</div></div>
<div class="stat-box"><div class="stat-v" id="s-bytes" style="color:var(--accent)">0</div><div class="stat-l">KB Sent</div></div>
<div class="stat-box"><div class="stat-v" id="s-ratio" style="color:var(--yellow)">0%</div><div class="stat-l">Savings</div></div>
<div class="stat-box"><div class="stat-v" id="s-rtt" style="color:var(--pink)">0ms</div><div class="stat-l">RTT</div></div>
<div class="stat-box"><div class="stat-v" id="s-quality" style="color:var(--green)">Full</div><div class="stat-l">Quality</div></div>
</div>
</div>
<!-- Controls -->
<div class="panel">
<div class="panel-h"><span>stream controls</span></div>
<div class="controls">
<button class="btn active" id="btn-play" onclick="toggleStream()">⏸ Pause</button>
<button class="btn" onclick="sendKeyframe()">🔑 Force Keyframe</button>
<button class="btn" onclick="cycleFPS()">🎯 <span id="fps-label">30 FPS</span></button>
<button class="btn" onclick="cycleScene()">🎬 <span id="scene-label">Waves</span></button>
<button class="btn" onclick="toggleDelta()">📐 <span id="delta-label">Delta: ON</span></button>
<button class="btn" onclick="toggleSignals()">📡 <span id="sig-label">Signals: ON</span></button>
<button class="btn btn-sm" onclick="simulateLatency()">🌐 +100ms RTT</button>
<button class="btn btn-sm" onclick="resetStats()">↻ Reset</button>
</div>
</div>
<!-- Compression chart -->
<div class="panel">
<div class="panel-h"><span>compression ratio per frame</span><span style="margin-left:auto" id="avg-ratio">avg: 0%</span></div>
<div class="comp-chart" id="comp-chart"></div>
</div>
</div>
<!-- Sidebar -->
<div class="sidebar">
<!-- Header Format -->
<div class="panel">
<div class="panel-h"><span>16-byte header format</span></div>
<div class="header-dia" id="header-dia">
<div style="margin-bottom:4px;color:var(--muted)">┌──────┬───────┬──────┬───────────┬───────┬────────┬────────┐</div>
<div><span class="hdr-field hdr-type">type</span><span class="hdr-field hdr-flags">flags</span><span class="hdr-field hdr-seq">seq</span><span class="hdr-field hdr-ts">timestamp</span><span class="hdr-field hdr-dim">width</span><span class="hdr-field hdr-dim">height</span><span class="hdr-field hdr-len">length</span></div>
<div style="margin-top:4px;color:var(--muted)"><span style="color:var(--cyan)">u8</span><span style="color:var(--yellow)">u8</span><span style="color:var(--accent)">u16</span><span style="color:var(--pink)">u32</span><span style="color:var(--green)">u16</span><span style="color:var(--green)">u16</span><span style="color:var(--orange)">u32</span></div>
<div style="margin-top:4px;color:var(--muted)">└──────┴───────┴──────┴───────────┴───────┴────────┴────────┘</div>
<div id="hdr-live" style="margin-top:6px;color:var(--text)"></div>
</div>
</div>
<!-- Protocol Inspector -->
<div class="panel" style="flex:1">
<div class="panel-h"><span>protocol inspector</span><span style="margin-left:auto" id="msg-count">0 msgs</span></div>
<div class="proto-scroll" id="proto-log"></div>
</div>
<!-- Hex View -->
<div class="panel">
<div class="panel-h"><span>last frame hex dump</span></div>
<div class="hex-view" id="hex-view">Waiting for frames...</div>
</div>
<!-- Signals -->
<div class="panel">
<div class="panel-h"><span>signal state</span><span style="margin-left:auto" id="sig-count">0 signals</span></div>
<div class="signal-list" id="signal-list"></div>
</div>
<!-- Quality -->
<div class="panel">
<div class="panel-h"><span>adaptive quality</span></div>
<div class="quality">
<div class="q-bar"><div class="q-fill" id="q-fill" style="width:100%;background:var(--green)"></div></div>
</div>
<div style="padding:.3rem .8rem;font-size:.6rem;color:var(--muted);display:flex;justify-content:space-between">
<span>Full (RTT&lt;50ms)</span><span>Reduced</span><span>Minimal</span>
</div>
</div>
</div>
</div>
<footer>DreamStack Universal Bitstream Protocol v12 — ds-stream · 16-byte headers · XOR+RLE delta · Adaptive quality · Stream mux · 24 frame types</footer>
<script>
// ─── Protocol Constants ───
const HEADER_SIZE = 16;
const FLAG_INPUT = 0x01, FLAG_KEYFRAME = 0x02, FLAG_COMPRESSED = 0x04;
const FRAME_TYPES = {
0x01:'Pixels', 0x02:'CompressedPx', 0x03:'DeltaPixels',
0x10:'AudioPCM', 0x11:'AudioOpus',
0x20:'Haptic', 0x21:'Actuator', 0x22:'LedMatrix',
0x30:'SignalSync', 0x31:'SignalDiff', 0x32:'SchemaAnnounce', 0x33:'SubscribeFilter',
0x40:'NeuralFrame', 0x41:'NeuralAudio', 0x42:'NeuralActuator', 0x43:'NeuralLatent',
0x50:'StateSync', 0x51:'Replay', 0x52:'Compressed',
0xF0:'Keyframe', 0x0F:'Auth', 0xFD:'Ack', 0xFE:'Ping', 0xFF:'End'
};
// ─── State ───
const W = 320, H = 200;
const canvas = document.getElementById('display');
const ctx = canvas.getContext('2d');
let running = true, useDelta = true, showSignals = true;
let seq = 0, frameCount = 0, totalBytes = 0, msgCount = 0;
let targetFPS = 30, currentScene = 0;
const scenes = ['Waves','Plasma','Rain','Matrix'];
let previousFrame = null, rttMs = 12;
let fpsCounter = 0, lastFpsTick = performance.now();
let compressionHistory = [];
let signals = { count: 0, time: 0, mouse_x: 0, mouse_y: 0, fps: 30, scene: 'waves', quality: 'full' };
// ─── Header encode/decode ───
function encodeHeader(type, flags, seq, ts, w, h, len) {
const buf = new ArrayBuffer(HEADER_SIZE);
const dv = new DataView(buf);
dv.setUint8(0, type); dv.setUint8(1, flags);
dv.setUint16(2, seq, true); dv.setUint32(4, ts, true);
dv.setUint16(8, w, true); dv.setUint16(10, h, true);
dv.setUint32(12, len, true);
return new Uint8Array(buf);
}
function decodeHeader(buf) {
const dv = new DataView(buf.buffer, buf.byteOffset);
return {
type: dv.getUint8(0), flags: dv.getUint8(1),
seq: dv.getUint16(2, true), timestamp: dv.getUint32(4, true),
width: dv.getUint16(8, true), height: dv.getUint16(10, true),
length: dv.getUint32(12, true)
};
}
// ─── XOR Delta ───
function computeDelta(current, previous) {
const delta = new Uint8Array(current.length);
for (let i = 0; i < current.length; i++) delta[i] = current[i] ^ previous[i];
return delta;
}
function applyDelta(previous, delta) {
const out = new Uint8Array(previous.length);
for (let i = 0; i < previous.length; i++) out[i] = previous[i] ^ delta[i];
return out;
}
// ─── RLE ───
function rleEncode(data) {
const out = [];
let i = 0;
while (i < data.length) {
if (data[i] === 0) {
let start = i;
while (i < data.length && data[i] === 0) i++;
let count = i - start;
while (count > 0) {
const chunk = Math.min(count, 65535);
out.push(0, chunk & 0xFF, (chunk >> 8) & 0xFF);
count -= chunk;
}
} else { out.push(data[i]); i++; }
}
return new Uint8Array(out);
}
function rleDecode(data) {
const out = [];
let i = 0;
while (i < data.length) {
if (data[i] === 0 && i + 2 < data.length) {
const count = data[i+1] | (data[i+2] << 8);
for (let j = 0; j < count; j++) out.push(0);
i += 3;
} else { out.push(data[i]); i++; }
}
return new Uint8Array(out);
}
// ─── Scene Generators ───
function generateWaves(t) {
const img = ctx.createImageData(W, H);
for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) {
const i = (y * W + x) * 4;
const v1 = Math.sin(x*0.03 + t*2) * Math.cos(y*0.02 + t) * 0.5 + 0.5;
const v2 = Math.sin((x+y)*0.02 - t*1.5) * 0.5 + 0.5;
img.data[i] = (v1 * 100 + 30)|0;
img.data[i+1] = (v2 * 130 + 80)|0;
img.data[i+2] = (v1 * v2 * 255)|0;
img.data[i+3] = 255;
}
return img;
}
function generatePlasma(t) {
const img = ctx.createImageData(W, H);
for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) {
const i = (y * W + x) * 4;
const v = Math.sin(x*0.05+t) + Math.sin(y*0.05+t*0.7) + Math.sin((x+y)*0.03+t*1.3) + Math.sin(Math.sqrt(x*x+y*y)*0.04);
const c = (v + 4) / 8;
img.data[i] = (Math.sin(c*Math.PI*2)*127+128)|0;
img.data[i+1] = (Math.sin(c*Math.PI*2+2.094)*127+128)|0;
img.data[i+2] = (Math.sin(c*Math.PI*2+4.189)*127+128)|0;
img.data[i+3] = 255;
}
return img;
}
let rainDrops = Array.from({length:60}, () => ({x:Math.random()*W, y:Math.random()*H, s:1+Math.random()*3, l:3+Math.random()*8}));
function generateRain(t) {
const img = ctx.createImageData(W, H);
// dark bg
for (let i = 0; i < img.data.length; i += 4) { img.data[i]=8; img.data[i+1]=10; img.data[i+2]=20; img.data[i+3]=255; }
rainDrops.forEach(d => {
d.y += d.s; if (d.y > H) { d.y = 0; d.x = Math.random()*W; }
for (let j = 0; j < d.l; j++) {
const py = (d.y - j)|0;
if (py >= 0 && py < H) {
const idx = (py * W + (d.x|0)) * 4;
const alpha = 1 - j/d.l;
img.data[idx] = (100 * alpha)|0;
img.data[idx+1] = (180 * alpha)|0;
img.data[idx+2] = (255 * alpha)|0;
}
}
});
return img;
}
let matrixCols = Array.from({length:W/6|0}, () => ({y:Math.random()*H, speed:2+Math.random()*5}));
function generateMatrix(t) {
const img = ctx.createImageData(W, H);
for (let i = 0; i < img.data.length; i += 4) { img.data[i]=0; img.data[i+1]=5; img.data[i+2]=0; img.data[i+3]=255; }
matrixCols.forEach((col, ci) => {
col.y += col.speed; if (col.y > H + 40) col.y = -20;
for (let j = 0; j < 15; j++) {
const py = (col.y - j*8)|0;
if (py >= 0 && py < H) {
const x = ci * 6;
const brightness = j === 0 ? 255 : Math.max(40, 200 - j*15);
for (let dx = 0; dx < 5 && x+dx < W; dx++) {
for (let dy = 0; dy < 7 && py+dy < H; dy++) {
const idx = ((py+dy)*W+(x+dx))*4;
if (Math.random() > 0.3) img.data[idx+1] = brightness;
}
}
}
}
});
return img;
}
const generators = [generateWaves, generatePlasma, generateRain, generateMatrix];
// ─── Protocol Message Logging ───
function logMessage(dir, type, seq, size, extra='') {
const log = document.getElementById('proto-log');
const name = FRAME_TYPES[type] || `0x${type.toString(16).padStart(2,'0')}`;
const dirClass = dir === 'OUT' ? 'proto-out' : 'proto-in';
const div = document.createElement('div');
div.className = 'proto-msg';
div.innerHTML = `<span class="proto-dir ${dirClass}">${dir}</span><span class="proto-type">${name}</span><span class="proto-seq">#${seq}</span>${extra?`<span style="color:var(--dim)">${extra}</span>`:''}<span class="proto-size">${size}B</span>`;
log.prepend(div);
while (log.children.length > 60) log.lastChild.remove();
msgCount++;
document.getElementById('msg-count').textContent = msgCount + ' msgs';
}
// ─── Hex Dump ───
function hexDump(header, payloadSize) {
const el = document.getElementById('hex-view');
const hdr = decodeHeader(header);
const bytes = Array.from(header).map(b => b.toString(16).padStart(2,'0'));
el.innerHTML =
`<span class="hex-offset">0000</span> <span class="hex-header">${bytes.slice(0,4).join(' ')}</span> <span class="hex-header">${bytes.slice(4,8).join(' ')}</span> <span class="hex-header">${bytes.slice(8,12).join(' ')}</span> <span class="hex-header">${bytes.slice(12,16).join(' ')}</span>\n` +
`<span style="color:var(--dim)"> type=<span class="hex-byte">0x${bytes[0]}</span> flags=<span class="hex-byte">0x${bytes[1]}</span> seq=<span class="hex-byte">${hdr.seq}</span> ts=<span class="hex-byte">${hdr.timestamp}</span></span>\n` +
`<span style="color:var(--dim)"> ${hdr.width}×${hdr.height} payload=<span class="hex-payload">${payloadSize} bytes</span></span>`;
// Live header display
document.getElementById('hdr-live').innerHTML = `Last: <span class="hdr-field hdr-type">0x${bytes[0]}</span> <span class="hdr-field hdr-flags">0x${bytes[1]}</span> <span class="hdr-field hdr-seq">${hdr.seq}</span> <span class="hdr-field hdr-ts">${hdr.timestamp}ms</span> <span class="hdr-field hdr-dim">${hdr.width}×${hdr.height}</span> <span class="hdr-field hdr-len">${payloadSize}B</span>`;
}
// ─── Update Signals ───
function updateSignals() {
if (!showSignals) return;
const el = document.getElementById('signal-list');
signals.time = (performance.now()/1000).toFixed(1);
signals.count = frameCount;
signals.fps = targetFPS;
signals.scene = scenes[currentScene].toLowerCase();
signals.quality = rttMs < 50 ? 'full' : rttMs < 150 ? 'reduced' : 'minimal';
el.innerHTML = Object.entries(signals).map(([k,v]) =>
`<div class="sig-row"><span class="sig-name">${k}</span><span class="sig-val">${v}</span></div>`
).join('');
document.getElementById('sig-count').textContent = Object.keys(signals).length + ' signals';
}
// ─── Compression Chart ───
function updateCompressionChart(ratio) {
compressionHistory.push(ratio);
if (compressionHistory.length > 40) compressionHistory.shift();
const el = document.getElementById('comp-chart');
el.innerHTML = compressionHistory.map(r => {
const h = Math.max(2, (1-r) * 60);
const c = r < 0.3 ? 'var(--green)' : r < 0.6 ? 'var(--yellow)' : 'var(--accent)';
return `<div class="comp-bar" style="height:${h}px;background:${c}" data-ratio="${((1-r)*100).toFixed(0)}%"></div>`;
}).join('');
const avg = compressionHistory.reduce((a,b)=>a+b,0) / compressionHistory.length;
document.getElementById('avg-ratio').textContent = `avg: ${((1-avg)*100).toFixed(0)}% saved`;
}
// ─── Quality Update ───
function updateQuality() {
const fill = document.getElementById('q-fill');
const el = document.getElementById('s-quality');
if (rttMs < 50) { fill.style.width = '100%'; fill.style.background = 'var(--green)'; el.textContent = 'Full'; el.style.color = 'var(--green)'; }
else if (rttMs < 150) { fill.style.width = '50%'; fill.style.background = 'var(--yellow)'; el.textContent = 'Med'; el.style.color = 'var(--yellow)'; }
else { fill.style.width = '15%'; fill.style.background = 'var(--red)'; el.textContent = 'Min'; el.style.color = 'var(--red)'; }
}
// ─── Main Loop ───
let t = 0;
function streamFrame() {
if (!running) return;
t += 0.016;
const timestamp = (performance.now())|0;
// Generate frame
const imageData = generators[currentScene](t);
const currentPixels = new Uint8Array(imageData.data.buffer);
let payloadBytes, frameType, flags, isKey = false;
if (!previousFrame || !useDelta || frameCount % 60 === 0) {
// Keyframe
payloadBytes = currentPixels;
frameType = 0x01; flags = FLAG_KEYFRAME;
isKey = true;
updateCompressionChart(1.0);
} else {
// Delta frame with RLE
const delta = computeDelta(currentPixels, previousFrame);
const compressed = rleEncode(delta);
const ratio = compressed.length / currentPixels.length;
payloadBytes = compressed;
frameType = 0x03; flags = FLAG_COMPRESSED;
updateCompressionChart(ratio);
}
// Encode header
const header = encodeHeader(frameType, flags, seq, timestamp, W, H, payloadBytes.length);
totalBytes += HEADER_SIZE + payloadBytes.length;
// Log
const extra = isKey ? 'KEY' : `${((1-payloadBytes.length/currentPixels.length)*100).toFixed(0)}% saved`;
logMessage('OUT', frameType, seq, HEADER_SIZE + payloadBytes.length, extra);
hexDump(header, payloadBytes.length);
// "Receive" — decode on receiver side
if (frameType === 0x03 && previousFrame) {
const decodedDelta = rleDecode(payloadBytes);
const reconstructed = applyDelta(previousFrame, decodedDelta);
const recImg = new ImageData(new Uint8ClampedArray(reconstructed.buffer), W, H);
ctx.putImageData(recImg, 0, 0);
previousFrame = reconstructed;
} else {
ctx.putImageData(imageData, 0, 0);
previousFrame = new Uint8Array(currentPixels);
}
// ACK back
if (frameCount % 5 === 0) {
const ackHeader = encodeHeader(0xFD, 0, seq, timestamp, 0, 0, 4);
logMessage('IN', 0xFD, seq, HEADER_SIZE + 4, `rtt=${rttMs}ms`);
}
// Signal sync
if (showSignals && frameCount % 30 === 0) {
const sigJson = JSON.stringify(signals);
logMessage('OUT', 0x30, seq, HEADER_SIZE + sigJson.length, `${Object.keys(signals).length} sigs`);
} else if (showSignals && frameCount % 10 === 0) {
logMessage('OUT', 0x31, seq, HEADER_SIZE + 20, 'diff');
}
// Ping
if (frameCount % 90 === 0) {
logMessage('OUT', 0xFE, seq, HEADER_SIZE, '♥');
}
seq = (seq + 1) & 0xFFFF;
frameCount++;
fpsCounter++;
// Update UI
document.getElementById('badge-frame').textContent = `${isKey?'K':'Δ'} #${frameCount}`;
document.getElementById('s-frames').textContent = frameCount;
document.getElementById('s-bytes').textContent = (totalBytes/1024).toFixed(0);
document.getElementById('s-rtt').textContent = rttMs + 'ms';
const savings = previousFrame && useDelta ? ((1-payloadBytes.length/(W*H*4))*100).toFixed(0) : '0';
document.getElementById('s-ratio').textContent = savings + '%';
updateSignals();
updateQuality();
}
// FPS counter
setInterval(() => {
const now = performance.now();
const fps = (fpsCounter / ((now - lastFpsTick) / 1000)).toFixed(0);
document.getElementById('s-fps').textContent = fps;
document.getElementById('badge-fps').textContent = fps + ' FPS';
fpsCounter = 0; lastFpsTick = now;
}, 1000);
// Stream loop
let streamTimer;
function startStream() {
streamTimer = setInterval(streamFrame, 1000 / targetFPS);
}
startStream();
// ─── Controls ───
function toggleStream() {
running = !running;
const btn = document.getElementById('btn-play');
btn.textContent = running ? '⏸ Pause' : '▶ Play';
btn.classList.toggle('active', running);
document.getElementById('badge-live').textContent = running ? '● LIVE' : '⏸ PAUSED';
document.getElementById('badge-live').style.color = running ? 'var(--red)' : 'var(--muted)';
document.getElementById('stream-status').textContent = running ? '● LIVE' : '⏸ PAUSED';
document.getElementById('stream-status').style.color = running ? 'var(--green)' : 'var(--muted)';
}
function sendKeyframe() { previousFrame = null; logMessage('IN', 0xF0, seq, HEADER_SIZE, 'forced'); }
function cycleFPS() {
const rates = [15, 30, 60];
const idx = (rates.indexOf(targetFPS) + 1) % rates.length;
targetFPS = rates[idx];
document.getElementById('fps-label').textContent = targetFPS + ' FPS';
clearInterval(streamTimer); startStream();
}
function cycleScene() {
currentScene = (currentScene + 1) % scenes.length;
document.getElementById('scene-label').textContent = scenes[currentScene];
previousFrame = null;
}
function toggleDelta() {
useDelta = !useDelta;
document.getElementById('delta-label').textContent = `Delta: ${useDelta?'ON':'OFF'}`;
}
function toggleSignals() {
showSignals = !showSignals;
document.getElementById('sig-label').textContent = `Signals: ${showSignals?'ON':'OFF'}`;
}
function simulateLatency() { rttMs = Math.min(300, rttMs + 50); updateQuality(); }
function resetStats() {
frameCount = 0; totalBytes = 0; seq = 0; msgCount = 0; rttMs = 12;
compressionHistory = []; previousFrame = null;
document.getElementById('proto-log').innerHTML = '';
document.getElementById('comp-chart').innerHTML = '';
}
// ─── Mouse tracking as input ───
const wrap = document.getElementById('canvas-wrap');
wrap.addEventListener('mousemove', e => {
const rect = wrap.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width * W)|0;
const y = ((e.clientY - rect.top) / rect.height * H)|0;
signals.mouse_x = x; signals.mouse_y = y;
document.getElementById('mouse-pos').textContent = `${x}, ${y}`;
if (running && frameCount % 3 === 0) logMessage('IN', 0x01, seq, HEADER_SIZE+5, `${x},${y}`);
});
wrap.addEventListener('click', e => {
const rect = wrap.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width * W)|0;
const y = ((e.clientY - rect.top) / rect.height * H)|0;
logMessage('IN', 0x02, seq, HEADER_SIZE+5, `click ${x},${y}`);
});
</script>
</body>
</html>