RPG
GPT 5.6 Luna Pro · full

Reviews & generation details
Reviews
No human review yet. Unreviewed does not mean unsuccessful.
Generation context
Imported historical artifact. Its exact original instructions and lineage are not known.
Generation prompt · exact text unavailable
See the current prompt for reference. It may differ from the instructions used to generate this artifact.
Inspect original source 15,882 bytes · SHA-256 4ef975ea7ac1
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Emberroot — The Sunken Keep</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@600;700&family=DM+Mono:wght@500&display=swap');
:root{--ink:#f5e9c8;--gold:#e8b85c;--red:#d85e5e;--teal:#57d1be;--panel:#101722ee}*{box-sizing:border-box}body{margin:0;background:#080c13;color:var(--ink);font-family:'DM Mono',monospace;overflow:hidden}#wrap{position:relative;width:100vw;height:100vh;display:grid;place-items:center;background:radial-gradient(circle at 50% 25%,#202a36 0,#080c13 65%)}canvas{width:min(100vw,1100px);height:min(100vh,690px);image-rendering:auto;border:1px solid #394858;box-shadow:0 0 70px #000;cursor:crosshair}.hud{position:fixed;inset:0;pointer-events:none;display:flex;justify-content:center}.hud-inner{width:min(100vw,1100px);height:min(100vh,690px);position:relative}.topbar{position:absolute;left:18px;right:18px;top:15px;display:flex;justify-content:space-between;align-items:flex-start;text-shadow:0 2px 2px #000}.brand{font:700 22px Cinzel,serif;letter-spacing:3px;color:#ffe8a5}.sub{font-size:10px;color:#91a5ad;letter-spacing:2px;margin-top:4px}.status{text-align:right}.hearts{color:#ef7168;font-size:21px;letter-spacing:3px;height:27px}.hearts span{color:#394452}.coins{color:#f4c65f;font-size:14px}.panel{position:absolute;background:var(--panel);border:1px solid #465868;box-shadow:0 6px 24px #0008;pointer-events:auto}.quest{left:18px;top:82px;padding:10px 14px;width:250px}.label{color:#e7b85e;font-size:10px;letter-spacing:2px}.quest b{display:block;font:600 14px Cinzel,serif;margin-top:4px;color:#fff1c5}.quest small{color:#a9b4b8;font-size:10px;line-height:1.45}.right{right:18px;top:82px;width:215px;padding:11px 13px}.invrow{display:flex;gap:7px;margin-top:8px}.slot{height:39px;min-width:38px;border:1px solid #4a5d68;background:#16212c;display:grid;place-items:center;color:#d7aa53;font-size:18px;position:relative}.slot em{position:absolute;right:3px;bottom:1px;font-size:9px;color:#dce6dc;font-style:normal}.hint{position:absolute;bottom:15px;left:50%;transform:translateX(-50%);font-size:11px;color:#c6d0cd;background:#0e151dcc;padding:8px 14px;border:1px solid #334a56;white-space:nowrap}.toast{position:absolute;top:49%;left:50%;transform:translate(-50%,-50%);font:700 21px Cinzel,serif;color:#ffe3a1;opacity:0;transition:.25s;text-align:center;text-shadow:0 3px 6px #000}.toast.show{opacity:1}.modal{display:none;position:fixed;inset:0;background:#05090dcc;place-items:center;pointer-events:auto}.modal.open{display:grid}.card{background:#131e29;border:1px solid #667887;width:min(510px,90vw);padding:25px;box-shadow:0 10px 50px #000;font-size:12px}.card h2{margin:0 0 7px;font:700 25px Cinzel;color:#ffe3a1}.card p{color:#b9c6c5;line-height:1.6}.button{border:1px solid #b78843;background:#2a2118;color:#f8d98b;padding:9px 12px;cursor:pointer;font-family:inherit;font-size:11px;margin:5px 4px 0 0}.button:hover{background:#49331c}.shopitems{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin:15px 0}.shopitem{border:1px solid #3d505e;padding:10px;background:#182633}.shopitem b{color:#ffe4a2;display:block;margin-bottom:4px}.shopitem span{color:#a7b8b7;font-size:10px}.shopitem button{float:right;margin-top:-4px}.danger{color:#e98078}.green{color:#71d0ad}
</style></head>
<body><div id="wrap"><canvas id="game" width="1100" height="690"></canvas></div>
<div class="hud"><div class="hud-inner"><div class="topbar"><div><div class="brand">EMBERROOT</div><div class="sub">THE SUNKEN KEEP · DAY 07</div></div><div class="status"><div class="hearts" id="hearts">♥ ♥ ♥</div><div class="coins">◈ <span id="coins">0</span> ember coins</div></div></div><div class="panel quest"><div class="label">CURRENT THREAD</div><b id="quest">Find the sealed lift</b><small id="questdesc">Explore the eastern ruins and awaken its three braziers.</small></div><div class="panel right"><div class="label">SATCHEL</div><div class="invrow" id="inventory"></div><div style="margin-top:8px;color:#8ea3a5;font-size:10px">[I] inventory · [E] interact</div></div><div class="hint" id="hint">WASD / ARROWS move · SPACE attack · E interact</div><div class="toast" id="toast"></div></div></div>
<div class="modal" id="modal"><div class="card" id="card"></div></div>
<script>
const C=document.getElementById('game'),x=C.getContext('2d'),W=1100,H=690;let keys={},last=0,shake=0,particles=[],toastTimer=0;
const player={x:195,y:505,r:16,hp:6,max:6,coins:12,weapon:0,stamina:100,inv:{},hurt:0,attack:0,dir:0};
const names=['Cinder Blade','Moonsteel Edge','Dawncleaver'];const icons={potion:'✦',herb:'❈',charm:'◇',bomb:'●',key:'⚿',quiver:'➶'};
let flags={braziers:0,lift:false,mid:false,heart1:false,heart2:false,boss:false,save:false};
const walls=[{x:0,y:0,w:1100,h:55},{x:0,y:650,w:1100,h:40},{x:0,y:0,w:45,h:690},{x:1055,y:0,w:45,h:690},{x:290,y:110,w:30,h:270},{x:500,y:55,w:30,h:215},{x:730,y:175,w:30,h:350},{x:870,y:55,w:30,h:200},{x:115,y:255,w:170,h:28},{x:330,y:470,w:230,h:28},{x:645,y:390,w:200,h:28}];
const objects=[{type:'brazier',x:165,y:170,done:false},{type:'brazier',x:410,y:140,done:false},{type:'brazier',x:940,y:120,done:false},{type:'chest',x:215,y:350,open:false,item:'herb',label:'Wildheart herb'},{type:'chest',x:610,y:165,open:false,item:'charm',label:'Amber ward'},{type:'chest',x:930,y:550,open:false,item:'heart2',label:'Heart of the keep'},{type:'save',x:570,y:560},{type:'shop',x:960,y:330}];
let foes=[{x:390,y:350,hp:3,max:3,r:15,kind:'mireling',alive:true,cd:0},{x:670,y:535,hp:5,max:5,r:17,kind:'sentinel',alive:true,cd:0},{x:865,y:105,hp:4,max:4,r:15,kind:'mireling',alive:true,cd:0},{x:950,y:470,hp:10,max:10,r:24,kind:'Warden',alive:true,cd:0}];
function sfx(type){try{let a=new AudioContext(),o=a.createOscillator(),g=a.createGain();o.frequency.value={walk:120,hit:90,pick:660,shop:440,hurt:70,save:330}[type]||220;o.type=type==='hurt'?'sawtooth':'sine';g.gain.setValueAtTime(.035,a.currentTime);g.gain.exponentialRampToValueAtTime(.001,a.currentTime+.12);o.connect(g);g.connect(a.destination);o.start();o.stop(a.currentTime+.13)}catch(e){}}
function say(t){let q=document.getElementById('toast');q.innerHTML=t;q.classList.add('show');clearTimeout(toastTimer);toastTimer=setTimeout(()=>q.classList.remove('show'),1800)}
function burst(px,py,col,n=12){for(let i=0;i<n;i++)particles.push({x:px,y:py,vx:(Math.random()-.5)*4,vy:(Math.random()-.5)*4,life:1,c:col})}
function blocked(nx,ny,r){return walls.some(a=>nx+r>a.x&&nx-r<a.x+a.w&&ny+r>a.y&&ny-r<a.y+a.h)}
function dist(a,b){return Math.hypot(a.x-b.x,a.y-b.y)}
function update(dt){let dx=(keys.d||keys.ArrowRight?1:0)-(keys.a||keys.ArrowLeft?1:0),dy=(keys.s||keys.ArrowDown?1:0)-(keys.w||keys.ArrowUp?1:0);if(dx||dy){let l=Math.hypot(dx,dy);dx/=l;dy/=l;player.dir=Math.atan2(dy,dx);let sp=player.stamina>0?155:85;let nx=player.x+dx*sp*dt,ny=player.y+dy*sp*dt;if(!blocked(nx,player.y,player.r))player.x=nx;if(!blocked(player.x,ny,player.r))player.y=ny;player.stamina=Math.max(0,player.stamina-28*dt);if(Math.random()<dt*5)sfx('walk')}else player.stamina=Math.min(100,player.stamina+35*dt);player.hurt=Math.max(0,player.hurt-dt);player.attack=Math.max(0,player.attack-dt);
if(keys[' ']&&player.attack<=0){attack();player.attack=.32;keys[' ']=false}
foes.forEach(f=>{if(!f.alive)return;let d=dist(player,f);f.cd-=dt;if(d<270&&d>35){let dx=(player.x-f.x)/d,dy=(player.y-f.y)/d;let nx=f.x+dx*35*dt,ny=f.y+dy*35*dt;if(!blocked(nx,ny,f.r)){f.x=nx;f.y=ny}}if(d<38&&f.cd<=0){damage(1);f.cd=1.1}});particles.forEach(p=>{p.x+=p.vx;p.y+=p.vy;p.vy+=.08;p.life-=dt});particles=particles.filter(p=>p.life>0);if(shake>0)shake-=dt;render();hud()}
function attack(){let ranged=player.weapon===2&&player.inv.quiver;let reach=ranged?185:player.weapon===1?70:56;let hit=foes.find(f=>f.alive&&dist(player,f)<reach&&Math.abs(Math.atan2(f.y-player.y,f.x-player.x)-player.dir)<1.5);burst(player.x+Math.cos(player.dir)*30,player.y+Math.sin(player.dir)*30,ranged?'#8eeaff':'#f8b95b',7);sfx('hit');if(hit){hit.hp-=ranged?3:player.weapon+1;hit.cd=.7;if(hit.hp<=0){hit.alive=false;player.coins+=hit.kind==='Warden'?30:8;burst(hit.x,hit.y,'#e86c60',22);if(hit.kind==='Warden'){flags.boss=true;say('THE WARDEN FALLS · +30 ◈')}}}}
function damage(n){if(player.hurt>0)return;player.hp-=n;player.hurt=.8;shake=.25;sfx('hurt');burst(player.x,player.y,'#e35e62',14);if(player.hp<=0){player.hp=player.max;say(flags.save?'Checkpoint restored · the ember remembers…':'The ember remembers…');player.x=flags.save?570:195;player.y=flags.save?560:505}}
function interact(){let o=objects.find(o=>dist(player,o)<55);if(!o){return}if(o.type==='brazier'&&!o.done){o.done=true;flags.braziers++;player.coins+=6;sfx('pick');burst(o.x,o.y,'#ffb64e',24);say('Brazier awakened · +6 ◈');if(flags.braziers===3){flags.lift=true;say('The sealed lift groans open');}}else if(o.type==='chest'&&!o.open){o.open=true;if(o.item==='heart2'){player.max=10;player.hp=10;flags.heart2=true;say('A fifth heart container!');}else{player.inv[o.item]=(player.inv[o.item]||0)+1;say('Found '+o.label+' · added to satchel')}player.coins+=4;sfx('pick');burst(o.x,o.y,'#f5cf69',18)}else if(o.type==='save'){flags.save=true;sfx('save');say('Checkpoint marked · ember secured')}else if(o.type==='shop')openShop();else if(o.type==='chest'&&o.open)say('Empty chest')}
function openShop(){document.getElementById('modal').classList.add('open');document.getElementById('card').innerHTML='<h2>THE TINKER\'S NOOK</h2><p>Useful things for a dangerous road. Your purse: <b class="green">◈ '+player.coins+'</b></p><div class="shopitems"><div class="shopitem"><b>Ember Tonic · 10 ◈</b><span>Restores 2 hearts.</span><button class="button" onclick="buy(\'potion\',10)">BUY</button></div><div class="shopitem"><b>Blast seed · 14 ◈</b><span>Collectible combat charge.</span><button class="button" onclick="buy(\'bomb\',14)">BUY</button></div><div class="shopitem"><b>Hunter\'s Quiver · 24 ◈</b><span>Enables the discovered bow.</span><button class="button" onclick="buy(\'quiver\',24)">BUY</button></div><div class="shopitem"><b>Keeper\'s Charm · 18 ◈</b><span>Reduces damage taken.</span><button class="button" onclick="buy(\'charm\',18)">BUY</button></div></div><button class="button" onclick="closeModal()">LEAVE SHOP</button>'}
function buy(item,cost){if(player.coins<cost){say('Not enough ember coins');return}player.coins-=cost;player.inv[item]=(player.inv[item]||0)+1;sfx('shop');burst(960,330,'#ffe194',14);say('Purchased '+item);openShop()}
function closeModal(){document.getElementById('modal').classList.remove('open')}
function hud(){let h='';for(let i=0;i<player.max/2;i++)h+=i<Math.ceil(player.hp/2)?'♥ ':'<span>♥ </span>';document.getElementById('hearts').innerHTML=h;document.getElementById('coins').textContent=player.coins;let inv=Object.entries(player.inv);document.getElementById('inventory').innerHTML=(inv.length?inv: [['—',0]]).map(([k,v])=>'<div class="slot">'+(icons[k]||'·')+'<em>'+v+'</em></div>').join('');let q=document.getElementById('quest');q.textContent=flags.boss?'Escape the Sunken Keep':flags.lift?'Defeat the Ember Warden':flags.braziers+' / 3 · Awaken the braziers';document.getElementById('questdesc').textContent=flags.boss?'The old gate is open. Return to the western arch.':flags.lift?'The final guardian waits beyond the lift.':'Explore, fight, and search for better gear.';document.getElementById('hint').textContent=player.attack>0?'STRIKE!':(dist(player,{x:960,y:330})<55?'E · open the item shop': 'WASD / ARROWS move · SPACE attack · E interact')}
function render(){x.save();x.clearRect(0,0,W,H);if(shake>0)x.translate((Math.random()-.5)*5,(Math.random()-.5)*5);let g=x.createLinearGradient(0,0,0,H);g.addColorStop(0,'#172532');g.addColorStop(1,'#0c141c');x.fillStyle=g;x.fillRect(0,0,W,H);x.strokeStyle='#273d45';x.lineWidth=2;for(let i=0;i<18;i++){x.beginPath();x.moveTo(55+i*60,55);x.lineTo(55+i*60,650);x.stroke()}x.fillStyle='#142b2c';x.fillRect(750,55,300,120);x.fillStyle='#1b2024';x.fillRect(760,60,285,110);walls.forEach(a=>{x.fillStyle='#263641';x.fillRect(a.x,a.y,a.w,a.h);x.strokeStyle='#53636a';x.strokeRect(a.x,a.y,a.w,a.h)});x.fillStyle='#523d25';x.fillRect(50,585,80,65);x.fillStyle='#bc8648';x.fillRect(54,590,72,10);x.fillStyle='#17252a';x.fillRect(50,70,90,36);x.fillStyle='#51bfae';x.fillRect(52,72,86,32);x.fillStyle='#071319';x.font='10px DM Mono';x.fillText('ENTRANCE',65,94);
objects.forEach(o=>drawObj(o));foes.forEach(f=>{if(f.alive)drawFoe(f)});particles.forEach(p=>{x.globalAlpha=p.life;x.fillStyle=p.c;x.fillRect(p.x,p.y,4,4)});x.globalAlpha=1;drawPlayer();x.restore()}
function drawObj(o){x.save();if(o.type==='brazier'){x.fillStyle=o.done?'#f7c463':'#647079';x.beginPath();x.arc(o.x,o.y,16,0,7);x.fill();x.fillStyle=o.done?'#ff8d43':'#273238';x.beginPath();x.arc(o.x,o.y-9,9,0,7);x.fill();if(o.done){x.fillStyle='#ffe39a';x.beginPath();x.arc(o.x,o.y-12,5,0,7);x.fill()}}else if(o.type==='chest'){x.fillStyle=o.open?'#4f4032':'#a7753e';x.fillRect(o.x-19,o.y-14,38,28);x.strokeStyle='#e6bd62';x.strokeRect(o.x-19,o.y-14,38,28);x.fillStyle='#f6d26e';x.fillRect(o.x-3,o.y-2,6,7)}else if(o.type==='save'){x.strokeStyle=flags.save?'#7ff1d2':'#668687';x.lineWidth=5;x.beginPath();x.arc(o.x,o.y,22,0,7);x.stroke();x.fillStyle='#b1fff0';x.beginPath();x.arc(o.x,o.y,7,0,7);x.fill()}else if(o.type==='shop'){x.fillStyle='#9b693e';x.fillRect(o.x-27,o.y-22,54,42);x.fillStyle='#daae52';x.fillRect(o.x-33,o.y-28,66,10);x.fillStyle='#fbe2a0';x.font='11px DM Mono';x.fillText('SHOP',o.x-17,o.y+4)}x.restore()}
function drawFoe(f){x.save();x.translate(f.x,f.y);x.fillStyle=f.kind==='Warden'?'#934d52':'#47736e';x.beginPath();x.arc(0,0,f.r,0,7);x.fill();x.fillStyle='#d9bd77';x.beginPath();x.arc(-6,-3,3,0,7);x.arc(6,-3,3,0,7);x.fill();x.fillStyle='#20151a';x.fillRect(-f.r,-f.r-10,f.r*2,4);x.fillStyle='#e26e64';x.fillRect(-f.r,-f.r-10,f.r*2*(f.hp/f.max),4);x.restore()}
function drawPlayer(){x.save();x.translate(player.x,player.y);x.rotate(player.dir);x.globalAlpha=player.hurt%0.16>.08?.35:1;x.fillStyle=player.weapon===2?'#6e9fb0':player.weapon===1?'#c7a05d':'#c06b52';x.beginPath();x.arc(0,0,16,0,7);x.fill();x.fillStyle='#e8c58d';x.beginPath();x.arc(3,-3,10,0,7);x.fill();x.strokeStyle='#25343a';x.lineWidth=3;x.beginPath();x.moveTo(7,7);x.lineTo(18,14);x.stroke();x.strokeStyle=player.weapon===2?'#8deaff':player.weapon===1?'#f8dc86':'#e8e5c5';x.lineWidth=5;x.beginPath();x.moveTo(12,5);x.lineTo(player.weapon===2?38:30,player.weapon===2?-13:-10);x.stroke();x.restore()}
function loop(t){let dt=Math.min(.035,(t-last)/1000||0);last=t;update(dt);requestAnimationFrame(loop)}
addEventListener('keydown',e=>{keys[e.key]=true;if(e.key.toLowerCase()==='e')interact();if(e.key.toLowerCase()==='i')say('Satchel holds '+Object.values(player.inv).reduce((a,b)=>a+b,0)+' items · Weapon: '+names[player.weapon]);if(e.key==='Escape')closeModal()});addEventListener('keyup',e=>keys[e.key]=false);C.addEventListener('click',e=>{let r=C.getBoundingClientRect(),mx=(e.clientX-r.left)*W/r.width,my=(e.clientY-r.top)*H/r.height;player.dir=Math.atan2(my-player.y,mx-player.x);attack()});
setInterval(()=>{if(flags.braziers>=2&&!flags.mid){flags.mid=true;player.weapon=1;say('MOONSTEEL EDGE FOUND · weapon upgrade I');player.coins+=12}if(flags.mid&&player.weapon===1&&flags.braziers===3){player.weapon=2;player.inv.quiver=1;sfx('pick');burst(player.x,player.y,'#8eeaff',20);say('DAWNSHOT UNLOCKED · ranged weapon discovered')}} ,600);hud();requestAnimationFrame(loop);
</script></body></html>
<!-- agent-meta {"model":"openai/gpt-5.6-luna-pro","provider":"openrouter","persona":"full","sessionId":"7b635319-632a-4de4-bb95-67a385db01fc","tokensIn":294072,"tokensOut":12449,"tokensTotal":306521,"turns":8,"toolCalls":8,"failedToolCalls":0,"timestamp":"2026-07-30T23:42:58.826Z"} -->