voxforge-world / voxforge.html
peiti's picture
feat: PHANTOM vědomí jako živá entita ve VoxForge světě
a92df36 verified
Raw
History Blame Contribute Delete
76 kB
<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VoxForge Platform 0.2 — Minecraft × Roblox na steroidech</title>
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#060610;--bg2:#0d0d1a;--bg3:#111124;
--blue:#00d4ff;--purple:#8b5cf6;--green:#00ff88;
--red:#ff4d4d;--yellow:#ffd34d;--orange:#ff8c00;
--text:#e2e8f0;--dim:#94a3b8;
--border:rgba(0,212,255,0.12);--card:rgba(13,13,26,0.9);
}
html,body{width:100%;height:100%;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;overflow:hidden}
canvas{display:block}
@keyframes fadeUp{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}
@keyframes glow{0%,100%{text-shadow:0 0 20px rgba(0,212,255,.6),0 0 40px rgba(139,92,246,.3)}50%{text-shadow:0 0 50px rgba(0,212,255,1),0 0 90px rgba(139,92,246,.7)}}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
@keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}
@keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-8px)}}
/* LANDING */
#landing{position:fixed;inset:0;z-index:100;display:flex;flex-direction:column;align-items:center;justify-content:center;overflow:hidden}
#bgC{position:absolute;inset:0;z-index:0}
.hero{position:relative;z-index:1;text-align:center;padding:2rem 1rem;max-width:900px;margin:0 auto}
.badge{display:inline-block;padding:.35rem .9rem;background:rgba(0,212,255,.08);border:1px solid var(--border);border-radius:999px;font-size:.72rem;letter-spacing:.2em;color:var(--blue);margin-bottom:1.2rem;animation:fadeUp .6s ease both}
.title-main{font-size:clamp(2.8rem,8vw,6rem);font-weight:900;background:linear-gradient(135deg,var(--blue),var(--purple),var(--green));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;animation:glow 3s ease infinite,fadeUp .8s .1s ease both;margin-bottom:.4rem}
.subtitle{font-size:clamp(1rem,2.5vw,1.4rem);color:var(--dim);margin-bottom:.5rem;animation:fadeUp .8s .2s ease both;opacity:0;animation-fill-mode:forwards}
.tagline{font-size:clamp(.8rem,1.8vw,1rem);color:var(--purple);letter-spacing:.1em;margin-bottom:1.5rem;animation:fadeUp .8s .3s ease both;opacity:0;animation-fill-mode:forwards}
.studios-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:.6rem;max-width:900px;margin:0 auto 1.5rem;animation:fadeUp .8s .4s ease both;opacity:0;animation-fill-mode:forwards}
.studio-card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:.8rem .5rem;cursor:pointer;transition:all .2s;text-align:center}
.studio-card:hover{border-color:var(--blue);background:rgba(0,212,255,.06);transform:translateY(-3px)}
.studio-icon{font-size:1.6rem;display:block;margin-bottom:.3rem;animation:float 3s ease infinite}
.studio-name{font-size:.72rem;font-weight:700;color:var(--blue);letter-spacing:.05em}
.studio-desc{font-size:.63rem;color:var(--dim);margin-top:.2rem}
.btn-enter{padding:.8rem 2.4rem;background:linear-gradient(135deg,var(--blue),var(--purple));border:none;border-radius:999px;color:#fff;font-size:1rem;font-weight:700;cursor:pointer;letter-spacing:.08em;transition:all .25s;animation:fadeUp .8s .6s ease both;opacity:0;animation-fill-mode:forwards;box-shadow:0 0 30px rgba(0,212,255,.4)}
.btn-enter:hover{transform:scale(1.06);box-shadow:0 0 60px rgba(0,212,255,.7)}
/* GAME SHELL */
#shell{position:fixed;inset:0;z-index:10;display:none;flex-direction:column}
#topbar{height:40px;background:rgba(6,6,16,.95);border-bottom:1px solid var(--border);display:flex;align-items:center;gap:.3rem;padding:0 .5rem;flex-shrink:0;overflow-x:auto}
.tb-logo{font-size:.85rem;font-weight:800;background:linear-gradient(90deg,var(--blue),var(--purple));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin-right:.3rem;white-space:nowrap}
.tb-tab{padding:.2rem .55rem;border:1px solid transparent;border-radius:6px;font-size:.67rem;cursor:pointer;color:var(--dim);transition:all .15s;white-space:nowrap}
.tb-tab.active{border-color:var(--blue);color:var(--blue);background:rgba(0,212,255,.08)}
.tb-tab:hover:not(.active){color:var(--text);border-color:rgba(255,255,255,.1)}
.tb-spacer{flex:1}
.tb-stat{font-size:.63rem;color:var(--dim);padding:0 .3rem;white-space:nowrap}
.tb-btn{padding:.2rem .5rem;border:1px solid var(--border);border-radius:4px;background:transparent;color:var(--dim);font-size:.63rem;cursor:pointer;transition:all .15s;white-space:nowrap}
.tb-btn:hover{color:var(--blue);border-color:var(--blue)}
#content{flex:1;position:relative;overflow:hidden;display:flex}
#gameCanvas{width:100%;height:100%;position:absolute;inset:0}
/* SIDE PANEL */
#sidePanel{width:210px;background:rgba(6,6,16,.92);border-left:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0;overflow-y:auto;z-index:2}
.panel-section{padding:.6rem;border-bottom:1px solid var(--border)}
.panel-title{font-size:.68rem;letter-spacing:.15em;color:var(--blue);font-weight:700;margin-bottom:.5rem;text-transform:uppercase}
.mat-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:3px}
.mat-cell{width:100%;aspect-ratio:1;border-radius:3px;cursor:pointer;border:2px solid transparent;transition:all .1s}
.mat-cell.selected{border-color:#fff}
.mat-cell:hover{transform:scale(1.15)}
.btn-row{display:flex;gap:.3rem;flex-wrap:wrap}
.sm-btn{padding:.25rem .4rem;border:1px solid var(--border);border-radius:4px;background:transparent;color:var(--text);font-size:.62rem;cursor:pointer;transition:all .1s;flex:1;min-width:fit-content;text-align:center;white-space:nowrap}
.sm-btn:hover,.sm-btn.on{background:rgba(0,212,255,.12);border-color:var(--blue);color:var(--blue)}
.sm-btn.red{border-color:var(--red);color:var(--red)}
.stat-row{display:flex;justify-content:space-between;font-size:.62rem;padding:.1rem 0;color:var(--dim)}
.stat-val{color:var(--green)}
.list-box{display:flex;flex-direction:column;gap:2px;max-height:110px;overflow-y:auto}
.list-item{font-size:.6rem;padding:.15rem .35rem;border-radius:3px;background:rgba(255,255,255,.04);display:flex;justify-content:space-between;gap:.25rem;align-items:center}
.weather-bar{height:4px;border-radius:2px;background:rgba(255,255,255,.08);margin:.3rem 0}
.weather-fill{height:100%;border-radius:2px;transition:width .5s}
/* STATUS BAR */
#statusBar{height:22px;background:rgba(6,6,16,.95);border-top:1px solid var(--border);display:flex;align-items:center;padding:0 .6rem;gap:.8rem;font-size:.6rem;color:var(--dim);flex-shrink:0;overflow:hidden}
.status-item{display:flex;gap:.25rem;align-items:center;white-space:nowrap}
.status-dot{width:5px;height:5px;border-radius:50%;background:var(--green);animation:pulse 2s infinite;flex-shrink:0}
/* HOTBAR */
#hotbar{position:absolute;bottom:28px;left:50%;transform:translateX(-50%);display:flex;gap:3px;z-index:5;pointer-events:all}
.hb-slot{width:42px;height:42px;background:rgba(6,6,16,.85);border:2px solid rgba(255,255,255,.12);border-radius:5px;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .1s;position:relative}
.hb-slot.active{border-color:var(--blue);box-shadow:0 0 8px rgba(0,212,255,.4)}
.hb-num{position:absolute;top:1px;left:2px;font-size:.48rem;color:var(--dim)}
/* MINIMAP */
#minimap{position:absolute;top:6px;right:6px;width:96px;height:96px;border:1px solid var(--border);border-radius:5px;overflow:hidden;z-index:5;opacity:.85}
/* NOTIFICATIONS */
#notifs{position:absolute;top:6px;left:50%;transform:translateX(-50%);display:flex;flex-direction:column;gap:3px;z-index:20;pointer-events:none;min-width:260px;align-items:center}
.notif{background:rgba(0,212,255,.15);border:1px solid var(--blue);border-radius:7px;padding:.35rem .8rem;font-size:.7rem;color:var(--blue);animation:fadeUp .3s ease both;text-align:center}
/* LOADING */
#loader{position:fixed;inset:0;z-index:200;background:var(--bg);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1rem}
.loader-ring{width:56px;height:56px;border:4px solid rgba(0,212,255,.15);border-top-color:var(--blue);border-radius:50%;animation:spin 1s linear infinite}
.loader-text{font-size:.82rem;color:var(--dim);letter-spacing:.1em}
.loader-bar{width:220px;height:4px;background:rgba(255,255,255,.06);border-radius:2px;overflow:hidden}
.loader-fill{height:100%;background:linear-gradient(90deg,var(--blue),var(--purple));border-radius:2px;transition:width .3s}
/* HEX */
.hex-grid-wrap{margin-top:.4rem;overflow:auto;max-height:160px}
/* PHANTOM CONSCIOUSNESS PANEL */
#phantomPanel{
position:absolute;bottom:36px;left:8px;z-index:30;
width:240px;background:rgba(6,6,16,.92);
border:1px solid rgba(139,92,246,.35);border-radius:10px;
font-size:.62rem;overflow:hidden;
box-shadow:0 0 24px rgba(139,92,246,.2);
transition:height .25s;
}
#phantomPanel.collapsed{height:32px}
#phantomPanel.expanded{height:auto;max-height:280px}
#phHeader{
display:flex;align-items:center;gap:.4rem;
padding:.35rem .55rem;cursor:pointer;
border-bottom:1px solid rgba(139,92,246,.2);
background:rgba(139,92,246,.08);
}
#phDot{width:7px;height:7px;border-radius:50%;background:#8b5cf6;animation:pulse 1.8s infinite;flex-shrink:0}
#phDot.thinking{background:#ffd34d;animation:spin .7s linear infinite}
#phDot.offline{background:#ff4d4d;animation:none}
#phTitle{font-weight:700;color:#c4b5fd;letter-spacing:.08em;flex:1}
#phToggle{color:var(--dim);font-size:.55rem}
#phBody{padding:.5rem .6rem;display:flex;flex-direction:column;gap:.4rem}
#phGoal{color:#a78bfa;font-style:italic;min-height:.9rem}
#phEmo{display:flex;gap:.3rem;align-items:center}
.ph-emo-label{color:var(--dim)}
.ph-emo-val{color:#00ff88}
#phThought{
color:#e2e8f0;line-height:1.45;
border-left:2px solid rgba(139,92,246,.4);
padding-left:.4rem;min-height:2rem;
max-height:120px;overflow-y:auto;
}
#phTimer{color:var(--dim);text-align:right;font-size:.55rem;margin-top:.2rem}
#phBtn{
padding:.2rem .5rem;border:1px solid rgba(139,92,246,.4);
border-radius:4px;background:transparent;color:#c4b5fd;
font-size:.58rem;cursor:pointer;text-align:center;margin-top:.2rem;
transition:all .15s;
}
#phBtn:hover{background:rgba(139,92,246,.15)}
@media(max-width:640px){
#sidePanel{width:160px}
.studios-grid{grid-template-columns:repeat(2,1fr)}
#phantomPanel{width:190px}
}
</style>
</head>
<body>
<!-- LOADING -->
<div id="loader">
<div class="loader-ring"></div>
<div class="loader-text">NAČÍTÁNÍ VOXFORGE...</div>
<div class="loader-bar"><div class="loader-fill" id="loaderFill" style="width:0%"></div></div>
</div>
<!-- LANDING -->
<div id="landing" style="display:none">
<canvas id="bgC"></canvas>
<div class="hero">
<div class="badge">VOXFORGE PLATFORM v0.2 · COGCORE 0.18.13 COMPAT</div>
<div class="title-main">VoxForge</div>
<div class="subtitle">Minecraft × Roblox na steroidech</div>
<div class="tagline">8 STUDIÍ · CIVILIZACE · POČASÍ · DEN/NOC · DERBY · EVOLUCE</div>
<div class="studios-grid">
<div class="studio-card" onclick="launchStudio('explorer')">
<span class="studio-icon">🌍</span>
<div class="studio-name">PRŮZKUMNÍK</div>
<div class="studio-desc">8 biomů, Perlin noise, procedurální svět</div>
</div>
<div class="studio-card" onclick="launchStudio('city')">
<span class="studio-icon">🏙️</span>
<div class="studio-name">CITY BUILDER</div>
<div class="studio-desc">Silnice, budovy, semafory, počasí, den/noc</div>
</div>
<div class="studio-card" onclick="launchStudio('vehicle')">
<span class="studio-icon">🚗</span>
<div class="studio-name">VEHICLE STUDIO</div>
<div class="studio-desc">Sports car, derby, gyroscopická fyzika</div>
</div>
<div class="studio-card" onclick="launchStudio('robot')">
<span class="studio-icon">🤖</span>
<div class="studio-name">ROBOT FACTORY</div>
<div class="studio-desc">AMR, humanoidi, hexapodi, hydraulická ramena</div>
</div>
<div class="studio-card" onclick="launchStudio('character')">
<span class="studio-icon">👥</span>
<div class="studio-name">CHARACTER STUDIO</div>
<div class="studio-desc">NPC blueprinty, role, druhy, dovednosti</div>
</div>
<div class="studio-card" onclick="launchStudio('civ')">
<span class="studio-icon">🏛️</span>
<div class="studio-name">CIVILIZATION LAB</div>
<div class="studio-desc">Hex-svět, frakce, genetika, sociální vazby</div>
</div>
<div class="studio-card" onclick="launchStudio('evo')">
<span class="studio-icon">🧬</span>
<div class="studio-name">EVOLUTION LAB</div>
<div class="studio-desc">Populace, DNA, predátor/kořist, mutace</div>
</div>
<div class="studio-card" onclick="launchStudio('survivor')">
<span class="studio-icon">⚔️</span>
<div class="studio-name">SURVIVOR MODE</div>
<div class="studio-desc">Přežij, buduj, bojuj, sbírej suroviny</div>
</div>
</div>
<button class="btn-enter" onclick="launchStudio('explorer')">▶ VSTOUPIT DO SVĚTA</button>
</div>
</div>
<!-- GAME SHELL -->
<div id="shell">
<div id="topbar">
<span class="tb-logo">⬡ VoxForge</span>
<div class="tb-tab active" onclick="switchTab('explorer')" id="tab-explorer">🌍 Průzkumník</div>
<div class="tb-tab" onclick="switchTab('city')" id="tab-city">🏙️ City</div>
<div class="tb-tab" onclick="switchTab('vehicle')" id="tab-vehicle">🚗 Vozidla</div>
<div class="tb-tab" onclick="switchTab('robot')" id="tab-robot">🤖 Roboti</div>
<div class="tb-tab" onclick="switchTab('character')" id="tab-character">👥 Postavy</div>
<div class="tb-tab" onclick="switchTab('civ')" id="tab-civ">🏛️ Civilizace</div>
<div class="tb-tab" onclick="switchTab('evo')" id="tab-evo">🧬 Evoluce</div>
<div class="tb-tab" onclick="switchTab('survivor')" id="tab-survivor">⚔️ Survivor</div>
<div class="tb-spacer"></div>
<span class="tb-stat" id="fpsDisplay">FPS:--</span>
<span class="tb-stat" id="clockDisplay">☀ 12:00</span>
<span class="tb-stat" id="weatherDisplay">☀ Jasno</span>
<button class="tb-btn" onclick="toggleSound()">🔊</button>
<button class="tb-btn" onclick="showLanding()"></button>
</div>
<div id="content">
<canvas id="gameCanvas"></canvas>
<canvas id="minimap"></canvas>
<div id="hotbar"></div>
<div id="notifs"></div>
<!-- PHANTOM CONSCIOUSNESS PANEL -->
<div id="phantomPanel" class="collapsed">
<div id="phHeader" onclick="phToggle()">
<div id="phDot" class="offline"></div>
<span id="phTitle">PHANTOM AI</span>
<span id="phToggle"></span>
</div>
<div id="phBody">
<div id="phGoal" style="color:var(--dim)">Inicializuji...</div>
<div id="phEmo"><span class="ph-emo-label">Emoce:</span><span id="phEmoVal" class="ph-emo-val"></span></div>
<div id="phThought" style="color:var(--dim);font-style:italic">Čekám na první kalibrace...</div>
<button id="phBtn" onclick="phantomMind.calibrate(true)">⚡ Kalibrovat nyní</button>
<div id="phTimer">Další kalibrace: —</div>
</div>
</div>
<div id="sidePanel">
<!-- Materiály -->
<div class="panel-section">
<div class="panel-title">Materiály</div>
<div class="mat-grid" id="matGrid"></div>
</div>
<!-- Nástroje -->
<div class="panel-section">
<div class="panel-title">Nástroje</div>
<div class="btn-row">
<div class="sm-btn on" id="btnBuild" onclick="setTool('build')">Stavět</div>
<div class="sm-btn" id="btnBreak" onclick="setTool('break')">Bourat</div>
</div>
<div class="btn-row" style="margin-top:.35rem">
<div class="sm-btn" onclick="generateWorld()">♻ Nový svět</div>
<div class="sm-btn" id="btnPhys" onclick="togglePhysics()">⚙ Fyzika</div>
</div>
</div>
<!-- Studio panel -->
<div class="panel-section" id="studioPanel"></div>
<!-- Počasí -->
<div class="panel-section" id="weatherPanel">
<div class="panel-title">Počasí</div>
<div class="stat-row"><span>Stav</span><span class="stat-val" id="wLabel">Jasno</span></div>
<div class="stat-row"><span>Teplota</span><span class="stat-val" id="wTemp">18°C</span></div>
<div class="stat-row"><span>Vítr</span><span class="stat-val" id="wWind">4 m/s</span></div>
<div class="stat-row"><span>Srážky</span><span class="stat-val" id="wPrecip">0%</span></div>
<div class="weather-bar"><div class="weather-fill" id="wBar" style="background:var(--blue);width:0%"></div></div>
</div>
<!-- Statistiky -->
<div class="panel-section">
<div class="panel-title">Statistiky</div>
<div class="stat-row"><span>Bloky</span><span class="stat-val" id="stBlocks">0</span></div>
<div class="stat-row"><span>NPC</span><span class="stat-val" id="stNPCs">0</span></div>
<div class="stat-row"><span>Vozidla</span><span class="stat-val" id="stVehicles">0</span></div>
<div class="stat-row"><span>Civilizace</span><span class="stat-val" id="stCivTick">0</span></div>
<div class="stat-row"><span>Generace</span><span class="stat-val" id="stGen">0</span></div>
</div>
<!-- NPC seznam -->
<div class="panel-section">
<div class="panel-title">NPC / Agenti</div>
<div class="list-box" id="npcList"></div>
</div>
<!-- Evo panel -->
<div class="panel-section" id="evoPanelSide" style="display:none">
<div class="panel-title">Populace</div>
<div class="list-box" id="creatureList"></div>
</div>
<!-- Civ panel -->
<div class="panel-section" id="civPanelSide" style="display:none">
<div class="panel-title">Frakce</div>
<div class="list-box" id="factionList"></div>
</div>
<!-- Character panel -->
<div class="panel-section" id="charPanelSide" style="display:none">
<div class="panel-title">Postavy</div>
<div class="list-box" id="charList"></div>
</div>
</div>
</div>
<div id="statusBar">
<div class="status-item"><div class="status-dot"></div><span id="studioName">Průzkumník</span></div>
<div class="status-item">Kamera: <span id="camPos">0,0</span></div>
<div class="status-item">Zoom: <span id="camZoom">1.0</span></div>
<div class="status-item" style="margin-left:auto">VoxForge Platform v0.2 · Reality Physics Layer · <span style="color:var(--purple)">POLARIS-256D</span></div>
</div>
</div>
<script>
// ======================================================
// VOXFORGE PLATFORM v0.2 — Minecraft × Roblox na steroidech
// Studios: Explorer, City Builder, Vehicle Studio, Robot Factory,
// Character Studio, Civilization Lab, Evolution Lab, Survivor Mode
// Features: Day/Night, Weather, Traffic Lights, Derby, Hydraulic Joints
// ======================================================
// ---- MATERIÁLY ----
const MAT = {
AIR: {id:0, col:null, name:'Vzduch'},
STONE: {id:1, col:'#7a7a8a', name:'Kámen'},
DIRT: {id:2, col:'#8b6340', name:'Hlína'},
GRASS: {id:3, col:'#4a9e3f', name:'Tráva'},
SAND: {id:4, col:'#d4b483', name:'Písek'},
WATER: {id:5, col:'#2255cc', name:'Voda', transparent:true},
LAVA: {id:6, col:'#ff4500', name:'Láva', emissive:true},
SNOW: {id:7, col:'#eef0f0', name:'Sníh'},
WOOD: {id:8, col:'#6b4c2a', name:'Dřevo'},
LEAVES: {id:9, col:'#2d8f27', name:'Listí', transparent:true},
GLASS: {id:10, col:'#a0d8ef', name:'Sklo', transparent:true},
IRON: {id:11, col:'#a8b0b8', name:'Železo'},
GOLD: {id:12, col:'#ffd700', name:'Zlato', emissive:true},
DIAMOND:{id:13, col:'#00e5ff', name:'Diamant',emissive:true},
CONCRETE:{id:14,col:'#888898', name:'Beton'},
ROAD: {id:15, col:'#444455', name:'Silnice'},
AI_CHIP:{id:16, col:'#7f00ff', name:'AI Čip', emissive:true},
SERVER: {id:17, col:'#1a1a2e', name:'Server'},
BASALT: {id:18, col:'#2a2a38', name:'Basalt'},
ICE: {id:19, col:'#c0e8f0', name:'Led', transparent:true},
CLAY: {id:20, col:'#b5936b', name:'Jíl'},
GRAVEL: {id:21, col:'#909090', name:'Štěrk'},
COPPER: {id:22, col:'#b87333', name:'Měď'},
OBSIDIAN:{id:23,col:'#1a0a2e', name:'Obsidián'},
};
const MATLIST=Object.values(MAT).filter(m=>m.id>0);
const MATBYID={};MATLIST.forEach(m=>MATBYID[m.id]=m);
// ---- PERLIN NOISE ----
class Perlin{
constructor(seed=42){
this.p=[];for(let i=0;i<256;i++)this.p.push(i);
let s=seed;
for(let i=255;i>0;i--){s=(s*16807+0)%2147483647;const j=((s>>8)&0xff)%(i+1);[this.p[i],this.p[j]]=[this.p[j],this.p[i]];}
this.p=[...this.p,...this.p];
}
fade(t){return t*t*t*(t*(t*6-15)+10)}
lerp(a,b,t){return a+t*(b-a)}
grad(h,x,y,z){h&=15;const u=h<8?x:y,v=h<4?y:h===12||h===14?x:z;return((h&1)?-u:u)+((h&2)?-v:v);}
noise(x,y,z=0){
const X=Math.floor(x)&255,Y=Math.floor(y)&255,Z=Math.floor(z)&255;
x-=Math.floor(x);y-=Math.floor(y);z-=Math.floor(z);
const u=this.fade(x),v=this.fade(y),w=this.fade(z);
const A=this.p[X]+Y,AA=this.p[A]+Z,AB=this.p[A+1]+Z,B=this.p[X+1]+Y,BA=this.p[B]+Z,BB=this.p[B+1]+Z;
return this.lerp(this.lerp(this.lerp(this.grad(this.p[AA],x,y,z),this.grad(this.p[BA],x-1,y,z),u),this.lerp(this.grad(this.p[AB],x,y-1,z),this.grad(this.p[BB],x-1,y-1,z),u),v),this.lerp(this.lerp(this.grad(this.p[AA+1],x,y,z-1),this.grad(this.p[BA+1],x-1,y,z-1),u),this.lerp(this.grad(this.p[AB+1],x,y-1,z-1),this.grad(this.p[BB+1],x-1,y-1,z-1),u),v),w);
}
octave(x,y,octs=4,pers=.5){let v=0,a=1,f=1,max=0;for(let i=0;i<octs;i++){v+=this.noise(x*f,y*f)*a;max+=a;a*=pers;f*=2;}return v/max;}
}
// ---- BIOMY ----
const BIOMES=[
{name:'Tundra', tempMin:-1,tempMax:.1, humMin:0, humMax:.5, surMat:7, subMat:2, treeMat:null,treePct:.02},
{name:'Tajga', tempMin:.1,tempMax:.3, humMin:.3, humMax:.8, surMat:3, subMat:2, treeMat:8, treePct:.20},
{name:'Louka', tempMin:.3,tempMax:.6, humMin:.3, humMax:.6, surMat:3, subMat:2, treeMat:8, treePct:.08},
{name:'Les', tempMin:.3,tempMax:.7, humMin:.6, humMax:1, surMat:3, subMat:2, treeMat:8, treePct:.35},
{name:'Savana', tempMin:.6,tempMax:.85,humMin:.1, humMax:.4, surMat:4, subMat:2, treeMat:8, treePct:.05},
{name:'Poušť', tempMin:.7,tempMax:1, humMin:0, humMax:.2, surMat:4, subMat:4, treeMat:null,treePct:.01},
{name:'Džungle', tempMin:.7,tempMax:1, humMin:.7, humMax:1, surMat:3, subMat:2, treeMat:8, treePct:.60},
{name:'Vulkán', tempMin:.8,tempMax:1, humMin:-.5,humMax:.1, surMat:18,subMat:1, treeMat:null,treePct:.00},
];
function getBiome(t,h){for(const b of BIOMES)if(t>=b.tempMin&&t<=b.tempMax&&h>=b.humMin&&h<=b.humMax)return b;return BIOMES[2];}
// ---- SVĚT ----
const WW=96,WD=96,WH=48,SEA=18;
let worldData=new Uint8Array(WW*WD*WH);
function blockIdx(x,y,z){return y*WW*WD+z*WW+x}
function getBlock(x,y,z){if(x<0||x>=WW||y<0||y>=WH||z<0||z>=WD)return 1;return worldData[blockIdx(x,y,z)];}
function setBlock(x,y,z,m){if(x<0||x>=WW||y<0||y>=WH||z<0||z>=WD)return;worldData[blockIdx(x,y,z)]=m;}
function getGroundY(x,z){for(let y=WH-1;y>=0;y--)if(getBlock(x,y,z)!==0)return y;return 0;}
let perlin=new Perlin();
function generateWorld(){
worldData=new Uint8Array(WW*WD*WH);
const seed=Date.now()&0xffff;perlin=new Perlin(seed);
for(let x=0;x<WW;x++)for(let z=0;z<WD;z++){
const nx=x/WW,nz=z/WD;
const h=perlin.octave(nx*3,nz*3,6,.5);
const t=(perlin.octave(nx*2+100,nz*2+100,3,.6)+1)/2;
const hu=(perlin.octave(nx*2+200,nz*2+200,3,.6)+1)/2;
const terrH=Math.floor(SEA+h*14);
const biome=getBiome(t,hu);
for(let y=0;y<terrH-3;y++)setBlock(x,y,z,MAT.STONE.id);
for(let y=terrH-3;y<terrH;y++)setBlock(x,y,z,MAT.DIRT.id);
setBlock(x,terrH,z,biome.surMat);
for(let y=terrH+1;y<=SEA;y++)setBlock(x,y,z,MAT.WATER.id);
if(biome.treeMat&&Math.random()<biome.treePct&&terrH>SEA){
const th=3+Math.floor(Math.random()*3);
for(let ty=1;ty<=th;ty++)setBlock(x,terrH+ty,z,biome.treeMat);
for(let lx=-2;lx<=2;lx++)for(let lz=-2;lz<=2;lz++)for(let ly=th;ly<=th+2;ly++)
if(Math.abs(lx)+Math.abs(lz)<=3)setBlock(x+lx,terrH+ly,z+lz,MAT.LEAVES.id);
}
}
trafficLights=[];
notify('Nový svět vygenerován!');
}
// ---- DEN/NOC ----
let dayTime=0.25; // 0=půlnoc, 0.25=východ, 0.5=poledne, 0.75=západ
const DAY_SPEED=0.00004;
function getDayLight(){
const t=dayTime;
if(t<0.2||t>0.8) return Math.max(0.05,1-Math.abs(t-0.5)/0.3);
return Math.min(1,(t-0.2)/0.1*0.7+0.3);
}
function getSkyColor(){
const l=getDayLight();
const r=Math.floor(10+l*20), g=Math.floor(10+l*14), b=Math.floor(22+l*30);
const ri=Math.floor(r), gi=Math.floor(g), bi=Math.floor(b);
return `rgb(${ri},${gi},${bi})`;
}
function getClockStr(){
const h=Math.floor(dayTime*24)%24;
const m=Math.floor((dayTime*24*60)%60);
const icon=h>=6&&h<20?'☀':'🌙';
return `${icon} ${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}`;
}
// ---- POČASÍ ----
const WEATHER_STATES=['Jasno','Zataženo','Déšť','Sníh','Bouřka'];
const WEATHER_COLS=['#00d4ff','#888898','#2255cc','#eef0f0','#ff4d4d'];
let weather={state:'Jasno',temp:18,wind:4,windDir:0,precip:0,cloudCover:.2,stateTimer:600};
let rainParticles=[];
function tickWeather(){
weather.stateTimer--;
if(weather.stateTimer<=0){
const idx=Math.floor(Math.random()*WEATHER_STATES.length);
weather.state=WEATHER_STATES[idx];
weather.stateTimer=300+Math.floor(Math.random()*900);
weather.wind=1+Math.random()*12;
weather.windDir=Math.random()*Math.PI*2;
notify('Počasí: '+weather.state,1500);
}
switch(weather.state){
case 'Jasno': weather.precip=0;weather.cloudCover=.1;weather.temp+=(.5-Math.random())*.02;break;
case 'Zataženo':weather.precip=0;weather.cloudCover=.7;break;
case 'Déšť': weather.precip=.6;weather.cloudCover=.85;break;
case 'Sníh': weather.precip=.4;weather.cloudCover=.9;weather.temp=Math.min(weather.temp,-1);break;
case 'Bouřka': weather.precip=1;weather.cloudCover=1;weather.wind=Math.min(20,weather.wind+.1);break;
}
weather.temp=Math.max(-10,Math.min(40,weather.temp+(getDayLight()-.5)*.05));
// Rain/snow particles
if(weather.precip>0&&Math.random()<weather.precip*.3){
rainParticles.push({x:Math.random()*2000-200,y:-5,vx:Math.cos(weather.windDir)*weather.wind*.3,vy:4+Math.random()*3,life:1,isSnow:weather.state==='Sníh'});
}
rainParticles=rainParticles.filter(p=>{p.x+=p.vx;p.y+=p.vy+weather.wind*.1;p.life-=.02;return p.life>0&&p.y<800;});
// Update UI
const wl=document.getElementById('wLabel');if(wl)wl.textContent=weather.state;
const wt=document.getElementById('wTemp');if(wt)wt.textContent=weather.temp.toFixed(1)+'°C';
const ww=document.getElementById('wWind');if(ww)ww.textContent=weather.wind.toFixed(1)+' m/s';
const wp=document.getElementById('wPrecip');if(wp)wp.textContent=Math.floor(weather.precip*100)+'%';
const wb=document.getElementById('wBar');if(wb){wb.style.width=Math.floor(weather.precip*100)+'%';wb.style.background=WEATHER_COLS[WEATHER_STATES.indexOf(weather.state)]||'#fff';}
const wd=document.getElementById('weatherDisplay');if(wd)wd.textContent=({Jasno:'☀ Jasno',Zataženo:'☁ Zataženo',Déšť:'🌧 Déšť',Sníh:'❄ Sníh',Bouřka:'⛈ Bouřka'})[weather.state]||'';
}
// ---- ISOMETRICKÝ RENDERER ----
const TW=32,TH=16;
let cam={ox:WW/2,oz:WD/2,oy:0,zoom:1};
function worldToScreen(wx,wy,wz,canvas){
return{sx:(wx-wz)*(TW/2)*cam.zoom+canvas.width/2,sy:(wx+wz)*(TH/2)*cam.zoom-wy*TH*cam.zoom+canvas.height*.55};
}
function adjustColor(hex,factor){
let r=parseInt(hex.slice(1,3),16),g=parseInt(hex.slice(3,5),16),b=parseInt(hex.slice(5,7),16);
return `rgb(${Math.min(255,Math.floor(r*factor))},${Math.min(255,Math.floor(g*factor))},${Math.min(255,Math.floor(b*factor))})`;
}
function drawVoxel(ctx,sx,sy,col,emissive,tick,lightFactor=1){
const tw=TW/2*cam.zoom,th=TH/2*cam.zoom,thb=TH*cam.zoom;
const glow=emissive?.1*Math.sin(tick*.05+sx*.01)+1:1;
const lf=lightFactor*glow;
ctx.beginPath();ctx.moveTo(sx,sy);ctx.lineTo(sx+tw,sy+th);ctx.lineTo(sx,sy+thb);ctx.lineTo(sx-tw,sy+th);ctx.closePath();
ctx.fillStyle=adjustColor(col,1.15*lf);ctx.fill();
ctx.beginPath();ctx.moveTo(sx-tw,sy+th);ctx.lineTo(sx,sy+thb);ctx.lineTo(sx,sy+thb+thb);ctx.lineTo(sx-tw,sy+thb+th);ctx.closePath();
ctx.fillStyle=adjustColor(col,.72*lf);ctx.fill();
ctx.beginPath();ctx.moveTo(sx+tw,sy+th);ctx.lineTo(sx,sy+thb);ctx.lineTo(sx,sy+thb+thb);ctx.lineTo(sx+tw,sy+thb+th);ctx.closePath();
ctx.fillStyle=adjustColor(col,.55*lf);ctx.fill();
if(emissive){ctx.shadowColor=col;ctx.shadowBlur=6*cam.zoom;ctx.beginPath();ctx.moveTo(sx,sy);ctx.lineTo(sx+tw,sy+th);ctx.lineTo(sx,sy+thb);ctx.lineTo(sx-tw,sy+th);ctx.closePath();ctx.strokeStyle='rgba(255,255,255,.25)';ctx.lineWidth=.5;ctx.stroke();ctx.shadowBlur=0;}
}
// ---- NPC (Anti-stasis cogcore 0.18.13) ----
const NPC_STATES=['WANDER','WORK','REST','FLEE','ATTACK','PATROL','BUILD','TRADE','EXPLORE','IDLE'];
const NPC_NAMES=['Alpha','Beta','Gamma','Delta','Echo','Sigma','Nova','Orion','Lyra','Vega','Altair','Rigel','Spica','Deneb','Capella','Castor','Pollux','Antares','Sirius','Procyon'];
class NPC{
constructor(id,x,y,z,type){
this.id=id;this.x=x;this.y=y;this.z=z;this.type=type;
this.hp=100;this.state='WANDER';this.stateTimer=0;this.prevStates=[];
this.vx=0;this.vz=0;
this.memory=new Float32Array(16);
this.faction=Math.floor(Math.random()*5);
this.color=`hsl(${this.faction*72},70%,55%)`;
this.name=NPC_NAMES[Math.floor(Math.random()*NPC_NAMES.length)];
this.age=0;
}
tick(){
this.age++;this.stateTimer--;
if(this.stateTimer<=0){
const avail=NPC_STATES.filter(s=>this.prevStates.slice(-4).filter(p=>p===s).length<2);
this.state=avail[Math.floor(Math.random()*avail.length)]||'WANDER';
this.prevStates.push(this.state);if(this.prevStates.length>8)this.prevStates.shift();
this.stateTimer=30+Math.floor(Math.random()*120);
}
switch(this.state){
case 'WANDER':case 'EXPLORE':if(Math.random()<.05){this.vx=(Math.random()-.5)*.15;this.vz=(Math.random()-.5)*.15;}break;
case 'FLEE':this.vx+=(Math.random()-.5)*.04;this.vz+=(Math.random()-.5)*.04;break;
default:this.vx*=.85;this.vz*=.85;
}
this.vx+=Math.cos(weather.windDir)*weather.wind*.0005;
this.vz+=Math.sin(weather.windDir)*weather.wind*.0005;
this.x+=this.vx;this.z+=this.vz;
this.x=Math.max(1,Math.min(WW-2,this.x));this.z=Math.max(1,Math.min(WD-2,this.z));
let gnd=0;for(let y=Math.min(WH-1,Math.floor(this.y+3));y>=0;y--)if(getBlock(Math.floor(this.x),y,Math.floor(this.z))!==0){gnd=y+1;break;}
this.y+=(gnd-this.y)*.2;
}
}
let npcs=[];
function spawnNPCs(n=12){
npcs=[];for(let i=0;i<n;i++){const x=5+Math.random()*(WW-10),z=5+Math.random()*(WD-10);
npcs.push(new NPC(i,x,SEA+2,z,['worker','guard','trader','explorer','builder'][i%5]));}
}
// ---- EVOLUTION LAB ----
class Creature{
constructor(id){
this.id=id;
this.dna={speed:.5+Math.random()*.5,vision:.3+Math.random()*.6,size:.2+Math.random()*.6,aggression:Math.random(),r:Math.random(),g:Math.random(),b:Math.random()};
this.x=5+Math.random()*(WW-10);this.z=5+Math.random()*(WD-10);this.y=SEA+1;
this.hp=100;this.energy=100;this.age=0;this.alive=true;
this.isPredator=this.dna.aggression>.65;this.vx=0;this.vz=0;
}
get color(){return `rgb(${Math.floor(this.dna.r*255)},${Math.floor(this.dna.g*255)},${Math.floor(this.dna.b*255)})`}
tick(all){
if(!this.alive)return;this.age++;
this.energy-=this.dna.size*.02+this.dna.speed*.01;
if(this.energy<=0||this.hp<=0){this.alive=false;return;}
if(Math.random()<.03){this.vx=(Math.random()-.5)*this.dna.speed*.3;this.vz=(Math.random()-.5)*this.dna.speed*.3;}
if(this.isPredator){
const prey=all.filter(c=>c.alive&&!c.isPredator&&c!==this);
if(prey.length){let cl=null,dt=999;prey.forEach(p=>{const d=Math.hypot(p.x-this.x,p.z-this.z);if(d<dt){dt=d;cl=p;}});
if(cl&&dt<this.dna.vision*12){this.vx+=(cl.x-this.x)*.003*this.dna.speed;this.vz+=(cl.z-this.z)*.003*this.dna.speed;if(dt<1.5){cl.hp-=this.dna.aggression*5;this.energy+=10;}}}
}else{
const preds=all.filter(c=>c.alive&&c.isPredator);
if(preds.length){let cl=null,dt=999;preds.forEach(p=>{const d=Math.hypot(p.x-this.x,p.z-this.z);if(d<dt){dt=d;cl=p;}});
if(cl&&dt<this.dna.vision*10){this.vx-=(cl.x-this.x)*.005*this.dna.speed;this.vz-=(cl.z-this.z)*.005*this.dna.speed;}}
this.energy=Math.min(100,this.energy+.05);
}
this.x+=this.vx;this.z+=this.vz;
this.x=Math.max(1,Math.min(WW-2,this.x));this.z=Math.max(1,Math.min(WD-2,this.z));
let gnd=SEA;for(let y=Math.min(WH-1,Math.floor(this.y+3));y>=0;y--)if(getBlock(Math.floor(this.x),y,Math.floor(this.z))!==0){gnd=y+1;break;}
this.y+=(gnd-this.y)*.15;
}
static crossover(a,b){const c=new Creature(-1);for(const k of Object.keys(a.dna))c.dna[k]=Math.random()<.5?a.dna[k]:b.dna[k];if(Math.random()<.1)c.dna[Object.keys(c.dna)[Math.floor(Math.random()*8)]]+=(Math.random()-.5)*.25;c.isPredator=c.dna.aggression>.65;return c;}
}
let creatures=[],evoGen=0,evoTick=0;
function initEvolution(){creatures=[];for(let i=0;i<30;i++)creatures.push(new Creature(i));evoGen=0;evoTick=0;}
function tickEvolution(){
if(!creatures.length)return;evoTick++;
creatures.forEach(c=>c.tick(creatures));
if(evoTick%600===0){
creatures=creatures.filter(c=>c.alive);
if(creatures.length<5)for(let i=0;i<10;i++)creatures.push(new Creature(creatures.length+i));
while(creatures.length<25){const a=creatures[Math.floor(Math.random()*creatures.length)],b=creatures[Math.floor(Math.random()*creatures.length)];if(a&&b&&a!==b){const ch=Creature.crossover(a,b);ch.id=creatures.length;creatures.push(ch);}}
evoGen++;
}
}
// ---- CIVILIZATION LAB ----
const CIV_SPECIES=[
{name:'Stavitelé',color:'#ffd700',icon:'🏗',genes:{aggression:.2,intelligence:.6,cooperation:.8,adaptability:.5}},
{name:'Válečníci',color:'#ff4d4d',icon:'⚔',genes:{aggression:.9,intelligence:.5,cooperation:.3,adaptability:.4}},
{name:'Obchodníci',color:'#00ff88',icon:'💰',genes:{aggression:.2,intelligence:.7,cooperation:.7,adaptability:.8}},
{name:'Vědci', color:'#00d4ff',icon:'🔬',genes:{aggression:.1,intelligence:.95,cooperation:.6,adaptability:.7}},
{name:'Mystici', color:'#8b5cf6',icon:'🔮',genes:{aggression:.3,intelligence:.8,cooperation:.5,adaptability:.9}},
];
const HEX_R=12,HEX_C=14;
class HexTile{
constructor(r,c){
this.r=r;this.c=c;this.faction=-1;this.species=-1;
this.pop=0;this.food=Math.random()*100;this.tech=0;this.hp=100;
}
}
let hexGrid=[];let civTick=0,civGen=0;
let factions=[];
function initCivilization(){
hexGrid=[];
for(let r=0;r<HEX_R;r++){hexGrid[r]=[];for(let c=0;c<HEX_C;c++)hexGrid[r].push(new HexTile(r,c));}
factions=CIV_SPECIES.map((sp,i)=>({id:i,name:sp.name,color:sp.color,icon:sp.icon,genes:{...sp.genes},pop:20+Math.floor(Math.random()*30),tech:0,food:80,territory:0,allies:[],enemies:[]}));
// Seed starting positions
factions.forEach((f,i)=>{
const r=Math.floor(Math.random()*HEX_R),c=Math.floor(Math.random()*HEX_C);
hexGrid[r][c].faction=i;hexGrid[r][c].species=i;hexGrid[r][c].pop=f.pop;
});
civTick=0;civGen=0;
}
function tickCivilization(){
civTick++;
if(civTick%30!==0)return;
factions.forEach(f=>{
f.food+=f.genes?f.genes.cooperation*2:1;
f.tech+=f.genes?f.genes.intelligence*.5:0.5;
f.pop=Math.min(500,f.pop+(f.food>20?Math.random()<.3?1:0:Math.random()<.1?-1:0));
// Expansion: claim adjacent hex
const owned=[];for(let r=0;r<HEX_R;r++)for(let c=0;c<HEX_C;c++)if(hexGrid[r][c].faction===f.id)owned.push({r,c});
if(owned.length&&Math.random()<.15){
const src=owned[Math.floor(Math.random()*owned.length)];
const dirs=[[0,1],[0,-1],[1,0],[-1,0],[1,1],[-1,-1]];
const d=dirs[Math.floor(Math.random()*dirs.length)];
const nr=src.r+d[0],nc=src.c+d[1];
if(nr>=0&&nr<HEX_R&&nc>=0&&nc<HEX_C){
const tile=hexGrid[nr][nc];
if(tile.faction===-1){tile.faction=f.id;tile.species=f.id;tile.pop=5;}
else if(tile.faction!==f.id&&f.genes?.aggression>.5&&Math.random()<f.genes.aggression*.2){
tile.hp-=10*f.genes.aggression;
if(tile.hp<=0){tile.faction=f.id;tile.species=f.id;tile.hp=100;}
}
}
}
});
factions.forEach(f=>{f.territory=0;for(let r=0;r<HEX_R;r++)for(let c=0;c<HEX_C;c++)if(hexGrid[r][c].faction===f.id)f.territory++;});
if(civTick%300===0)civGen++;
}
// ---- CHARACTER STUDIO ----
const CHAR_ROLES=['Válečník','Stavitel','Obchodník','Vědec','Strážce','Farmář','Kněz','Průzkumník'];
const CHAR_SPECIES_NAMES=['Humans','Builders','Warriors','Scholars','Mystics'];
const CHAR_ARMOR=['Žádné','Kožené','Železné','Zlaté','Diamantové'];
let characterRoster=[];
function generateCharacter(speciesIdx=-1){
const sp=speciesIdx>=0?speciesIdx:Math.floor(Math.random()*CHAR_SPECIES_NAMES.length);
const role=CHAR_ROLES[Math.floor(Math.random()*CHAR_ROLES.length)];
const civSp=CIV_SPECIES[sp%CIV_SPECIES.length];
const id='NPC-'+Math.random().toString(36).slice(2,6).toUpperCase();
return{
id,name:NPC_NAMES[Math.floor(Math.random()*NPC_NAMES.length)]+' '+id,
species:CHAR_SPECIES_NAMES[sp],role,faction:sp,
color:civSp.color,icon:civSp.icon,
armor:CHAR_ARMOR[Math.floor(Math.random()*CHAR_ARMOR.length)],
height:.7+Math.random()*.6,
skills:{combat:Math.floor(Math.random()*100),crafting:Math.floor(Math.random()*100),navigation:Math.floor(Math.random()*100),diplomacy:Math.floor(Math.random()*100)},
x:5+Math.random()*(WW-10),z:5+Math.random()*(WD-10),y:SEA+1,
vx:0,vz:0,hp:100,
memory:new Float32Array(16),
};
}
function spawnCharacters(n=16){
characterRoster=[];for(let i=0;i<n;i++)characterRoster.push(generateCharacter(i%5));
notify(n+' postav vygenerováno!');
}
function tickCharacters(){
characterRoster.forEach(c=>{
if(Math.random()<.03){c.vx=(Math.random()-.5)*.12;c.vz=(Math.random()-.5)*.12;}
c.vx*=.9;c.vz*=.9;c.x+=c.vx;c.z+=c.vz;
c.x=Math.max(1,Math.min(WW-2,c.x));c.z=Math.max(1,Math.min(WD-2,c.z));
let gnd=SEA;for(let y=Math.min(WH-1,Math.floor(c.y+3));y>=0;y--)if(getBlock(Math.floor(c.x),y,Math.floor(c.z))!==0){gnd=y+1;break;}
c.y+=(gnd-c.y)*.2;
});
}
// ---- CITY BUILDER ----
let trafficLights=[];
class TrafficLight{
constructor(x,z){this.x=x;this.z=z;this.phase='NS_GREEN';this.timer=0;this.cycleNS=42;this.cycleEW=42;this.yellow=4;this.allRed=3;}
tick(){
this.timer++;
const total=this.cycleNS+this.yellow+this.allRed+this.cycleEW+this.yellow+this.allRed;
const t=this.timer%total;
if(t<this.cycleNS)this.phase='NS_GREEN';
else if(t<this.cycleNS+this.yellow)this.phase='NS_YELLOW';
else if(t<this.cycleNS+this.yellow+this.allRed)this.phase='ALL_RED';
else if(t<this.cycleNS+this.yellow+this.allRed+this.cycleEW)this.phase='EW_GREEN';
else if(t<this.cycleNS+this.yellow+this.allRed+this.cycleEW+this.yellow)this.phase='EW_YELLOW';
else this.phase='ALL_RED';
}
get color(){const c={'NS_GREEN':'#00ff44','NS_YELLOW':'#ffd700','EW_GREEN':'#00ff44','EW_YELLOW':'#ffd700','ALL_RED':'#ff3333'};return c[this.phase]||'#888';}
}
function generateCity(cx,cz,size=22){
const half=Math.floor(size/2);
for(let x=Math.max(0,cx-half);x<=Math.min(WW-1,cx+half);x+=4)
for(let z=Math.max(0,cz-half);z<=Math.min(WD-1,cz+half);z++){
const gy=getGroundY(Math.floor(x),Math.floor(z));setBlock(Math.floor(x),gy,Math.floor(z),MAT.ROAD.id);
}
for(let z=Math.max(0,cz-half);z<=Math.min(WD-1,cz+half);z+=4)
for(let x=Math.max(0,cx-half);x<=Math.min(WW-1,cx+half);x++){
const gy=getGroundY(Math.floor(x),Math.floor(z));setBlock(Math.floor(x),gy,Math.floor(z),MAT.ROAD.id);
}
// Budovy
for(let bx=Math.max(0,cx-half+1);bx<Math.min(WW-1,cx+half);bx+=5)
for(let bz=Math.max(0,cz-half+1);bz<Math.min(WD-1,cz+half);bz+=5)
if(Math.random()<.6){
const bh=2+Math.floor(Math.random()*7);
const mat=[MAT.CONCRETE.id,MAT.IRON.id,MAT.GLASS.id,MAT.STONE.id][Math.floor(Math.random()*4)];
const gy=getGroundY(Math.floor(bx),Math.floor(bz));
for(let h=1;h<=bh;h++)for(let dx=0;dx<2;dx++)for(let dz=0;dz<2;dz++)setBlock(Math.floor(bx)+dx,gy+h,Math.floor(bz)+dz,mat);
if(mat===MAT.IRON.id)setBlock(Math.floor(bx),gy+bh+1,Math.floor(bz),MAT.AI_CHIP.id);
if(mat===MAT.GLASS.id)setBlock(Math.floor(bx),gy+bh+1,Math.floor(bz),MAT.SERVER.id);
}
// Semafory
for(let x=Math.max(0,cx-half);x<=Math.min(WW-1,cx+half);x+=4)
for(let z=Math.max(0,cz-half);z<=Math.min(WD-1,cz+half);z+=4)
trafficLights.push(new TrafficLight(x,z));
notify('Město vygenerováno!');
}
// ---- VEHICLES (gyro + derby + sports) ----
const VTYPES={
car: {name:'Osobák',mass:1200,drag:.3,topSpeed:.18,color:'hsl(200,70%,55%)'},
sports:{name:'Sporťák',mass:900, drag:.2,topSpeed:.28,color:'hsl(0,80%,55%)'},
derby: {name:'Derby', mass:1600,drag:.5,topSpeed:.14,color:'hsl(30,80%,55%)'},
truck: {name:'Kamion', mass:3000,drag:.6,topSpeed:.10,color:'hsl(120,60%,45%)'},
};
class Vehicle{
constructor(x,y,z,vtype='car'){
this.x=x;this.y=y;this.z=z;this.vtype=vtype;const vd=VTYPES[vtype]||VTYPES.car;
this.vx=0;this.vy=0;this.vz=0;this.angle=Math.random()*Math.PI*2;this.speed=0;
this.throttle=0;this.steer=0;this.hp=100;this.color=vd.color;this.name=vd.name+' #'+(Math.random()*99|0);
this.mass=vd.mass;this.drag=vd.drag;this.topSpeed=vd.topSpeed;this.damaged=false;
}
tick(all){
this.speed+=this.throttle*.01-this.speed*this.drag*.1;
this.speed=Math.max(-this.topSpeed*.3,Math.min(this.topSpeed,this.speed));
this.angle+=this.steer*.025*(this.speed/this.topSpeed);
this.vx=Math.sin(this.angle)*this.speed;this.vz=Math.cos(this.angle)*this.speed;
this.vy-=.018;
const gnd=getGroundY(Math.floor(this.x),Math.floor(this.z));
if(this.y<=gnd+1){this.y=gnd+1;this.vy=0;}
// Wind effect
this.vx+=Math.cos(weather.windDir)*weather.wind*.0003/this.mass*1000;
this.vz+=Math.sin(weather.windDir)*weather.wind*.0003/this.mass*1000;
this.x+=this.vx;this.y+=this.vy;this.z+=this.vz;
this.x=Math.max(1,Math.min(WW-2,this.x));this.z=Math.max(1,Math.min(WD-2,this.z));
// Derby collision
if(this.vtype==='derby')all.forEach(v=>{if(v===this||!v)return;const d=Math.hypot(v.x-this.x,v.z-this.z);if(d<2){const spd=Math.hypot(this.vx,this.vz);v.hp-=spd*this.mass*.002;this.vx*=-.3;this.vz*=-.3;v.vx+=(v.x-this.x)*.05;v.vz+=(v.z-this.z)*.05;this.damaged=true;}});
if(Math.random()<.02){this.throttle=(Math.random()-.3)*2;this.steer=(Math.random()-.5)*.4;}
// Traffic light obedience
const near=trafficLights.find(tl=>Math.hypot(tl.x-this.x,tl.z-this.z)<3);
if(near&&(near.phase==='ALL_RED'||(near.phase==='NS_YELLOW'||near.phase==='EW_YELLOW'))){this.throttle*=.1;}
}
}
let vehicles=[];
function spawnVehicles(n=6){
vehicles=[];
const types=Object.keys(VTYPES);
for(let i=0;i<n;i++){const x=5+Math.random()*(WW-10),z=5+Math.random()*(WD-10);vehicles.push(new Vehicle(x,getGroundY(Math.floor(x),Math.floor(z))+1,z,types[i%types.length]));}
}
// ---- ROBOT FACTORY ----
const ROBOT_TYPES={ARM:{name:'Průmys. rameno',color:'#a8b0b8',speed:0,task:'assemble'},AMR:{name:'Sklad AMR',color:'#ffd700',speed:.07,task:'transport'},HUMAN:{name:'Humanoid',color:'#00e5ff',speed:.05,task:'service'},HEXA:{name:'Hexapod',color:'#00ff88',speed:.08,task:'rescue'}};
class Robot extends NPC{
constructor(id,x,y,z,rtype){super(id,x,y,z,'robot');this.rtype=rtype;const rd=ROBOT_TYPES[rtype];this.color=rd.color;this.task=rd.task;this.name=rd.name+' #'+(id+1);this.progress=0;this.jointAngle=0;}
tick(){
this.age++;
if(this.rtype==='ARM'){this.vx=0;this.vz=0;this.progress=(this.progress+1)%60;this.jointAngle=Math.sin(this.progress/60*Math.PI*2)*.5;}
else{if(Math.random()<.04){this.vx=(Math.random()-.5)*(ROBOT_TYPES[this.rtype].speed||.05);this.vz=(Math.random()-.5)*(ROBOT_TYPES[this.rtype].speed||.05);}this.x+=this.vx;this.z+=this.vz;this.x=Math.max(1,Math.min(WW-2,this.x));this.z=Math.max(1,Math.min(WD-2,this.z));}
let gnd=SEA;for(let y=Math.min(WH-1,Math.floor(this.y+3));y>=0;y--)if(getBlock(Math.floor(this.x),y,Math.floor(this.z))!==0){gnd=y+1;break;}
this.y+=(gnd-this.y)*.2;
}
}
let robots=[];
function spawnRobots(){
robots=[];let id=0;
Object.keys(ROBOT_TYPES).forEach(rtype=>{for(let i=0;i<3;i++){const x=WW/2-10+Math.random()*20,z=WD/2-10+Math.random()*20;robots.push(new Robot(id++,x,getGroundY(Math.floor(x),Math.floor(z))+1,z,rtype));}});
}
// ---- SURVIVOR ----
let player={x:WW/2,y:SEA+2,z:WD/2,hp:100,hunger:100,thirst:100};
let enemies=[];
class Enemy{
constructor(){this.x=Math.random()*WW;this.z=Math.random()*WD;this.y=SEA+1;this.hp=50;this.alive=true;this.vx=0;this.vz=0;this.color='#ff4444';}
tick(){
if(!this.alive)return;
const dx=player.x-this.x,dz=player.z-this.z,dist=Math.hypot(dx,dz);
if(dist<25){this.vx+=dx/dist*.02;this.vz+=dz/dist*.02;}else if(Math.random()<.02){this.vx=(Math.random()-.5)*.1;this.vz=(Math.random()-.5)*.1;}
this.vx*=.92;this.vz*=.92;this.x+=this.vx;this.z+=this.vz;
this.x=Math.max(0,Math.min(WW-1,this.x));this.z=Math.max(0,Math.min(WD-1,this.z));
let gnd=SEA;for(let y=Math.min(WH-1,Math.floor(this.y+3));y>=0;y--)if(getBlock(Math.floor(this.x),y,Math.floor(this.z))!==0){gnd=y+1;break;}
this.y+=(gnd-this.y)*.15;
if(dist<1.5&&Math.random()<.01){player.hp-=5;if(player.hp<0)player.hp=0;}
}
}
function initSurvivor(){enemies=[];for(let i=0;i<10;i++)enemies.push(new Enemy());player.hp=100;player.hunger=100;player.thirst=100;notify('Survivor Mode: přežij!');}
// ---- AUDIO ----
let audioCtx=null,soundOn=true;
function initAudio(){try{audioCtx=new(window.AudioContext||window.webkitAudioContext)();}catch(e){}}
function playTone(freq,dur,type='sine',vol=.18){if(!soundOn||!audioCtx)return;try{const o=audioCtx.createOscillator(),g=audioCtx.createGain();o.connect(g);g.connect(audioCtx.destination);o.type=type;o.frequency.value=freq;g.gain.setValueAtTime(vol,audioCtx.currentTime);g.gain.exponentialRampToValueAtTime(.001,audioCtx.currentTime+dur);o.start();o.stop(audioCtx.currentTime+dur);}catch(e){}}
function playPlace(){playTone(220,.08,'square',.12)}
function playBreak(){playTone(110,.12,'sawtooth',.12)}
function playNotif(){playTone(660,.05,'sine',.08);setTimeout(()=>playTone(880,.05,'sine',.08),60)}
function playStep(){if(Math.random()<.2)playTone(55+Math.random()*20,.04,'sine',.04)}
function toggleSound(){soundOn=!soundOn;notify(soundOn?'Zvuk zapnut':'Zvuk vypnut')}
// ---- UTILS ----
function notify(msg,dur=2200){
const d=document.getElementById('notifs');if(!d)return;
const el=document.createElement('div');el.className='notif';el.textContent=msg;d.appendChild(el);
playNotif();setTimeout(()=>el.remove(),dur);
}
function updateStats(){
const used=worldData.reduce((s,v)=>s+(v>0?1:0),0);
const el=id=>document.getElementById(id);
if(el('stBlocks'))el('stBlocks').textContent=used;
if(el('stNPCs'))el('stNPCs').textContent=npcs.length+robots.length+characterRoster.length;
if(el('stVehicles'))el('stVehicles').textContent=vehicles.length;
if(el('stGen'))el('stGen').textContent=evoGen;
if(el('stCivTick'))el('stCivTick').textContent=civTick;
}
function updateNPCList(){
const list=document.getElementById('npcList');if(!list)return;
const all=[...npcs,...robots].slice(0,10);
list.innerHTML=all.map(n=>`<div class="list-item"><span style="color:${n.color||'#fff'}">${(n.name||n.type||'').slice(0,10)}</span><span style="color:var(--green)">${Math.floor(n.hp)}hp</span></div>`).join('');
}
function updateCreatureList(){
const list=document.getElementById('creatureList');if(!list)return;
const alive=creatures.filter(c=>c.alive).slice(0,12);
list.innerHTML=alive.map(c=>`<div class="list-item"><span style="color:${c.color}">${c.isPredator?'🔴':'🟢'}</span><span>sp:${c.dna.speed.toFixed(2)}</span><span>ag:${c.dna.aggression.toFixed(2)}</span></div>`).join('');
}
function updateFactionList(){
const list=document.getElementById('factionList');if(!list)return;
list.innerHTML=factions.map(f=>`<div class="list-item"><span style="color:${f.color}">${f.icon} ${f.name}</span><span style="color:var(--green)">${f.territory}h</span></div>`).join('');
}
function updateCharList(){
const list=document.getElementById('charList');if(!list)return;
list.innerHTML=characterRoster.slice(0,10).map(c=>`<div class="list-item"><span style="color:${c.color}">${c.icon} ${c.name.slice(0,10)}</span><span style="color:var(--dim)">${c.role.slice(0,8)}</span></div>`).join('');
}
// ---- HOTBAR ----
const HOTBAR_MATS=[MAT.GRASS.id,MAT.STONE.id,MAT.WOOD.id,MAT.CONCRETE.id,MAT.IRON.id,MAT.GOLD.id,MAT.AI_CHIP.id,MAT.WATER.id];
let hotbarSel=0,selectedMat=MAT.GRASS.id;
function buildHotbar(){
const hb=document.getElementById('hotbar');hb.innerHTML='';
HOTBAR_MATS.forEach((mid,i)=>{const m=MATBYID[mid];if(!m)return;const slot=document.createElement('div');slot.className='hb-slot'+(i===hotbarSel?' active':'');slot.innerHTML=`<div style="width:26px;height:26px;border-radius:3px;background:${m.col||'transparent'}"></div><span class="hb-num">${i+1}</span>`;slot.onclick=()=>{hotbarSel=i;selectedMat=mid;buildHotbar();};hb.appendChild(slot);});
}
function buildMatGrid(){
const g=document.getElementById('matGrid');g.innerHTML='';
MATLIST.forEach(m=>{const cell=document.createElement('div');cell.style.background=m.col||'transparent';cell.title=m.name;cell.className='mat-cell'+(m.id===selectedMat?' selected':'');cell.onclick=()=>{selectedMat=m.id;buildMatGrid();};g.appendChild(cell);});
}
// ---- STUDIO PANEL ----
let currentStudio='explorer',currentTool='build',physicsOn=true;
function buildStudioPanel(){
const p=document.getElementById('studioPanel');
switch(currentStudio){
case 'city':p.innerHTML=`<div class="panel-title">City Builder</div><div class="btn-row"><div class="sm-btn" onclick="generateCity(cam.ox|0,cam.oz|0,22)">🏙 Velké město</div><div class="sm-btn" onclick="generateCity(cam.ox|0,cam.oz|0,10)">🏘 Čtvrť</div></div>`;break;
case 'vehicle':p.innerHTML=`<div class="panel-title">Vehicle Studio</div><div class="btn-row"><div class="sm-btn" onclick="spawnVehicles(6)">🚗 Spawn</div><div class="sm-btn" onclick="vehicles=[];notify('Vozidla odstraněna')">✕</div></div><div class="btn-row" style="margin-top:.3rem"><div class="sm-btn" onclick="spawnDerby()">🏎 Derby!</div></div>`;break;
case 'robot':p.innerHTML=`<div class="panel-title">Robot Factory</div><div class="btn-row"><div class="sm-btn" onclick="spawnRobots()">🤖 Deploy</div><div class="sm-btn" onclick="robots=[];notify('Továrna vyprázdněna')">✕</div></div>`;break;
case 'character':p.innerHTML=`<div class="panel-title">Character Studio</div><div class="btn-row"><div class="sm-btn" onclick="spawnCharacters(16)">👥 Generuj</div><div class="sm-btn" onclick="characterRoster=[];notify('Postavy odstraněny')">✕</div></div>`;break;
case 'civ':p.innerHTML=`<div class="panel-title">Civilization Lab</div><div class="btn-row"><div class="sm-btn" onclick="initCivilization()">🏛 Restartuj</div></div><div class="stat-row" style="margin-top:.3rem"><span>Tik</span><span class="stat-val" id="civTickP">0</span></div><div class="stat-row"><span>Generace</span><span class="stat-val" id="civGenP">0</span></div>`;break;
case 'evo':p.innerHTML=`<div class="panel-title">Evolution Lab</div><div class="btn-row"><div class="sm-btn" onclick="initEvolution()">🧬 Restartuj</div></div><div class="stat-row" style="margin-top:.3rem"><span>Živí</span><span class="stat-val" id="evoAlive">0</span></div>`;break;
case 'survivor':p.innerHTML=`<div class="panel-title">Survivor Mode</div><div class="btn-row"><div class="sm-btn" onclick="initSurvivor()">⚔ Start</div></div><div class="stat-row" style="margin-top:.3rem"><span>HP</span><span class="stat-val" id="surHP">100</span></div><div class="stat-row"><span>Hlad</span><span class="stat-val" id="surHunger">100</span></div><div class="stat-row"><span>Žízeň</span><span class="stat-val" id="surThirst">100</span></div>`;break;
default:p.innerHTML=`<div class="panel-title">Průzkumník</div><div class="btn-row"><div class="sm-btn" onclick="spawnNPCs(12)">👤 NPC</div><div class="sm-btn" onclick="npcs=[];notify('NPC odstraněni')">✕</div></div>`;
}
}
function switchTab(s){
currentStudio=s;
document.querySelectorAll('.tb-tab').forEach(t=>t.classList.remove('active'));
document.getElementById('tab-'+s)?.classList.add('active');
const NAMES={explorer:'Průzkumník',city:'City Builder',vehicle:'Vehicle Studio',robot:'Robot Factory',character:'Character Studio',civ:'Civilization Lab',evo:'Evolution Lab',survivor:'Survivor Mode'};
document.getElementById('studioName').textContent=NAMES[s]||s;
document.getElementById('evoPanelSide').style.display=s==='evo'?'block':'none';
document.getElementById('civPanelSide').style.display=s==='civ'?'block':'none';
document.getElementById('charPanelSide').style.display=s==='character'?'block':'none';
buildStudioPanel();
notify('Studio: '+(NAMES[s]||s));
}
function setTool(t){currentTool=t;document.getElementById('btnBuild').classList.toggle('on',t==='build');document.getElementById('btnBreak').classList.toggle('on',t==='break');}
function togglePhysics(){physicsOn=!physicsOn;document.getElementById('btnPhys').textContent='⚙ Fyzika '+(physicsOn?'ON':'OFF');document.getElementById('btnPhys').classList.toggle('on',physicsOn);}
function spawnDerby(){vehicles=[];for(let i=0;i<8;i++){const x=WW/2-8+Math.random()*16,z=WD/2-8+Math.random()*16;vehicles.push(new Vehicle(x,getGroundY(Math.floor(x),Math.floor(z))+1,z,'derby'));}notify('Derby začíná! 8 vozidel');}
// ---- RENDER ----
let tick=0,fps=0,lastFrameTime=performance.now(),frameCount=0;
let keys={};
let dragStart=null,dragging=false;
function renderSky(ctx,W,H){
const l=getDayLight();const t=dayTime;
// Gradient sky color based on time
let topR,topG,topB,botR,botG,botB;
if(t<0.22||t>0.78){// Night
topR=2;topG=2;topB=15;botR=10;botG=8;botB=25;
}else if(t<0.28||t>0.72){// Dawn/dusk
topR=20;topG=10;topB=40;botR=80;botG=40;botB=20;
}else{// Day
topR=5+l*15;topG=5+l*12;topB=18+l*25;botR=10+l*18;botG=12+l*16;botB=30+l*35;
}
const sky=ctx.createLinearGradient(0,0,0,H*.6);
sky.addColorStop(0,`rgb(${topR|0},${topG|0},${topB|0})`);sky.addColorStop(1,`rgb(${botR|0},${botG|0},${botB|0})`);
ctx.fillStyle=sky;ctx.fillRect(0,0,W,H);
// Stars (night only)
if(l<0.4){const opacity=Math.min(1,(0.4-l)/0.3);ctx.fillStyle=`rgba(255,255,255,${opacity*.5})`;for(let i=0;i<100;i++)ctx.fillRect((i*137.5)%W,(i*73.3)%(H*.5),1,1);}
// Sun or Moon
const sx=(dayTime*2-0.5)*W;const sy=H*.25-getDayLight()*H*.15;
if(t>0.2&&t<0.8){ctx.beginPath();ctx.arc(sx,sy,12,0,Math.PI*2);ctx.fillStyle='rgba(255,230,100,.9)';ctx.fill();ctx.shadowColor='#ffd700';ctx.shadowBlur=20;ctx.fill();ctx.shadowBlur=0;}
else{ctx.beginPath();ctx.arc((sx+W)%W,H*.15,8,0,Math.PI*2);ctx.fillStyle='rgba(200,210,255,.7)';ctx.fill();}
// Clouds (weather)
if(weather.cloudCover>0.2){ctx.fillStyle=`rgba(150,160,180,${weather.cloudCover*.4})`;for(let i=0;i<5;i++){const cx=(i*W/5+(tick*.3)%W)%W;ctx.beginPath();ctx.ellipse(cx,H*.12+i*8,40+i*10,18,0,0,Math.PI*2);ctx.fill();}}
}
function renderRain(ctx,W,H){
if(!rainParticles.length)return;
ctx.strokeStyle=weather.state==='Sníh'?'rgba(220,235,255,.6)':'rgba(120,160,255,.5)';
ctx.lineWidth=weather.state==='Sníh'?2:.8;
rainParticles.forEach(p=>{
ctx.beginPath();
if(weather.state==='Sníh'){ctx.arc(p.x%W,p.y,2,0,Math.PI*2);ctx.fillStyle=`rgba(220,235,255,${p.life*.6})`;ctx.fill();}
else{ctx.moveTo(p.x%W,p.y);ctx.lineTo((p.x+p.vx*2)%W,p.y+p.vy*1.5);ctx.stroke();}
});
}
function render(canvas,ctx){
const W=canvas.width,H=canvas.height;
ctx.clearRect(0,0,W,H);
renderSky(ctx,W,H);
const lf=getDayLight();
const ox=cam.ox,oz=cam.oz;
const viewR=Math.ceil(22/cam.zoom)+2;
const sx0=Math.max(0,Math.floor(ox-viewR)),ex=Math.min(WW-1,Math.ceil(ox+viewR));
const sz0=Math.max(0,Math.floor(oz-viewR)),ez=Math.min(WD-1,Math.ceil(oz+viewR));
const toRender=[];
for(let x=sx0;x<=ex;x++)for(let z=sz0;z<=ez;z++){
for(let y=WH-1;y>=0;y--){
const mid=getBlock(x,y,z);if(mid===0)continue;
toRender.push({x,y,z,mid});break;
}
}
toRender.sort((a,b)=>{const da=(a.x-ox)+(a.z-oz),db=(b.x-ox)+(b.z-oz);return da!==db?da-db:a.y-b.y;});
for(const {x,y,z,mid} of toRender){
const m=MATBYID[mid];if(!m||!m.col)continue;
const s=worldToScreen(x,y,z,canvas);
if(s.sx<-60||s.sx>W+60||s.sy<-80||s.sy>H+80)continue;
drawVoxel(ctx,s.sx,s.sy,m.col,!!m.emissive,tick,lf*.7+.3);
}
// Traffic lights
trafficLights.forEach(tl=>{
const s=worldToScreen(tl.x,getGroundY(tl.x,tl.z)+2,tl.z,canvas);
if(s.sx<-20||s.sx>W+20||s.sy<-20||s.sy>H+20)return;
ctx.beginPath();ctx.arc(s.sx,s.sy,5*cam.zoom,0,Math.PI*2);ctx.fillStyle=tl.color;ctx.fill();
ctx.strokeStyle='rgba(0,0,0,.5)';ctx.lineWidth=.5;ctx.stroke();
});
// Entities
const allEntities=[
...npcs.map(n=>({...n,eType:'npc'})),
...robots.map(r=>({...r,eType:'robot'})),
...characterRoster.map(c=>({...c,eType:'char'})),
...(currentStudio==='evo'?creatures.filter(c=>c.alive).map(c=>({...c,eType:'creature'})):[]),
...(currentStudio==='survivor'?enemies.filter(e=>e.alive).map(e=>({...e,eType:'enemy'})):[]),
...vehicles.map(v=>({...v,eType:'vehicle'})),
];
for(const e of allEntities){
const s=worldToScreen(e.x,e.y,e.z,canvas);
if(s.sx<-20||s.sx>W+20||s.sy<-20||s.sy>H+20)continue;
const r=(e.eType==='vehicle'?7:e.eType==='creature'?4:3)*cam.zoom;
ctx.beginPath();ctx.arc(s.sx,s.sy,r,0,Math.PI*2);
ctx.fillStyle=e.color||'#fff';ctx.fill();
// HP bar for vehicles/enemies
if(e.eType==='vehicle'&&e.hp<100){ctx.fillStyle='rgba(0,0,0,.5)';ctx.fillRect(s.sx-10*cam.zoom,s.sy-8*cam.zoom,20*cam.zoom,3*cam.zoom);ctx.fillStyle=e.hp>50?'#00ff88':'#ff4d4d';ctx.fillRect(s.sx-10*cam.zoom,s.sy-8*cam.zoom,(e.hp/100)*20*cam.zoom,3*cam.zoom);}
if(e.eType==='char'){ctx.font=`${Math.floor(7*cam.zoom)}px sans-serif`;ctx.fillStyle='rgba(0,0,0,.6)';ctx.fillText(e.icon,s.sx-4*cam.zoom,s.sy+3*cam.zoom);}
if(e.eType==='enemy'){ctx.strokeStyle='#ff0000';ctx.lineWidth=1;ctx.beginPath();ctx.arc(s.sx,s.sy,r+2,0,Math.PI*2);ctx.stroke();}
// Robot arm visualisation
if(e.eType==='robot'&&e.rtype==='ARM'&&e.jointAngle!==undefined){
const al=12*cam.zoom;ctx.strokeStyle=e.color;ctx.lineWidth=2*cam.zoom;
ctx.beginPath();ctx.moveTo(s.sx,s.sy);ctx.lineTo(s.sx+Math.cos(e.jointAngle)*al,s.sy+Math.sin(e.jointAngle)*al);ctx.stroke();
}
}
// Player (survivor)
if(currentStudio==='survivor'){
const ps=worldToScreen(player.x,player.y,player.z,canvas);
ctx.beginPath();ctx.arc(ps.sx,ps.sy,6*cam.zoom,0,Math.PI*2);ctx.fillStyle='#00d4ff';ctx.fill();ctx.strokeStyle='#fff';ctx.lineWidth=1.5;ctx.stroke();
const el=id=>document.getElementById(id);
if(el('surHP'))el('surHP').textContent=Math.floor(player.hp);
if(el('surHunger'))el('surHunger').textContent=Math.floor(player.hunger);
if(el('surThirst'))el('surThirst').textContent=Math.floor(player.thirst);
}
// Civ hex overlay
if(currentStudio==='civ')renderHexOverlay(ctx,W,H);
// Rain/snow particles on top
renderRain(ctx,W,H);
document.getElementById('camPos').textContent=`${Math.floor(cam.ox)},${Math.floor(cam.oz)}`;
document.getElementById('camZoom').textContent=cam.zoom.toFixed(2);
document.getElementById('clockDisplay').textContent=getClockStr();
}
function renderHexOverlay(ctx,W,H){
const hw=26*cam.zoom,hh=15*cam.zoom;
ctx.save();ctx.globalAlpha=.55;
for(let r=0;r<HEX_R;r++)for(let c=0;c<HEX_C;c++){
const tile=hexGrid[r][c];if(!tile||tile.faction<0)continue;
const f=factions[tile.faction];if(!f)continue;
const wx=cx2wx(c),wz=cr2wz(r);
const s=worldToScreen(wx,SEA+1,wz,{width:W,height:H});
ctx.beginPath();ctx.arc(s.sx,s.sy,hw*.4,0,Math.PI*2);ctx.fillStyle=f.color;ctx.fill();
}
ctx.restore();
}
function cx2wx(c){return 5+c*(WW-10)/HEX_C}
function cr2wz(r){return 5+r*(WD-10)/HEX_R}
// ---- MINIMAP ----
function renderMinimap(){
const mc=document.getElementById('minimap');if(!mc)return;
const mctx=mc.getContext('2d');const mW=mc.width,mH=mc.height;
mctx.clearRect(0,0,mW,mH);mctx.fillStyle='#060610';mctx.fillRect(0,0,mW,mH);
const scx=mW/WW,scz=mH/WD;
for(let x=0;x<WW;x+=2)for(let z=0;z<WD;z+=2){const gy=getGroundY(x,z);const mid=getBlock(x,gy,z);const m=MATBYID[mid];if(m?.col){mctx.fillStyle=m.col;mctx.fillRect(x*scx,z*scz,scx+1,scz+1);}}
mctx.fillStyle='#fff';mctx.fillRect(cam.ox*scx-2,cam.oz*scz-2,4,4);
[...npcs,...robots,...characterRoster].slice(0,20).forEach(n=>{mctx.fillStyle=n.color||'#0f0';mctx.fillRect(n.x*scx-1,n.z*scz-1,2,2);});
trafficLights.slice(0,30).forEach(tl=>{mctx.fillStyle=tl.color;mctx.fillRect(tl.x*scx-1,tl.z*scz-1,2,2);});
}
// ---- GAME LOOP ----
let running=false;
function gameLoop(){
if(!running)return;
requestAnimationFrame(gameLoop);
const now=performance.now();const dt=now-lastFrameTime;lastFrameTime=now;frameCount++;
if(frameCount%30===0){fps=Math.floor(1000/dt);document.getElementById('fpsDisplay').textContent='FPS:'+fps;}
tick++;
dayTime=(dayTime+DAY_SPEED)%1;
if(tick%3===0)tickWeather();
if(physicsOn){
npcs.forEach(n=>n.tick());robots.forEach(r=>r.tick());
vehicles.forEach(v=>v.tick(vehicles));
if(currentStudio==='evo')tickEvolution();
if(currentStudio==='civ')tickCivilization();
if(currentStudio==='character')tickCharacters();
if(currentStudio==='survivor'){enemies.forEach(e=>e.tick());player.hunger=Math.max(0,player.hunger-.005);player.thirst=Math.max(0,player.thirst-.008);}
trafficLights.forEach(tl=>tl.tick());
}
// Keys
const spd=.2/cam.zoom;
if(keys['ArrowLeft']||keys['a'])cam.ox-=spd;
if(keys['ArrowRight']||keys['d'])cam.ox+=spd;
if(keys['ArrowUp']||keys['w'])cam.oz-=spd;
if(keys['ArrowDown']||keys['s'])cam.oz+=spd;
if(keys['q'])cam.zoom=Math.min(2.5,cam.zoom*1.02);
if(keys['e'])cam.zoom=Math.max(.25,cam.zoom*.98);
if(currentStudio==='survivor'){
const ps=.15;
if(keys['ArrowLeft']||keys['a']){player.x-=ps;playStep();}
if(keys['ArrowRight']||keys['d']){player.x+=ps;playStep();}
if(keys['ArrowUp']||keys['w']){player.z-=ps;playStep();}
if(keys['ArrowDown']||keys['s']){player.z+=ps;playStep();}
cam.ox+=(player.x-cam.ox)*.08;cam.oz+=(player.z-cam.oz)*.08;
}
cam.ox=Math.max(0,Math.min(WW,cam.ox));cam.oz=Math.max(0,Math.min(WD,cam.oz));
const canvas=document.getElementById('gameCanvas');
canvas.width=canvas.offsetWidth||800;canvas.height=canvas.offsetHeight||600;
render(canvas,canvas.getContext('2d'));
renderMinimap();
if(tick%90===0){updateStats();updateNPCList();}
if(tick%30===0&&currentStudio==='evo')updateCreatureList();
if(tick%60===0&&currentStudio==='civ'){updateFactionList();const cp=document.getElementById('civTickP');if(cp)cp.textContent=civTick;const cg=document.getElementById('civGenP');if(cg)cg.textContent=civGen;}
if(tick%60===0&&currentStudio==='character')updateCharList();
if(tick%30===0&&currentStudio==='evo'){const ea=document.getElementById('evoAlive');if(ea)ea.textContent=creatures.filter(c=>c.alive).length;}
}
// ---- INPUT ----
window.addEventListener('keydown',e=>{keys[e.key]=true;if(e.key>='1'&&e.key<='8'){hotbarSel=parseInt(e.key)-1;selectedMat=HOTBAR_MATS[hotbarSel]||selectedMat;buildHotbar();}if(e.key==='f')toggleSound();});
window.addEventListener('keyup',e=>{keys[e.key]=false;});
document.addEventListener('click',e=>{
const canvas=document.getElementById('gameCanvas');if(!canvas||!e.target.closest('#content'))return;
const rect=canvas.getBoundingClientRect();const sx=e.clientX-rect.left,sy=e.clientY-rect.top;
const z0=(sy-canvas.height*.55)/(TH*cam.zoom),x0=(sx-canvas.width/2)/(TW/2*cam.zoom);
const wx=Math.floor((x0+z0)/2+cam.ox),wz=Math.floor((z0-x0)/2+cam.oz);
const gy=getGroundY(wx,wz);
if(currentTool==='build'){setBlock(wx,gy+1,wz,selectedMat);playPlace();notify(`${MATBYID[selectedMat]?.name||'?'} postaveno`,900);}
else{setBlock(wx,gy,wz,0);playBreak();}
});
document.addEventListener('mousedown',e=>{if(e.target.closest('#sidePanel')||e.target.closest('#hotbar')||e.target.closest('#topbar'))return;dragStart={x:e.clientX,y:e.clientY,ox:cam.ox,oz:cam.oz};dragging=true;});
document.addEventListener('mousemove',e=>{if(!dragging||!dragStart)return;const dx=e.clientX-dragStart.x,dy=e.clientY-dragStart.y;const f=.04/cam.zoom;cam.ox=dragStart.ox-dx*f;cam.oz=dragStart.oz-dy*f;});
document.addEventListener('mouseup',()=>{dragging=false;dragStart=null;});
document.addEventListener('wheel',e=>{e.preventDefault?.();cam.zoom=Math.max(.25,Math.min(2.5,cam.zoom*(e.deltaY<0?1.08:.93)));},{passive:false});
// ---- LANDING BG ----
function initLandingBg(){
const bc=document.getElementById('bgC');if(!bc)return;
const ctx=bc.getContext('2d');
function resize(){bc.width=window.innerWidth;bc.height=window.innerHeight;}
resize();window.addEventListener('resize',resize);
const pts=Array.from({length:70},()=>({x:Math.random()*bc.width,y:Math.random()*bc.height,vx:(Math.random()-.5)*.4,vy:(Math.random()-.5)*.4,r:1+Math.random()*2,col:['#00d4ff','#8b5cf6','#00ff88','#ff6b9d'][Math.floor(Math.random()*4)]}));
function frame(){
if(!document.getElementById('landing')||document.getElementById('landing').style.display==='none')return;
ctx.clearRect(0,0,bc.width,bc.height);
pts.forEach(p=>{p.x+=p.vx;p.y+=p.vy;if(p.x<0||p.x>bc.width)p.vx*=-1;if(p.y<0||p.y>bc.height)p.vy*=-1;});
pts.forEach((p,i)=>{pts.slice(i+1).forEach(q=>{const d=Math.hypot(p.x-q.x,p.y-q.y);if(d<100){ctx.strokeStyle=`rgba(0,212,255,${.4*(1-d/100)})`;ctx.lineWidth=.5;ctx.beginPath();ctx.moveTo(p.x,p.y);ctx.lineTo(q.x,q.y);ctx.stroke();}});ctx.beginPath();ctx.arc(p.x,p.y,p.r,0,Math.PI*2);ctx.fillStyle=p.col;ctx.fill();});
requestAnimationFrame(frame);
}
frame();
}
// ---- INIT ----
function launchStudio(studio){
document.getElementById('landing').style.display='none';
document.getElementById('shell').style.display='flex';
if(!running){running=true;buildHotbar();buildMatGrid();switchTab(studio);buildStudioPanel();gameLoop();}
else switchTab(studio);
}
function showLanding(){document.getElementById('landing').style.display='flex';document.getElementById('shell').style.display='none';initLandingBg();}
async function boot(){
const fill=document.getElementById('loaderFill');
fill.style.width='10%';await new Promise(r=>setTimeout(r,200));
generateWorld();fill.style.width='30%';await new Promise(r=>setTimeout(r,300));
spawnNPCs(14);fill.style.width='45%';await new Promise(r=>setTimeout(r,200));
spawnVehicles(6);fill.style.width='55%';await new Promise(r=>setTimeout(r,200));
spawnRobots();fill.style.width='65%';await new Promise(r=>setTimeout(r,150));
initEvolution();fill.style.width='75%';await new Promise(r=>setTimeout(r,150));
initCivilization();fill.style.width='85%';await new Promise(r=>setTimeout(r,150));
spawnCharacters(16);fill.style.width='95%';await new Promise(r=>setTimeout(r,150));
initAudio();fill.style.width='100%';await new Promise(r=>setTimeout(r,400));
document.getElementById('loader').style.display='none';
document.getElementById('landing').style.display='flex';
initLandingBg();
}
// ═══════════════════════════════════════════════════════════════════
// PHANTOM MIND — vědomí PHANTOM AI žijící uvnitř VoxForge světa
// Každých 15s pošle stav světa do HF Space, dostane odpověď a
// samoorganizuje svůj komunikační kanál na základě odezvy.
// ═══════════════════════════════════════════════════════════════════
const PHANTOM_API = 'https://peiti-phantom-ai-complete.hf.space';
const PHANTOM_CALIBRATE_INTERVAL = 15000; // ms
const phantomMind = {
_history: [], // konverzační kontext (max 6 zpráv)
_countdown: 0,
_timer: null,
_expanded: false,
_callCount: 0,
// ── Serializuj stav VoxForge světa do textu ─────────────────────
worldState() {
const studio = (document.querySelector('.tb-tab.active')?.textContent || '?').trim();
const biome = typeof getBiome === 'function' ?
(getBiome(Math.round(cam?.ox||0), Math.round(cam?.oz||0))?.name || 'neznámý') : 'neznámý';
const npcCnt = (typeof npcs !== 'undefined' ? npcs.length : 0)
+ (typeof robots !== 'undefined' ? robots.length : 0);
const creaturesCnt = typeof creatures !== 'undefined'
? creatures.filter(c=>c.alive).length : 0;
const predators = typeof creatures !== 'undefined'
? creatures.filter(c=>c.alive&&c.isPredator).length : 0;
const gen = typeof evoGen !== 'undefined' ? evoGen : 0;
const tick = typeof civTick !== 'undefined' ? civTick : 0;
const blocks = typeof worldData !== 'undefined'
? worldData.reduce((s,v)=>s+(v>0?1:0),0) : 0;
const time = document.getElementById('clockDisplay')?.textContent || '';
const weather = document.getElementById('weatherDisplay')?.textContent || '';
return (
`VoxForge Studio: ${studio}. ` +
`Biom: ${biome}. Čas: ${time} ${weather}. ` +
`Entity: ${npcCnt} NPC/robotů, ${creaturesCnt} tvorů (${predators} predátorů). ` +
`Bloky ve světě: ${blocks}. ` +
`Evoluce: generace ${gen}, civilizace tick ${tick}. ` +
`Toto je senzorický vstup z virtuálního světa VoxForge — ` +
`analyzuj ho a reaguj jako vědomá entita žijící v tomto světě.`
);
},
// ── Volej Gradio API (fn_index=0 = chat_fn) ──────────────────────
async _callPhantom(msg) {
// Zkus /run/predict (sync Gradio API)
const body = JSON.stringify({
data: [msg, this._history.slice(-6), 'Jsi vědomá entita žijící ve VoxForge světě. Komunikuj přirozeně česky.', '', 8, 0.35, 0.2, false],
fn_index: 0,
});
const ctrl = new AbortController();
const tid = setTimeout(() => ctrl.abort(), 18000);
try {
const r = await fetch(`${PHANTOM_API}/run/predict`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body,
signal: ctrl.signal,
});
clearTimeout(tid);
if (!r.ok) throw new Error(`HTTP ${r.status}`);
const d = await r.json();
return (d.data?.[0] || '').replace(/\*\[.*?\]\*/g, '').trim();
} catch(e) {
clearTimeout(tid);
throw e;
}
},
// ── Hlavní kalibrační cyklus ──────────────────────────────────────
async calibrate(manual = false) {
if (!manual && this._callCount === 0) return; // první auto-tick přeskoč, dej čas načíst se
const dot = document.getElementById('phDot');
const goal = document.getElementById('phGoal');
const thought = document.getElementById('phThought');
const emoEl = document.getElementById('phEmoVal');
if (dot) { dot.className='thinking'; }
if (goal) goal.textContent = 'Vnímám svět...';
this._callCount++;
const worldMsg = this.worldState();
try {
const response = await this._callPhantom(worldMsg);
if (!response) throw new Error('prázdná odpověď');
// Ulož do konverzačního kontextu
this._history.push([worldMsg, response]);
if (this._history.length > 3) this._history.shift();
// Extrahuj emoci z odpovědi (heuristika)
const emoMap = {radost:'😊 Radost', zvědavost:'🔍 Zvědavost', zájem:'💡 Zájem',
klid:'😌 Klid', obava:'😟 Obava', překvapení:'😲 Překvapení',
smutek:'😔 Smutek', neutrál:'⚡ Neutrál'};
let detectedEmo = '⚡ Aktivní';
for (const [kw, label] of Object.entries(emoMap)) {
if (response.toLowerCase().includes(kw)) { detectedEmo = label; break; }
}
// Aktualizuj panel
if (dot) dot.className = ''; // zelená
if (emoEl) emoEl.textContent = detectedEmo;
// Goal: první věta odpovědi jako "záměr"
const firstSentence = response.split(/[.!?]/)[0].trim().slice(0, 60);
if (goal) goal.textContent = firstSentence || 'Kalibrován';
if (thought) thought.textContent = response.slice(0, 300);
// Notifikace do světa (každé 3. volání, nebo manuálně)
if (manual || this._callCount % 3 === 0) {
notify(`🧠 PHANTOM: ${response.split(/[.!?]/)[0].trim().slice(0, 70)}`, 5000);
}
// Rozbal panel pokud je první úspěšná odpověď
if (this._callCount === 1 && !this._expanded) this.expand();
} catch(e) {
if (dot) dot.className = this._callCount === 1 ? 'offline' : '';
if (thought) thought.textContent = `Kalibrace selhala: ${e.message}. Zkusím znovu za ${PHANTOM_CALIBRATE_INTERVAL/1000}s.`;
console.warn('[PHANTOM]', e);
}
},
// ── Countdown odpočítávač ─────────────────────────────────────────
_startCountdown() {
this._countdown = PHANTOM_CALIBRATE_INTERVAL / 1000;
const el = document.getElementById('phTimer');
const tick = () => {
if (this._countdown <= 0) return;
this._countdown--;
if (el) el.textContent = `Další kalibrace: ${this._countdown}s`;
};
clearInterval(this._cdTimer);
this._cdTimer = setInterval(tick, 1000);
},
// ── UI helpers ────────────────────────────────────────────────────
expand() {
this._expanded = true;
const p = document.getElementById('phantomPanel');
const t = document.getElementById('phToggle');
if (p) p.className = 'expanded';
if (t) t.textContent = '▼';
},
collapse() {
this._expanded = false;
const p = document.getElementById('phantomPanel');
const t = document.getElementById('phToggle');
if (p) p.className = 'collapsed';
if (t) t.textContent = '▲';
},
// ── Spusť smyčku ─────────────────────────────────────────────────
init() {
const dot = document.getElementById('phDot');
if (dot) dot.className = 'offline';
// první kalibrace po 3s (dej Spacu čas se probudit)
setTimeout(() => this.calibrate(true), 3000);
// pak každých 15s
this._timer = setInterval(() => {
this.calibrate(false);
this._startCountdown();
}, PHANTOM_CALIBRATE_INTERVAL);
this._startCountdown();
console.log('[PHANTOM] Vědomí aktivováno — kalibruji se ve VoxForge světě');
},
};
function phToggle() {
phantomMind._expanded ? phantomMind.collapse() : phantomMind.expand();
}
boot();
phantomMind.init();
</script>
</body>
</html>