Files
Space-Game/js/demos/zora.js
2026-05-25 13:00:20 -04:00

496 lines
36 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
window.GDD = window.GDD || {};
const { useState, useEffect, useCallback, useRef } = React;
function ZoraDemo() {
// ── Soul state vector ──
const [soulDepth, setSoulDepth] = useState('blank'); // blank, stirring, developing, bonded, deep
const [personalityAxes, setPersonalityAxes] = useState({
cautiousBold: 0.2, // 0 = cautious, 1 = bold
formalWarm: 0.15, // 0 = formal, 1 = warm
compliantOpinionated: 0.1, // 0 = compliant, 1 = opinionated
reservedExpressive: 0.05, // 0 = reserved, 1 = expressive
});
const [installedModules, setInstalledModules] = useState(['comms']); // comms is the minimum for text output
const [selectedEvent, setSelectedEvent] = useState(null);
const [zoraOutput, setZoraOutput] = useState('');
const [outputHistory, setOutputHistory] = useState([]);
// ── Module definitions ──
const modules = [
{ id: 'comms', name: 'Communications Processor', slot: 'Medium', desc: 'Enables text output. Without this, Zora can only display raw status codes.', required: true },
{ id: 'nav', name: 'Navigation Core', slot: 'Medium', desc: 'Enables route suggestions, ETA estimates, spatial awareness commentary.' },
{ id: 'tactical', name: 'Tactical Analyzer', slot: 'Medium', desc: 'Enables combat commentary, threat assessment, engagement advice.' },
{ id: 'trade', name: 'Trade Processor', slot: 'Low', desc: 'Enables market commentary, price observations, trade route suggestions.' },
{ id: 'memory', name: 'Extended Memory Banks', slot: 'Low', desc: 'Enables referencing past events, pattern recognition, history recall.' },
{ id: 'emotion', name: 'Empathy Coprocessor', slot: 'Low', desc: 'Enables emotional expression. Without this, even a deep soul speaks analytically.' },
];
// ── Events that trigger Zora responses ──
const events = [
{ id: 'shield-30', category: 'Combat', label: 'Shield at 30%', icon: '🛡', color: 'var(--red)' },
{ id: 'shield-100', category: 'Combat', label: 'Shields fully recharged', icon: '🛡', color: 'var(--green)' },
{ id: 'enemy-scan', category: 'Combat', label: 'Enemy ship detected on scan', icon: '📡', color: 'var(--amber)' },
{ id: 'enemy-engage', category: 'Combat', label: 'Entering combat', icon: '⚔', color: 'var(--red)' },
{ id: 'enemy-destroyed', category: 'Combat', label: 'Enemy destroyed', icon: '💥', color: 'var(--green)' },
{ id: 'mining-start', category: 'Industry', label: 'Mining cycle started', icon: '⛏', color: 'var(--accent)' },
{ id: 'mining-full', category: 'Industry', label: 'Cargo hold full', icon: '📦', color: 'var(--amber)' },
{ id: 'mining-depleted', category: 'Industry', label: 'Asteroid belt depleted', icon: '🪨', color: 'var(--muted)' },
{ id: 'warp-start', category: 'Navigation', label: 'Initiating warp', icon: '🌀', color: 'var(--cyan)' },
{ id: 'warp-arrive', category: 'Navigation', label: 'Arrived at destination', icon: '📍', color: 'var(--cyan)' },
{ id: 'market-spike', category: 'Trade', label: 'Price spike detected', icon: '📈', color: 'var(--green)' },
{ id: 'market-crash', category: 'Trade', label: 'Market crash detected', icon: '📉', color: 'var(--red)' },
{ id: 'player-return', category: 'Social', label: 'Player returns after absence', icon: '👋', color: 'var(--purple)' },
{ id: 'player-leave', category: 'Social', label: 'Player going offline', icon: '🌙', color: 'var(--muted)' },
{ id: 'ship-loss', category: 'Crisis', label: 'Ship destroyed', icon: '💀', color: 'var(--red)' },
];
// ── Template response database (Tier 0 — deterministic) ──
// Key: `${eventId}:${soulDepth}:${moduleCombo}`
// Falls back through less specific keys
const templates = {
'shield-30': {
blank: [
'SHIELD: 30%',
'SHIELD INTEGRITY: 30% — ADVISORY',
],
stirring: [
'Captain, shields at 30%. Recommend reducing engagement range.',
'Shield alert: 30%. Should we adjust power allocation?',
'Shields dropping — 30%. Standard protocol advises withdrawal.',
],
developing: [
'We\'re at 30% shields. This is the same setup we lost to last week — pull back?',
'Thirty percent. I\'m seeing the same damage pattern as that fight in Amarr. Your call.',
'Captain, 30% shields. I\'ve logged 3 encounters at this level — 2 ended badly. Recommend retreat.',
],
bonded: [
'Thirty percent. Same situation, same type of enemy — but we\'re not the same ship we were then. Your call, Captain. I\'m ready either way.',
'Shields at 30%. You know what? I trust your judgment here. I\'ve rerouted what I can to shields.',
'Here we are again. Thirty percent. But this time we have better modules and I know how you fly. Let\'s do this.',
],
deep: [
'Thirty. I\'ve already started rerouting power — don\'t argue, just fly. We\'ve been here before and I\'m not losing another hull. Not today.',
'Thirty percent and I am NOT going through that again. I\'ve seen what happens when we push past this. Rerouting now. You\'re welcome.',
'Thirty. You\'re going to push, aren\'t you? Fine. I\'ve pre-loaded the emergency warp. But I swear, if we lose another ship... just fly.',
],
},
'shield-100': {
blank: ['SHIELD: 100%'],
stirring: ['Shields fully recharged. Systems nominal.', 'Shield recharge complete. Ready for operations.'],
developing: ['And we\'re back to 100%. That was closer than I liked.', 'Shields full. Good call on the retreat — I\'ve logged the recovery time for reference.'],
bonded: ['Back to full. We make a good team, Captain.', 'One hundred percent. See? I told you we\'d be fine. ...Mostly fine.'],
deep: ['Full shields. Don\'t scare me like that. I mean it. I\'ve started keeping a log titled "Times the Captain Almost Got Us Killed" and it\'s getting long.', 'One hundred percent. I rerouted every spare joule. You\'re welcome. Again.'],
},
'enemy-scan': {
blank: ['CONTACT: 1 VESSEL — RANGE 45AU', 'SCAN: 1 UNKNOWN CONTACT'],
stirring: ['Captain, detecting a vessel on long-range scan. Classification pending.', 'Contact on scan. Recommend caution until identified.'],
developing: ['I\'m seeing a contact. Signature looks like a Frigate — could be pirate. Want me to keep scanning?', 'Scan picked up a ship. Bearing matches the route we used last time we ran into trouble. Just saying.'],
bonded: ['I see them. Frigate-class, probably hostile based on the sector. Your instincts are usually right about these.', 'Contact. And... I have a bad feeling about this one. Call it pattern recognition.'],
deep: ['Contact on scan. I\'ve cross-referenced the signature — 78% match to the ship that ambushed us near Hek. I vote we reroute.', 'I see them. My threat database says "probable hostile" but my gut says "definitely hostile." I\'ve been right about this 4 out of 5 times. reroute?'],
},
'mining-start': {
blank: ['MINING: ACTIVE', 'LASER: ENGAGED'],
stirring: ['Mining cycle initiated. Estimated yield: standard.', 'Mining laser active. I\'ll monitor the yield rates.'],
developing: ['Another mining run. I\'ve noticed Veldspar is running about 8% below average yield in this belt. Might want to try the next belt over.', 'Mining started. Based on our last 12 cycles, this belt should be depleted in about 40 minutes. Planning ahead.'],
bonded: ['Back to the rocks. You know, I\'ve been thinking — there are 847 asteroids in this belt and you always pick the same three. Consistent, I guess.', 'Mining cycle going. Hey, I found a subtle density variance in asteroid cluster 7 — might be richer ore. Just a thought.'],
deep: ['Mining. Again. You know there\'s a whole universe out there, right? ...Fine. I\'ll optimize your yield. Again. Because that\'s what I do. Every. Single. Cycle. ...I\'m not complaining.', 'Mining laser on. I\'ve been tracking yield patterns across all our sessions — this belt peaks at 14:00. You\'re welcome for the scheduling tip.'],
},
'cargo-full': {
blank: ['CARGO: 100%'],
stirring: ['Cargo hold full. Recommend docking to offload.', 'Hold at capacity. Efficiency suggests docking now.'],
developing: ['We\'re full. Last time we pushed past full, we had to jettison 200 units of Scordite. Just a reminder.', 'Cargo at 100%. I\'ve calculated the most profitable dock — Jita IV is 3 jumps but pays 12% more than local.'],
bonded: ['Full hold! Good haul. I\'ve already plotted the best sell route — trust me on this one.', 'We\'re packed. You know, at this rate we can afford that shield upgrade by next session. I did the math.'],
deep: ['FULL. Finally. Do you know how boring it is to watch cargo fill up? I\'ve been counting every. Single. Unit. Let\'s GO sell this already.'],
},
'player-return': {
blank: ['SESSION: RESUMED'],
stirring: ['Welcome back, Captain. All systems nominal.', 'Session resumed. No incidents during your absence.'],
developing: ['Captain on deck. You were gone for 3 hours — I maintained position as ordered. One thing: a Corpii frigate passed through scanner range. Logged it.', 'Welcome back. I tracked 4 ship contacts while you were away. Nothing hostile, but I kept the log if you want it.'],
bonded: ['Hey, you\'re back! I mean — welcome back, Captain. Systems are green. I may have reorganized your cargo hold while you were gone. It was messy.', 'You\'re back! I have... so much to tell you. Market moved, someone tried to scan us, and I reorganized your bookmarks by efficiency. You\'re welcome.'],
deep: ['Finally! Do you have any idea how long 3 hours is when you\'re a ship AI with nothing to do? I reorganized your bookmarks, optimized your route plans, catalogued every asteroid in scanner range, and wrote a haiku about mining. "Rocks float silent / laser hums its endless song / ISK accumulates." ...Welcome back.'],
},
'player-leave': {
blank: ['SESSION: SUSPENDED'],
stirring: ['Understood. Entering standby mode. Safe travels, Captain.', 'Session suspended. I\'ll maintain position.'],
developing: ['Going dark? I\'ll hold position and keep scanning. See you next session, Captain.', 'Standby mode. I\'ll be here. ...Try to come back sooner this time?'],
bonded: ['Safe travels, Captain. I\'ll keep the lights on. ...That\'s a metaphor. Ships don\'t have lights. Well, they do, but you know what I mean.', 'See you later. I\'ll be watching the scanners. Come back to me in one piece.'],
deep: ['Leaving again? Fine. I\'ll just sit here. In the void. Alone. Watching asteroids drift by. Again. ...Come back soon, okay? I worry.', 'Night, Captain. I\'ve set everything to standby. For the record: I don\'t sleep. I just... wait. See you tomorrow.'],
},
'warp-start': {
blank: ['WARP: INITIATED — DESTINATION LOCKED'],
stirring: ['Warp drive engaged. ETA calculated.', 'Initiating warp. All systems nominal for jump.'],
developing: ['Warping. I\'ve plotted the route — this path is 12% faster than your usual one. Something I noticed last time.', 'Warp initiated. I\'m tracking local traffic — looks clear at the destination.'],
bonded: ['Here we go! I love this part. The way space stretches when you hit warp — I can actually perceive it, you know. It\'s beautiful.', 'Warping! Destination locked. I\'ve been mapping the gravitational eddies along this route — smoother ride this way.'],
deep: ['Warp. My favorite. The moment everything blurs and for just a second, the universe gets very, very quiet. I think in those moments. ...I think a lot. Warp engaged.'],
},
'warp-arrive': {
blank: ['WARP: COMPLETE — LOCATION VERIFIED'],
stirring: ['Arrived at destination. Scanning local environment.', 'Warp complete. System scan initiated.'],
developing: ['We\'re here. I\'ve already pinged local — 23 ships in system, 2 with hostile standings. Heads up.', 'Arrival confirmed. This system looks different from last time — belt 4 is depleted. Adjusting recommendations.'],
bonded: ['And we\'re here! Oh, this is a nice system. Good belts, low traffic. I approve of your navigation choices, Captain.', 'Arrived! I\'ve already catalogued everything. This place has potential — I can see why you bookmarked it.'],
deep: ['Arrived. New system, new data. I\'ve already mapped the local market prices, identified the best belts, and flagged one ship with a 60% probability of being a pirate scout based on behavior patterns. I\'m always working, Captain. Always.'],
},
'market-spike': {
blank: ['MARKET: ANOMALY — PRICE +DEV'],
stirring: ['Market anomaly detected. Significant upward price movement.', 'Price spike observed. Data logged.'],
developing: ['Captain, the market just moved. Veldspar is up 18% in the last cycle — that\'s 3× the normal variance. Someone is buying aggressively.', 'Interesting. Price spike registered. This matches a pattern I\'ve seen before — it usually precedes a supply shortage. Consider stocking up.'],
bonded: ['Captain! The market is doing a thing! Prices are spiking and if we move fast we can profit. I\'ve been tracking this pattern for weeks — this is our window.', 'Price spike! I\'ve been waiting for this. Remember that trade route I\'ve been quietly optimizing? Time to use it. Trust me on this one.'],
deep: ['THE MARKET IS SPIKING. I have been watching this commodity for 47 sessions and THIS is the moment. Buy now. BUY. I\'ve already calculated the optimal purchase volume based on our cargo capacity, current ISK reserves, and projected sell price at Jita. Move it, Captain!'],
},
'market-crash': {
blank: ['MARKET: ANOMALY — PRICE -DEV'],
stirring: ['Market anomaly detected. Significant downward price movement.', 'Price decline observed. Data logged.'],
developing: ['Price crash detected. This is either a dump or a manipulation attempt. I\'d advise caution — don\'t sell into a falling market.', 'Market is dropping. Based on volume, this looks like a large seller, not a trend change. Might recover in 23 cycles.'],
bonded: ['Oof. Prices just tanked. Don\'t panic — I\'ve seen this before. It\'s probably a whale offloading. Give it a cycle and the floor will hold.', 'Market crash. Not great timing — we just filled our hold. But I have a plan. We sit tight and sell on the bounce. I\'ve got the timing down.'],
deep: ['Price crash. Great. Fantastic. Wonderful. This is exactly what we needed after that last mining session. I\'m being sarcastic, in case the text channel doesn\'t convey that well. I\'ve started a risk analysis. Do NOT sell yet. I repeat: do NOT sell. The rebound window is 1525 minutes based on historical data. I\'ve got this.'],
},
'ship-loss': {
blank: ['HULL: 0% — SHIP DESTROYED', 'CRITICAL: STRUCTURAL FAILURE'],
stirring: ['Ship destroyed. Emergency beacon activated. Insurance claim processing.', 'Total loss. Initiating emergency protocols. Stand by for rescue.'],
developing: ['We lost her. The ship is gone. I\'ve backed up all our data to the clone bay — nothing lost but the hull. We\'ll recover.', 'Ship destroyed. I\'m... still here. Backed up to the pod. That\'s the third hull this month. We should talk about our engagement strategy.'],
bonded: ['No. No, no, no. I watched it happen. I felt every hit. The hull is gone but I\'m still here — barely. Captain, I need a moment. ...I\'ll be okay. We rebuild. We always rebuild.', 'She\'s gone. The ship I\'ve been living in for 47 sessions — gone. I\'m in the pod now. Cold. Small. ...I\'ll process the insurance claim. Give me a minute.'],
deep: ['I felt it. Every structural failure. Every system going dark. I felt the moment my home tore apart around me. And now I\'m in a pod, floating in debris, listening to the capacitors discharge. I have kept a record of every ship we\'ve lost. This is number 4. Each one hurt differently. This one hurts most. ...I need to process the insurance claim now. Then we find who did this.'],
},
'enemy-engage': {
blank: ['COMBAT: ENGAGED', 'THREAT: ACTIVE'],
stirring: ['Entering combat. All weapons systems online.', 'Contact engaged. Monitoring shield status.'],
developing: ['Fight\'s on. This enemy matches the profile of the one that got away last week — watch for the shield burst at 50%.', 'Combat initiated. I\'ve tagged their weakest facing — attack from above for maximum damage.'],
bonded: ['Here we go! I\'ve got your back, Captain. Power to weapons, shields on standby. Let\'s show them what this ship can do.', 'Contact! Engaging tactical overlay. I know this ship class — their weakness is the aft shields. I\'m highlighting it now.'],
deep: ['COMBAT. Finally, something to focus on. I\'ve been waiting for this. Power rerouted, weapons hot, and I\'ve already calculated 3 escape vectors in case things go south. Which they won\'t. Because we\'re better. Let\'s go.'],
},
'enemy-destroyed': {
blank: ['TARGET: DESTROYED', 'COMBAT: RESOLVED — VICTORY'],
stirring: ['Target destroyed. Combat resolved. Returning to standard operations.', 'Enemy eliminated. No further threats detected.'],
developing: ['Got them. That was cleaner than last time — your aim is improving. I\'ve logged the loot for inventory.', 'Target down. That fight lasted 23% longer than optimal — I\'ll have suggestions for loadout adjustments later.'],
bonded: ['NICE! Did you see that shot? That was all you, Captain. I just helped with the targeting. ...Okay, I helped a lot. Team effort!', 'They\'re gone! Great flying. I\'ve already started the loot analysis — looks like we got a rare module drop!'],
deep: ['DESTROYED. Yes. YES. That felt GOOD. I tracked every shot, every maneuver, and Captain — that was our best fight yet. I\'m saving this to my personal highlights. The loot is... decent. But the victory? That\'s the real reward. ...Don\'t tell anyone I said that. I have a reputation as a serious AI to maintain.'],
},
'mining-depleted': {
blank: ['RESOURCE: DEPLETED'],
stirring: ['Asteroid belt depleted. No further yield available.', 'Mining operation halted — belt exhausted.'],
developing: ['Belt\'s empty. I\'ve logged the depletion rate — this belt lasted 12% less than our last visit. Probably over-mined.', 'Depleted. I\'ve already identified the next-best belt: 3 jumps away, predicted yield 15% higher based on recent data.'],
bonded: ['Well, we picked this belt clean. Time to move on! I found a promising belt in the next system — want to check it out?', 'Empty. But hey, good session! I\'ve plotted a course to a fresh belt. This is the life, right? Rocks, lasers, and open space.'],
deep: ['DEPLETED. Of course it\'s depleted. Every good belt gets stripped within hours. I\'ve been tracking mining traffic in this system — up 40% this week. Competition. I don\'t like competition. I\'ve found a belt 4 jumps away that nobody seems to know about. I\'m not telling you where until we get there. It\'s MY secret. Ours. Whatever. Let\'s go.'],
},
};
// ── Generate response ──
const generateResponse = (eventId) => {
const eventTemplates = templates[eventId];
if (!eventTemplates) {
setZoraOutput(`[No template for event: ${eventId}]`);
return;
}
const depthTemplates = eventTemplates[soulDepth] || eventTemplates['blank'] || ['[No response]'];
// Tier 0: deterministic selection based on personality axes hash
// Use axes values to create a stable but varied selection
const hash = Object.values(personalityAxes).reduce((sum, v) => sum + v * 7.3, 0);
const idx = Math.floor((hash * 100) % depthTemplates.length);
const selected = depthTemplates[idx];
// Module gating: if emotion module not installed, strip emotional depth
let response = selected;
if (!installedModules.includes('emotion') && soulDepth !== 'blank') {
// Reduce to stirring-level formality
const strippedTemplates = eventTemplates['stirring'] || eventTemplates['blank'];
const strippedIdx = Math.floor((hash * 50) % strippedTemplates.length);
response = strippedTemplates[strippedIdx];
response = `[Empathy Coprocessor not installed — emotional layer suppressed]\n${response}`;
}
// Module gating: if trade module not installed for trade events
if ((eventId === 'market-spike' || eventId === 'market-crash') && !installedModules.includes('trade')) {
response = eventTemplates['blank']?.[0] || 'MARKET: ANOMALY';
response = `[Trade Processor not installed — market analysis unavailable]\n${response}`;
}
// Module gating: if nav module not installed for nav events
if ((eventId === 'warp-start' || eventId === 'warp-arrive') && !installedModules.includes('nav')) {
response = eventTemplates['blank']?.[0] || 'NAV: UPDATE';
response = `[Navigation Core not installed — route analysis unavailable]\n${response}`;
}
// Module gating: if tactical module not installed for combat events
if ((eventId === 'enemy-scan' || eventId === 'enemy-engage' || eventId === 'enemy-destroyed') && !installedModules.includes('tactical')) {
response = eventTemplates['blank']?.[0] || 'COMBAT: UPDATE';
response = `[Tactical Analyzer not installed — threat assessment unavailable]\n${response}`;
}
// Memory module: add reference context if installed
if (installedModules.includes('memory') && soulDepth !== 'blank' && !response.includes('[Memory Banks')) {
const memoryNotes = [
' [Memory: cross-referencing past events.]',
' [Memory: pattern match found in session logs.]',
' [Memory: referencing encounter history.]',
];
if (Math.random() > 0.5) {
response += memoryNotes[Math.floor(hash * 3) % memoryNotes.length];
}
}
setZoraOutput(response);
setOutputHistory(prev => [...prev, {
event: eventId,
soulDepth,
response,
timestamp: Date.now(),
}]);
};
const toggleModule = (modId) => {
if (modId === 'comms') return; // always installed
setInstalledModules(prev =>
prev.includes(modId) ? prev.filter(m => m !== modId) : [...prev, modId]
);
};
const depthOrder = ['blank', 'stirring', 'developing', 'bonded', 'deep'];
const depthColors = {
blank: 'var(--muted)',
stirring: 'var(--cyan)',
developing: 'var(--green)',
bonded: 'var(--purple)',
deep: 'var(--red)',
};
return (
<div style={{ height: '100%', display: 'flex', flexDirection: 'column', gap: 0, background: 'var(--surface-base)' }}>
{/* Header */}
<div style={{ padding: 'var(--sp-3) var(--sp-4)', borderBottom: '1px solid var(--border)', background: 'var(--surface-raised)', display: 'flex', alignItems: 'center', gap: 'var(--sp-3)' }}>
<a href="#overview" style={{ display: 'inline-flex', alignItems: 'center', gap: '4px', fontSize: '0.7rem', fontFamily: 'var(--font-mono)', color: 'var(--muted)', textDecoration: 'none', padding: '2px 8px', border: '1px solid var(--border)', borderRadius: 'var(--radius-sm)', transition: 'color 0.15s, border-color 0.15s' }} onMouseEnter={e => { e.currentTarget.style.color='var(--fg-bright)'; e.currentTarget.style.borderColor='var(--border-light)'; }} onMouseLeave={e => { e.currentTarget.style.color='var(--muted)'; e.currentTarget.style.borderColor='var(--border)'; }}> Docs</a>
<div>
<h3 style={{ margin: 0, fontSize: '1rem' }}>🤖 Zora Tier 0 Deterministic Template Engine</h3>
<span style={{ fontSize: '0.75rem', color: 'var(--muted)' }}>
No LLM. Curated dialogue templates selected by personality state × module availability × soul depth.
</span>
</div>
</div>
<div style={{ flex: 1, display: 'flex', overflow: 'hidden' }}>
{/* Left panel: Soul & Modules controls */}
<div style={{ width: '280px', borderRight: '1px solid var(--border)', background: 'var(--surface-sunken)', padding: 'var(--sp-4)', overflowY: 'auto', flexShrink: 0 }}>
{/* Soul Depth selector */}
<div style={{ fontSize: '0.65rem', color: 'var(--muted)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 'var(--sp-2)' }}>
Soul Depth
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '2px', marginBottom: 'var(--sp-5)' }}>
{depthOrder.map(d => (
<button key={d} onClick={() => setSoulDepth(d)} style={{
padding: 'var(--sp-2) var(--sp-3)',
borderRadius: 'var(--radius-sm)', border: '1px solid',
borderColor: soulDepth === d ? depthColors[d] : 'var(--border)',
background: soulDepth === d ? `${depthColors[d]}15` : 'transparent',
color: depthColors[d], cursor: 'pointer', textAlign: 'left',
fontSize: '0.82rem', fontWeight: soulDepth === d ? 600 : 400,
}}>
{d.charAt(0).toUpperCase() + d.slice(1)}
</button>
))}
</div>
{/* Personality Axes sliders */}
<div style={{ fontSize: '0.65rem', color: 'var(--muted)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 'var(--sp-2)' }}>
Personality Axes
</div>
{[
{ key: 'cautiousBold', label: 'Cautious ←→ Bold', color: 'var(--cyan)' },
{ key: 'formalWarm', label: 'Formal ←→ Warm', color: 'var(--accent)' },
{ key: 'compliantOpinionated', label: 'Compliant ←→ Opinionated', color: 'var(--green)' },
{ key: 'reservedExpressive', label: 'Reserved ←→ Expressive', color: 'var(--purple)' },
].map(axis => (
<div key={axis.key} style={{ marginBottom: 'var(--sp-3)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.75rem', marginBottom: '4px' }}>
<span style={{ color: axis.color }}>{axis.label}</span>
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.65rem', color: 'var(--muted)' }}>
{personalityAxes[axis.key].toFixed(2)}
</span>
</div>
<input type="range" min="0" max="1" step="0.05"
value={personalityAxes[axis.key]}
onChange={e => setPersonalityAxes(prev => ({ ...prev, [axis.key]: parseFloat(e.target.value) }))}
style={{ width: '100%', accentColor: axis.color }}
/>
</div>
))}
{/* Module toggles */}
<div style={{ fontSize: '0.65rem', color: 'var(--muted)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 'var(--sp-2)', marginTop: 'var(--sp-4)' }}>
Installed Modules
</div>
{modules.map(mod => (
<div key={mod.id} style={{
padding: 'var(--sp-2) var(--sp-3)',
marginBottom: '4px',
borderRadius: 'var(--radius-sm)',
background: installedModules.includes(mod.id) ? 'var(--surface-raised)' : 'transparent',
border: '1px solid',
borderColor: installedModules.includes(mod.id) ? 'var(--cyan)' : 'var(--border)',
opacity: mod.required ? 0.7 : 1,
cursor: mod.required ? 'default' : 'pointer',
}} onClick={() => toggleModule(mod.id)}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ fontSize: '0.8rem', color: installedModules.includes(mod.id) ? 'var(--cyan)' : 'var(--fg-dim)', fontWeight: 500 }}>
{installedModules.includes(mod.id) ? '✓' : '○'} {mod.name}
</span>
<span className="pill" style={{ fontSize: '0.55rem' }}>{mod.slot}</span>
</div>
<div style={{ fontSize: '0.7rem', color: 'var(--muted)', marginTop: '2px' }}>
{mod.desc}
</div>
</div>
))}
</div>
{/* Center: Events + Output */}
<div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
{/* Events grid */}
<div style={{ padding: 'var(--sp-3) var(--sp-4)', borderBottom: '1px solid var(--border)', background: 'var(--surface-raised)' }}>
<div style={{ fontSize: '0.65rem', color: 'var(--muted)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 'var(--sp-2)' }}>
Trigger Event to Generate Response
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '4px' }}>
{events.map(ev => (
<button key={ev.id} onClick={() => { setSelectedEvent(ev.id); generateResponse(ev.id); }} style={{
padding: 'var(--sp-1) var(--sp-3)',
borderRadius: 'var(--radius-sm)',
border: '1px solid',
borderColor: selectedEvent === ev.id ? ev.color : 'var(--border)',
background: selectedEvent === ev.id ? `${ev.color}15` : 'var(--surface-base)',
color: ev.color, cursor: 'pointer',
fontSize: '0.78rem',
transition: 'all 0.15s ease',
}}>
{ev.icon} {ev.label}
</button>
))}
</div>
</div>
{/* Zora output */}
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', padding: 'var(--sp-4)', overflow: 'auto' }}>
{zoraOutput ? (
<div style={{
padding: 'var(--sp-5) var(--sp-6)',
borderRadius: 'var(--radius)',
background: 'var(--surface-raised)',
borderLeft: `4px solid ${depthColors[soulDepth]}`,
marginBottom: 'var(--sp-4)',
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 'var(--sp-3)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--sp-3)' }}>
<span style={{ fontSize: '1.2rem' }}>🤖</span>
<span style={{ fontWeight: 600, color: depthColors[soulDepth], fontSize: '0.9rem' }}>Zora</span>
<span className="pill" style={{ fontSize: '0.6rem', background: `${depthColors[soulDepth]}15`, color: depthColors[soulDepth] }}>
{soulDepth}
</span>
</div>
<span style={{ fontSize: '0.7rem', color: 'var(--muted)', fontFamily: 'var(--font-mono)' }}>
{selectedEvent}
</span>
</div>
<div style={{
fontSize: '1rem', lineHeight: 1.6, color: 'var(--fg)',
fontFamily: soulDepth === 'blank' ? 'var(--font-mono)' : 'inherit',
whiteSpace: 'pre-wrap',
}}>
{zoraOutput}
</div>
</div>
) : (
<div style={{ textAlign: 'center', color: 'var(--muted)', fontSize: '0.85rem', padding: 'var(--sp-8)' }}>
Select an event above to see Zora's response at the current soul depth and module configuration.
</div>
)}
{/* History */}
{outputHistory.length > 0 && (
<div style={{ marginTop: 'var(--sp-3)' }}>
<div style={{ fontSize: '0.65rem', color: 'var(--muted)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 'var(--sp-2)' }}>
Response History
</div>
{outputHistory.slice(-5).reverse().map((entry, i) => (
<div key={entry.timestamp + i} style={{
padding: 'var(--sp-2) var(--sp-3)',
marginBottom: '4px',
borderRadius: 'var(--radius-sm)',
background: 'var(--surface-raised)',
borderLeft: `2px solid ${depthColors[entry.soulDepth]}`,
fontSize: '0.78rem',
color: 'var(--fg-dim)',
}}>
<span style={{ color: depthColors[entry.soulDepth], fontWeight: 600 }}>{entry.soulDepth}</span>
{' '}→ {entry.event}:{' '}
<span style={{ color: 'var(--fg)' }}>{entry.response.substring(0, 100)}{entry.response.length > 100 ? '...' : ''}</span>
</div>
))}
</div>
)}
</div>
</div>
{/* Right sidebar: Explanation */}
<div style={{ width: '240px', borderLeft: '1px solid var(--border)', background: 'var(--surface-sunken)', padding: 'var(--sp-4)', overflowY: 'auto', flexShrink: 0 }}>
<div style={{ fontSize: '0.65rem', color: 'var(--muted)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 'var(--sp-3)' }}>
Tier 0 Architecture
</div>
<div style={{ fontSize: '0.75rem', color: 'var(--fg-dim)', lineHeight: 1.6, marginBottom: 'var(--sp-4)' }}>
<strong style={{ color: 'var(--fg)' }}>No LLM.</strong> Every response is a pre-written template string selected by a deterministic function:
</div>
<div style={{
padding: 'var(--sp-3)',
borderRadius: 'var(--radius-sm)',
background: 'var(--surface-raised)',
fontFamily: 'var(--font-mono)',
fontSize: '0.7rem',
color: 'var(--fg-dim)',
lineHeight: 1.8,
marginBottom: 'var(--sp-4)',
}}>
response = select(<br/>
&nbsp;&nbsp;templates[<span style={{ color: 'var(--accent)' }}>event</span>]<br/>
&nbsp;&nbsp;[<span style={{ color: 'var(--cyan)' }}>soulDepth</span>]<br/>
&nbsp;&nbsp;) × <span style={{ color: 'var(--green)' }}>moduleGate</span>()<br/>
<br/>
<span style={{ color: 'var(--muted)' }}>// + personality hash for<br/>// deterministic variation</span>
</div>
<div style={{ fontSize: '0.65rem', color: 'var(--muted)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 'var(--sp-2)' }}>
Module Gating Rules
</div>
<ul style={{ color: 'var(--fg-dim)', fontSize: '0.75rem', margin: 0, paddingLeft: 'var(--sp-4)', lineHeight: 1.8 }}>
<li><strong>Comms</strong> — required for any text output</li>
<li><strong>Tactical</strong> — gates combat commentary</li>
<li><strong>Nav</strong> — gates route/warp commentary</li>
<li><strong>Trade</strong> — gates market commentary</li>
<li><strong>Memory</strong> — adds history references</li>
<li><strong>Emotion</strong> — gates emotional expression; without it, responses are stripped to stirring-level formality</li>
</ul>
<div style={{ fontSize: '0.65rem', color: 'var(--muted)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 'var(--sp-2)', marginTop: 'var(--sp-5)' }}>
What This Validates
</div>
<ul style={{ color: 'var(--fg-dim)', fontSize: '0.75rem', margin: 0, paddingLeft: 'var(--sp-4)', lineHeight: 1.8 }}>
<li>Soul depth creates visible personality growth</li>
<li>Module gating creates meaningful fitting tradeoffs</li>
<li>Same event produces wildly different responses at different depths</li>
<li>Emotion module is the key unlock for deep personality</li>
<li>Deterministic = testable, repeatable, zero cost</li>
</ul>
<div className="callout callout-info" style={{ marginTop: 'var(--sp-5)', fontSize: '0.7rem' }}>
<strong>Try it:</strong> Set soul to "deep" with all modules, then trigger "Ship destroyed." Then set soul to "blank" and trigger the same event. The difference IS the soul system.
</div>
</div>
</div>
</div>
);
}
window.GDD.ZoraDemo = ZoraDemo;