← Warden results

Warden

GPT 6 Astra · full

Initial view of Warden

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 62,716 bytes · SHA-256 51b3995a1d4e
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>The Warden’s Corrosion</title><style>
@import url('data:text/css,');
:root{--gold:#d2b577;--dim:#898a81;--ink:#0b151b;--line:#bda36a30}*{box-sizing:border-box}body{margin:0;background:#091117;color:#dfd9c8;font-family:Georgia,serif;overflow:hidden}button{font:inherit;color:inherit;cursor:pointer;background:transparent;border:1px solid var(--line);transition:.2s}button:hover{background:#d2b57718;border-color:var(--gold)}button:disabled{opacity:.35;cursor:default}.shell{height:100vh;display:flex;flex-direction:column;padding:0 36px}header{height:88px;flex-shrink:0;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--line)}.brand{display:flex;align-items:center;gap:17px}.mark{font-size:38px;color:var(--gold)}h1{font-size:22px;font-weight:normal;letter-spacing:3px;margin:0}.eyebrow{font:10px Arial,sans-serif;letter-spacing:3px;color:var(--gold);text-transform:uppercase}.brand .eyebrow{margin-top:8px;color:var(--dim)}.top-actions{display:flex;gap:10px}.top-actions button{padding:10px 16px;font:10px Arial;letter-spacing:2px}.layout{display:grid;grid-template-columns:minmax(0,1fr) 255px;gap:27px;flex:1;min-height:0;padding:24px 0 18px}.main{display:flex;flex-direction:column;min-height:0}.zone-title{display:flex;justify-content:space-between;align-items:center;margin-bottom:18px}.zone-title h2{font-size:26px;font-weight:normal;margin:7px 0 0}.zone-count{font-size:13px;letter-spacing:3px;color:var(--gold)}.viewport{position:relative;flex:1;min-height:200px;border:1px solid #ad956047;overflow:hidden;background:#111e26;box-shadow:0 16px 50px #0004}canvas{display:block;width:100%;height:100%;object-fit:contain}.canvas-top{position:absolute;top:20px;left:22px;pointer-events:none}.hearts{font-size:25px;letter-spacing:4px;color:#d29a67;text-shadow:0 0 15px #d69c6630;line-height:1.1}.hp-text{font:9px Arial;letter-spacing:2px;color:#bdc2b7;margin-top:6px}.place-label{position:absolute;top:23px;right:22px;text-align:right;font:9px Arial;letter-spacing:2px;color:#9badae;pointer-events:none}.place-label b{display:block;color:#d2b577;font-size:10px;margin-bottom:7px}.toast{position:absolute;bottom:70px;left:50%;transform:translateX(-50%);text-align:center;width:80%;color:#e9d6a9;font-size:14px;text-shadow:0 2px 6px #000;pointer-events:none;transition:opacity .4s}.prompt{position:absolute;bottom:23px;left:50%;transform:translateX(-50%);padding:10px 18px;background:#0b151ae6;border:1px solid #b49b5b60;white-space:nowrap;font:11px Arial;letter-spacing:1px;display:none}.prompt b{color:#e7c88b;margin-right:10px}.shimmer{position:absolute;inset:0;pointer-events:none;box-shadow:inset 0 0 80px #efbe7266;opacity:0;transition:opacity .5s}.below{display:flex;justify-content:space-between;align-items:center;height:39px;flex-shrink:0;font:9px Arial;letter-spacing:1.5px;color:#859396}.below span:first-child{color:#b5a17c}.side{border-left:1px solid var(--line);padding-left:25px;overflow-y:auto;scrollbar-width:thin}.section{padding:19px 0;border-bottom:1px solid var(--line)}.section:first-child{padding-top:0}.section-title{font:9px Arial;letter-spacing:2.5px;color:#8d9b9d;margin-bottom:16px}.quest{font-size:19px;line-height:1.4;margin:0 0 9px}.desc{font:12px/1.8 Arial;color:#8e9b9c;margin:0}.objective{font:11px/1.7 Arial;color:#cfbb92;margin-top:15px;display:flex;gap:10px}.objective:before{content:'◇';color:var(--gold)}.weapon{display:flex;align-items:center;gap:13px;margin:12px 0}.weapon-icon{font-size:30px;color:#d3b375;width:37px;text-align:center}.weapon strong{font-size:15px;font-weight:normal;display:block;margin-bottom:5px}.weapon small{font:9px Arial;letter-spacing:1px;color:#899798}.currency{display:flex;justify-content:space-between;align-items:center;margin-top:18px;font-size:14px}.currency b{font-size:20px;color:#dbbd77;font-weight:normal}.journey{list-style:none;padding:0;margin:0;position:relative}.journey li{font:11px Arial;padding:9px 0;display:flex;align-items:center;color:#58696e;gap:12px}.journey li i{font:9px Arial;border:1px solid #34474d;border-radius:50%;width:19px;height:19px;display:grid;place-items:center;font-style:normal}.journey li.active{color:#d7c193}.journey li.active i{border-color:#ccb06d;box-shadow:0 0 14px #c9a76722}.journey li.done{color:#8eaaa5}.inventory-open{width:100%;padding:12px;margin-top:16px;display:flex;justify-content:space-between;font:10px Arial;letter-spacing:1.5px}footer{height:49px;flex-shrink:0;border-top:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;font:10px Arial;color:#89999c}.controls{display:flex;gap:20px}.key{display:inline-block;border:1px solid #59656765;padding:4px 6px;color:#d0cebe;margin-right:6px;font:9px Arial}.status{font:9px Arial;letter-spacing:1.5px;color:#78898d}.status:before{content:'●';color:#90a996;margin-right:8px}.modal-wrap{position:fixed;inset:0;background:#050e15c9;backdrop-filter:blur(7px);z-index:5;display:none;align-items:center;justify-content:center;padding:25px}.modal{max-width:650px;width:100%;max-height:85vh;overflow:auto;background:linear-gradient(135deg,#1b2b31,#0d191f);border:1px solid #b3975c66;padding:32px;box-shadow:0 30px 100px #0008}.modal h2{font-size:29px;font-weight:normal;margin:12px 0 8px}.modal-head{display:flex;align-items:center;justify-content:space-between}.close{border:0;font-size:24px}.modal .desc{margin-bottom:20px}.item-row{display:flex;align-items:center;gap:17px;border-top:1px solid var(--line);padding:17px 0}.item-symbol{font-size:25px;color:var(--gold);width:32px;text-align:center}.item-info{flex:1}.item-info h3{margin:0 0 5px;font-size:17px;font-weight:normal}.item-info p{margin:0;font:11px/1.6 Arial;color:#8fa0a1}.item-row button{font:10px Arial;padding:10px 13px;letter-spacing:1px}.primary{background:#baa16b;color:#101b20;border-color:#baa16b;padding:13px 22px;font:11px Arial;letter-spacing:2px}.modal-foot{margin-top:20px;display:flex;gap:12px}.touch{display:none;position:absolute;bottom:12px;left:12px;right:12px;justify-content:space-between;pointer-events:none}.touch div{display:flex;gap:5px}.touch button{pointer-events:auto;background:#12232aba;width:39px;height:39px;font:13px Arial}.help-grid{display:grid;grid-template-columns:1fr 1fr;gap:15px;font:12px/1.8 Arial;color:#bdc5bf}.help-grid b{display:block;color:#d5b879;font-family:Georgia;font-size:17px;font-weight:normal}.pause-label{position:absolute;inset:0;display:none;place-items:center;background:#08121988;letter-spacing:5px;color:#dfc693}@media(max-width:900px){.shell{padding:0 16px}.layout{grid-template-columns:minmax(0,1fr) 200px;gap:16px}.side{padding-left:16px}.controls{gap:10px}h1{font-size:17px}.top-actions button{padding:9px}.brand .eyebrow{letter-spacing:1.5px}.quest{font-size:17px}}@media(max-width:650px){.shell{padding:0 10px}header{height:66px}.mark{font-size:26px}.brand{gap:8px}h1{font-size:12px;letter-spacing:1.5px}.brand .eyebrow{font-size:7px}.top-actions button{font-size:8px;padding:8px}.layout{display:block;padding-top:15px}.main{height:100%}.side{display:none}.zone-title h2{font-size:22px}.zone-title{margin-bottom:12px}.canvas-top{top:12px;left:12px}.place-label{top:15px;right:12px;font-size:7px}.hearts{font-size:20px}.controls{gap:7px;font-size:8px}.controls .extra{display:none}.status{display:none}.touch{display:flex}.prompt{bottom:62px;font-size:9px}.toast{bottom:107px}.below{font-size:7px}.modal{padding:22px}.help-grid{grid-template-columns:1fr}.touch button{touch-action:none}}
</style></head><body><div class="shell"><header><div class="brand"><div class="mark">☼</div><div><h1>THE WARDEN’S CORROSION</h1><div class="eyebrow">Aurelion’s sunken observatory</div></div></div><div class="top-actions"><button id="soundBtn" title="Toggle synthesized audio">SOUND OFF</button><button id="helpBtn">GUIDE <span style="color:#d2b577">?</span></button></div></header><div class="layout"><main class="main"><div class="zone-title"><div><div class="eyebrow" id="chapter">I · THE OUTER REACHES</div><h2 id="zoneName">The Ashen Threshold</h2></div><div class="zone-count" id="zoneCount">01 <span style="color:#53616a">/ 06</span></div></div><div class="viewport" id="viewport"><canvas id="game" aria-label="Explore the observatory with WASD or arrow keys. J attacks, K fires, E interacts." tabindex="0"></canvas><div class="canvas-top"><div class="hearts" id="hearts">♥♥♥</div><div class="hp-text" id="hpText">6 / 6 VITALITY</div></div><div class="place-label"><b id="areaLabel">SANCTUARY APPROACH</b><span id="areaSub">THE CORROSION WAITS BEYOND</span></div><div class="toast" id="toast"></div><div class="prompt" id="prompt"></div><div class="shimmer" id="shimmer"></div><div class="pause-label" id="pauseLabel">PAUSED</div><div class="touch"><div><button data-key="ArrowLeft">←</button><button data-key="ArrowUp">↑</button><button data-key="ArrowDown">↓</button><button data-key="ArrowRight">→</button></div><div><button data-key="j">⚔</button><button data-key="k">✧</button><button data-key="e">E</button><button data-key="i">I</button></div></div></div><div class="below"><span id="bottomHint">◇ A sanctuary remembers what the world forgets.</span><span id="saveStatus">NO MEMORY INSCRIBED</span></div></main><aside class="side"><section class="section"><div class="section-title">THE WARDEN’S CHARGE</div><p class="quest">Rekindle the last light.</p><p class="desc">Above a valley without dawn, the observatory is falling silent.</p><div class="objective" id="objective">Rest at the Sanctuary Stone, then cross the broken threshold.</div></section><section class="section"><div class="section-title">ARMAMENT & RELICS</div><div class="weapon"><div class="weapon-icon">⚔</div><div><strong id="weaponName">Ember Blade</strong><small id="weaponType">I · FORGED STEEL</small></div></div><div class="weapon"><div class="weapon-icon" id="rangedIcon" style="opacity:.3">◇</div><div><strong id="rangedName" style="color:#697e85">Undiscovered</strong><small id="rangedType">A DISTANT RESONANCE</small></div></div><div class="currency"><span>◈ &nbsp; Golden Sigils</span><b id="gold">0</b></div><button class="inventory-open" id="inventoryBtn"><span>OPEN SATCHEL</span><span>[ I ]</span></button></section><section class="section"><div class="section-title">THE ASCENT</div><ol class="journey" id="journey"></ol></section><section class="section"><p class="desc" style="font-family:Georgia;font-style:italic;font-size:13px">“We did not build the sun.<br>Only a place for it to return.”</p><div class="eyebrow" style="font-size:8px;margin-top:12px;color:#697c80">— The first Warden</div></section></aside></div><footer><div class="controls"><span><span class="key">W A S D</span> Move</span><span><span class="key">J</span> Strike</span><span><span class="key">K</span> Aether</span><span><span class="key">E</span> Interact</span><span><span class="key">I</span> Satchel</span><span class="extra"><span class="key">SPACE</span> Evade</span></div><div class="status" id="worldStatus">THE BEACON IS DORMANT</div></footer></div><div class="modal-wrap" id="modalWrap"><div class="modal" id="modal"></div></div><script>
'use strict';
const $=id=>document.getElementById(id),canvas=$('game'),ctx=canvas.getContext('2d');
const W=1000,H=640,names=['The Ashen Threshold','The Rusted Archives','The Collapsed Gallery','The Sunken Vault','The Twin Spire','The Core Chamber'];
const chapters=['I · THE OUTER REACHES','II · MEMORIES IN RUST','III · THE FIRST REKINDLING','IV · BENEATH THE MERIDIAN','V · THE FINAL ASCENT','VI · A SUN REMEMBERED'];
const hints=['Rest at the Sanctuary Stone. Defeat the sentinels to unseal the eastern gate.','Defeat the archive guards. Search the northern stacks; a merchant waits at the center.','Cross both bronze plates in succession. Restore the altar; inspect the cracked northern wall.','Halfway to the Beacon. Find the hidden Shard, then awaken both mirror crystals.','Claim the Solar Falchion. Fire at the distant northern switch and survive the gauntlet.','Shatter the two shielding crystals with Aether. Defeat the Astral Custodian and kindle the Beacon.'];
const lore=['A sanctuary remembers what the world forgets.','Their catalogues survived. Their names did not.','Beneath the dust, an ember still remembers its flame.','HALFWAY · A second kind of light sleeps below.','Two spires. One shadow. The last ascent begins.','Even a dying star can be taught to burn again.'];
let p,zone,world,gold,inv,equipped,flags,stock,elapsed=0,modal=false,paused=false,keys={},particles=[],bolts=[],floats=[],shake=0,flash=0,attackFX=null,lastTime=0,toastTime=0,footTime=0,shimmerTime=0,soundOn=false,audio=null,saveData=null,near=null,win=false;
const rnd=(a,b)=>a+Math.random()*(b-a),dist=(a,b)=>Math.hypot(a.x-b.x,a.y-b.y);
function initial(){p={x:145,y:370,hp:6,max:6,weapon:0,ranged:false,ammo:0,dir:0,cool:0,invul:0,dash:0};zone=0;gold=0;inv={band:0,stem:1,lantern:0,cloak:0,seal:0};equipped={band:false,lantern:false,cloak:false};flags={};stock={stem:3,seal:2,heart:1};world=Array.from({length:6},(_,i)=>makeZone(i));win=false;}
function enemy(x,y,hp,type='sentinel'){return {x,y,hp,max:hp,type,cool:1,freeze:0,hit:0,dead:false,homeX:x,homeY:y};}
function obj(type,x,y,extra={}){return {type,x,y,...extra};}
function makeZone(z){let o=[obj('exit',927,355),...(z>0?[obj('back',69,355)]:[])],e=[];if(z===0){o.push(obj('stone',195,310,{id:0}),obj('jar',370,430));e=[enemy(570,315,5),enemy(744,405,5)];}
if(z===1){o.push(obj('stone',468,320,{id:1}),obj('shop',530,230),obj('chest',267,215,{id:'band',hidden:true}),obj('jar',320,450),obj('jar',734,230),obj('cache',779,434,{id:'archiveCache'}));e=[enemy(328,338,6),enemy(610,401,7),enemy(754,305,6)];}
if(z===2){o.push(obj('plate',305,397,{id:'plateA'}),obj('plate',461,397,{id:'plateB'}),obj('altar',644,288,{level:1}),obj('wall',755,208,{id:'galleryWall',hp:4}),obj('heart',794,199,{id:'heart4',behind:'galleryWall'}),obj('jar',235,235),obj('cache',560,455,{id:'galleryCache'}));e=[enemy(515,297,9),enemy(734,415,13,'brute')];}
if(z===3){o.push(obj('wall',302,218,{id:'vaultWall',hp:5}),obj('shard',350,208,{behind:'vaultWall'}),obj('switch',603,202,{id:'mirrorA'}),obj('switch',792,260,{id:'mirrorB'}),obj('lantern',694,230),obj('cache',780,446,{id:'vaultCache'}),obj('jar',430,442));e=[enemy(475,336,10),enemy(690,402,12),enemy(785,343,9)];}
if(z===4){o.push(obj('stone',173,307,{id:2}),obj('altar',351,250,{level:2}),obj('switch',667,165,{id:'spireSwitch'}),obj('heart',677,230,{id:'heart5',switch:'spireSwitch'}),obj('chest',800,430,{id:'cloak'}),obj('jar',267,437,{seal:true}),obj('cache',810,233,{id:'spireBonus',bonus:true}),obj('trap',522,350),obj('trap',652,413));e=[enemy(499,290,15,'brute'),enemy(675,320,13),enemy(745,396,18,'brute'),enemy(823,313,13)];}
if(z===5){o=o.filter(a=>a.type!=='exit');o.push(obj('switch',580,206,{id:'coreA'}),obj('switch',802,443,{id:'coreB'}),obj('beacon',856,294),obj('cache',227,233,{id:'finalCache'}),obj('trap',488,395));e=[enemy(698,331,64,'boss'),enemy(469,279,12),enemy(480,450,12)];}return {objects:o,enemies:e,plateUntil:0,solved:false,cleared:false};}
function notify(text,t=4){$('toast').textContent=text;toastTime=t;$('toast').style.opacity=1;}
function tone(type){if(!soundOn)return;try{audio??=new (window.AudioContext||window.webkitAudioContext)();audio.resume();let seq={step:[65,.055,'triangle'],metal:[140,.07,'square'],water:[310,.08,'sine'],swing:[180,.12,'sawtooth'],fire:[240,.16,'sawtooth'],solar:[80,.3,'triangle'],bolt:[820,.16,'triangle'],hit:[110,.12,'square'],hurt:[185,.22,'sawtooth'],coin:[1200,.09,'sine'],item:[550,.25,'sine'],rare:[330,.6,'triangle'],save:[260,.7,'sine'],buy:[780,.28,'sine'],seal:[430,.35,'sine'],shop:[95,.5,'sine']}[type]||[400,.2,'sine'];for(let i=0;i<(type==='rare'||type==='save'||type==='buy'?3:1);i++){let o=audio.createOscillator(),g=audio.createGain(),t=audio.currentTime+i*.1;o.type=seq[2];o.frequency.setValueAtTime(seq[0]*[1,1.25,1.5][i],t);o.frequency.exponentialRampToValueAtTime(seq[0]*(type==='swing'?.25:1.6),t+seq[1]);g.gain.setValueAtTime(.0001,t);g.gain.exponentialRampToValueAtTime(type==='step'?.025:.06,t+.012);g.gain.exponentialRampToValueAtTime(.0001,t+seq[1]);o.connect(g);g.connect(audio.destination);o.start(t);o.stop(t+seq[1]+.03);}}catch(e){}}
function burst(x,y,color,n=12){for(let i=0;i<n;i++){let a=rnd(0,Math.PI*2),v=rnd(15,100);particles.push({x,y,vx:Math.cos(a)*v,vy:Math.sin(a)*v-15,life:rnd(.3,.9),max:1,color,size:rnd(1,3)});}}
function textFX(x,y,text,color='#efba7d'){floats.push({x,y,text,color,life:1.3});}
function shimmer(){shimmerTime=2;}
function updateUI(){ $('zoneName').textContent=names[zone];$('chapter').textContent=chapters[zone];$('zoneCount').innerHTML=`0${zone+1} <span style="color:#53616a">/ 06</span>`;$('objective').textContent=hints[zone];$('bottomHint').textContent='◇ '+lore[zone];$('areaLabel').textContent=['SANCTUARY APPROACH','THE INDEX OF LOST STARS','THE EMBER ALTAR','THE DROWNED MERIDIAN','THE SOLAR ASCENT','THE CORE BEACON'][zone];$('areaSub').textContent=world[zone].cleared?'THE HALL FALLS QUIET':'THE CORROSION WAITS BEYOND';$('hearts').innerHTML=Array.from({length:p.max/2},(_,i)=>`<span style="color:${p.hp>=i*2+2?'#d9a478':p.hp===i*2+1?'#d9a478':'#394a50'}">${p.hp===i*2+1?'◐':'♥'}</span>`).join('');$('hpText').textContent=`${p.hp} / ${p.max} VITALITY`;$('gold').textContent=gold;$('weaponName').textContent=['Ember Blade','Ignited Edge','Solar Falchion'][p.weapon];$('weaponType').textContent=['I · FORGED STEEL','II · EMBER AWAKENED','III · SUN-FORGED LIGHT'][p.weapon];$('rangedName').textContent=p.ranged?'Aether Shard':'Undiscovered';$('rangedName').style.color=p.ranged?'#a1c8d4':'#697e85';$('rangedType').textContent=p.ranged?`${p.ammo} CHARGES · K TO FIRE`:'A DISTANT RESONANCE';$('rangedIcon').style.opacity=p.ranged?1:.3;$('journey').innerHTML=names.map((n,i)=>`<li class="${i===zone?'active':flags['visited'+i]?'done':''}"><i>${flags['visited'+i]&&i!==zone?'✓':i+1}</i>${n.replace('The ','')}</li>`).join('');}
function visible(o){return !o.gone&&(!o.behind||flags[o.behind])&&(!o.switch||flags[o.switch])&&(o.type!=='lantern'||flags.mirrorA&&flags.mirrorB)&&!(o.id==='heart5'&&flags.heart5);}
function gateReady(){let clear=world[zone].enemies.every(e=>e.dead);return clear&&(zone!==2||p.weapon>=1)&&(zone!==3||p.ranged&&flags.mirrorA&&flags.mirrorB)&&(zone!==4||p.weapon===2);}
function changeZone(z){zone=z;p.x=z>lastZone?110:860;p.y=360;flags['visited'+z]=true;bolts=[];particles=[];updateUI();notify(lore[z],4);lastZone=z;}
let lastZone=0;
function strike(){if(p.cool>0||modal||paused||win)return;p.cool=([.46,.29,.64][p.weapon])*(equipped.band?.83:1);attackFX={x:p.x,y:p.y,a:p.dir,life:.24,max:.24,level:p.weapon};tone(['swing','fire','solar'][p.weapon]);if(p.weapon===2){shake=3;flash=.15;}const range=[76,88,117][p.weapon];for(const e of world[zone].enemies){if(e.dead||dist(p,e)>range)continue;let a=Math.atan2(e.y-p.y,e.x-p.x)-p.dir;a=Math.atan2(Math.sin(a),Math.cos(a));if(Math.abs(a)>[1.3,1.4,1.9][p.weapon])continue;damageEnemy(e,[2,4,7][p.weapon]);}for(const o of world[zone].objects){if(!visible(o)||dist(p,o)>range)continue;if(o.type==='wall'){o.hp-=p.weapon===2?3:1;burst(o.x,o.y,'#a69376',12);tone('hit');if(o.hp<=0){flags[o.id]=true;o.gone=true;notify('Stone gives way. Something stirs beyond.');burst(o.x,o.y,'#b8beab',25);}else notify('The hairline cracks widen…',1.6);}if(o.type==='jar'){o.gone=true;addGold(15,o.x,o.y);if(o.seal){inv.seal+=2;notify('Binding Seal ×2 · Use from your satchel to still an enemy.');}else if(Math.random()<.6){inv.stem++;notify('Vital Stem found · I to open your satchel.');}burst(o.x,o.y,'#aa8b68');}}updateUI();}
function damageEnemy(e,d){if(e.type==='boss'&&!(flags.coreA&&flags.coreB)){burst(e.x,e.y,'#74c9ea');notify('Aether shields the Custodian. Strike both distant crystals.',2.5);tone('bolt');return;}e.hp-=d;e.hit=.25;e.freeze=Math.max(e.freeze,p.weapon===1?.25:.1);burst(e.x,e.y,p.weapon===2?'#ffe3a2':'#ed9b60',15);textFX(e.x,e.y-30,'−'+d);tone('hit');if(e.hp<=0){e.dead=true;addGold(Math.floor(rnd(5,16)),e.x,e.y);burst(e.x,e.y,'#dcc291',24);if(p.ranged)p.ammo+=3;if(e.type==='boss'){flags.boss=true;notify('The last sentinel kneels. The Beacon awaits your hand.',6);}if(world[zone].enemies.every(a=>a.dead)){world[zone].cleared=true;notify(zone===5?'The Core is yours. Approach the Beacon.':'The hall falls quiet. The way ahead is unsealed.',4);}}}
function addGold(n,x=p.x,y=p.y){gold+=n;textFX(x,y-15,'+'+n+' ◈','#ddc788');burst(x,y,'#e0bc64',10);tone('coin');}
function fire(){if(!p.ranged){notify('A distant resonance. Find its source in the Sunken Vault.',2);return;}if(p.cool>0||modal||paused)return;if(p.ammo<=0){notify('No Aether charges. Rest at a Sanctuary or visit the merchant.');return;}p.ammo--;p.cool=.35;let angle=p.dir;const candidates=[...world[zone].objects.filter(o=>o.type==='switch'&&!flags[o.id]),...world[zone].enemies.filter(e=>!e.dead)].filter(t=>{let a=Math.atan2(t.y-p.y,t.x-p.x)-angle;return Math.abs(Math.atan2(Math.sin(a),Math.cos(a)))<.45;}).sort((a,b)=>dist(p,a)-dist(p,b));if(candidates[0])angle=Math.atan2(candidates[0].y-p.y,candidates[0].x-p.x);bolts.push({x:p.x,y:p.y-8,vx:Math.cos(angle)*510,vy:Math.sin(angle)*510,life:1.9});tone('bolt');updateUI();}
function hurt(amount,trap=false){if(p.invul>0||win)return;if(trap&&equipped.cloak){burst(p.x,p.y,'#87a5bd',8);p.invul=1.2;notify('Shadow Cloak turns the iron thorns aside.',1.5);return;}p.hp=Math.max(0,p.hp-amount);p.invul=1.3;flash=.65;shake=7;burst(p.x,p.y,'#d9584f',23);textFX(p.x,p.y-35,'−'+amount,'#f48675');tone('hurt');updateUI();if(p.hp<=0)death();}
function heal(){if(inv.stem<1){notify('No Vital Stems remain.');return;}if(p.hp===p.max){notify('Your vitality is already full.');return;}inv.stem--;p.hp=Math.min(p.max,p.hp+4);burst(p.x,p.y,'#99cfaa',22);tone('item');notify('Vital Stem · Four vitality restored.');updateUI();}
function useSeal(){if(!inv.seal)return;let e=world[zone].enemies.filter(e=>!e.dead).sort((a,b)=>dist(p,a)-dist(p,b))[0];if(!e){notify('No construct answers the seal.');return;}inv.seal--;e.freeze=3.2;burst(e.x,e.y,'#8ccfef',25);tone('seal');notify('Binding Seal · The nearest construct is held for three seconds.');closeModal();}
function addHeart(id){if(flags[id])return;flags[id]=true;p.max=Math.min(10,p.max+2);p.hp=p.max;burst(p.x,p.y,'#ffdf8a',40);shimmer();tone('rare');notify('A heart of sunlight · Maximum vitality increased by two.',5);updateUI();}
function interact(){if(modal||paused||!near)return;const o=near;if(o.type==='stone'){p.hp=p.max;if(p.ranged)p.ammo=Math.max(18,p.ammo);flags['stone'+o.id]=true;save();burst(o.x,o.y-28,'#efdf9d',30);tone('save');notify('Memory inscribed · Vitality restored'+(p.ranged?' · Aether replenished.':'.'));}if(o.type==='shop'){if(!world[1].cleared){notify('The merchant will awaken when the archive guards fall.');return;}shop();}if(o.type==='exit'){if(gateReady()){if(zone===3)flags.vaultComplete=true;changeZone(zone+1);}else notify(zone===2?'The altar and its two plates must awaken; silence the constructs.':zone===3?'Find the Shard, awaken both mirror crystals, and clear the Vault.':zone===4?'Claim the Solar Falchion and defeat the Spire guard.':'Defeat the constructs to unseal the gate.');}if(o.type==='back')changeZone(zone-1);if(o.type==='altar'){if(p.weapon>=o.level)return;if(o.level===1&&!world[2].solved){notify('Two bronze plates. Two footsteps before the light fades.');return;}p.weapon=o.level;flags['upgrade'+o.level]=true;shimmer();burst(p.x,p.y,'#f7c578',40);tone('rare');notify(o.level===1?'Ignited Edge · Faster strikes leave an ember wake.':'Solar Falchion · A heavier, wider sweep of living sunlight.',6);}if(o.type==='chest'){o.gone=true;inv[o.id]=1;addGold(25);tone('rare');notify((o.id==='band'?'Warden’s Band':'Shadow Cloak')+' discovered · Equip it in your satchel.');burst(o.x,o.y,'#e7e8cb',20);}if(o.type==='cache'){o.gone=true;addGold(o.bonus?160:40);inv.stem+=o.bonus?2:1;if(p.ranged)p.ammo+=6;notify(o.bonus?'The Spire’s treasury · 160 Sigils, two Stems and six charges.':'A forgotten cache · 40 Sigils and a Vital Stem.');}if(o.type==='heart'){addHeart(o.id);o.gone=true;}if(o.type==='shard'){p.ranged=true;p.ammo=24;o.gone=true;flags.shard=true;burst(p.x,p.y,'#94d7ef',40);tone('rare');shimmer();notify('Aether Shard · K launches crystal bolts. Face the mirror crystals to awaken them.',7);}if(o.type==='lantern'){o.gone=true;inv.lantern=1;addGold(45);tone('rare');notify('Phantom Lantern · Equip to reveal the old paths. Reflection puzzle restored.',6);}if(o.type==='beacon'){if(!flags.boss){notify('The Custodian still binds the Beacon.');return;}win=true;flags.won=true;tone('save');shimmer();showEnding();}updateUI();}
function save(){saveData=JSON.parse(JSON.stringify({p,zone,world,gold,inv,equipped,flags,stock}));try{localStorage.setItem('wardens-corrosion-v1',JSON.stringify(saveData));$('saveStatus').textContent='MEMORY INSCRIBED · STONE '+(zone===0?'I':zone===1?'II':'III');}catch(e){$('saveStatus').textContent='MEMORY INSCRIBED · THIS SESSION';}}
function loadMemory(){if(!saveData)return;let d=JSON.parse(JSON.stringify(saveData));({p,zone,world,gold,inv,equipped,flags,stock}=d);p.hp=p.max;p.invul=2;p.cool=0;lastZone=zone;win=false;bolts=[];particles=[];closeModal();updateUI();notify('The stone remembers. Rise again, Warden.');}
function openModal(html){modal=true;keys={};$('modal').innerHTML=html;$('modalWrap').style.display='flex';}
function closeModal(){if(p.hp<=0)return;modal=false;$('modalWrap').style.display='none';canvas.focus();}
function modalHeader(eyebrow,title,desc){return `<div class="modal-head"><span class="eyebrow">${eyebrow}</span><button class="close" onclick="closeModal()" aria-label="Close">×</button></div><h2>${title}</h2><p class="desc">${desc}</p>`;}
const itemDefs={band:['◉','Warden’s Band','A silver ring. Strikes recover 17% faster.'],stem:['♧','Vital Stem','Restores 4 vitality · Two full hearts.'],lantern:['♧','Phantom Lantern','Soft blue light reveals forgotten paths and cracks.'],cloak:['◈','Shadow Cloak','Dark woven trim guards against environmental traps.'],seal:['❖','Binding Seal','Holds the nearest construct still for 3 seconds.']};
function inventory(){let html=modalHeader('THE WARDEN’S SATCHEL','What remains with you.',`${gold} Golden Sigils · ${p.hp}/${p.max} vitality · Accessories must be equipped by hand.`);for(let [id,d]of Object.entries(itemDefs)){html+=`<div class="item-row"><div class="item-symbol" style="${inv[id]?'':'opacity:.25'}">${d[0]}</div><div class="item-info"><h3>${d[1]}${id==='stem'||id==='seal'?' ×'+inv[id]:''}</h3><p>${inv[id]?d[2]:'Undiscovered · '+({band:'Rusted Archives',lantern:'Sunken Vault',cloak:'Twin Spire',stem:'Caches or merchant',seal:'Twin Spire or merchant'}[id])}</p></div><button ${!inv[id]?'disabled':''} onclick="itemAction('${id}')">${id==='stem'||id==='seal'?'USE':equipped[id]?'UNEQUIP':'EQUIP'}</button></div>`;}html+='<div class="modal-foot"><button class="primary" onclick="closeModal()">RETURN TO THE ASCENT</button></div>';openModal(html);}
function itemAction(id){if(!inv[id])return;if(id==='stem'){heal();inventory();}else if(id==='seal'){useSeal();if(modal)inventory();}else{equipped[id]=!equipped[id];tone('item');inventory();}}
function shop(){tone('shop');let html=modalHeader('THE MERCHANT OF LOST HOURS','Trade in small mercies.',`${gold} Golden Sigils · The altar is safe. Its offerings are finite.`);let items=[['stem','♧','Vital Stem',50,`${stock.stem} remain · Restores 4 vitality`],['seal','❖','Binding Seal',75,`${stock.seal} remain · Binds one enemy for 3 seconds`],['heart','♥','Heart of the Fifth Sun',300,flags.heart4?'A permanent fifth heart · Also hidden in the Twin Spire':'Requires the fourth heart from the Gallery'],['ammo','◇','Aether charge',10,flags.vaultComplete?'One crystalline charge':'Available after the Sunken Vault']];for(let [id,sym,title,cost,desc]of items){let available=id==='ammo'?flags.vaultComplete:id==='heart'?stock.heart&&!flags.heart5&&flags.heart4:stock[id]>0;html+=`<div class="item-row"><div class="item-symbol">${sym}</div><div class="item-info"><h3>${title}</h3><p>${desc}</p></div><button ${!available||gold<cost?'disabled':''} onclick="buy('${id}',${cost})">${id==='heart'&&flags.heart5?'CLAIMED':cost+' ◈'}</button></div>`;}html+=`<div class="modal-foot"><button onclick="farm()" style="padding:12px;font:10px Arial;letter-spacing:1px">REAWAKEN ARCHIVE GUARDS</button></div><p class="desc" style="margin:12px 0 0">Revisit combat for more Sigils. The sanctuary and merchant stay safe.</p>`;openModal(html);}
function buy(id,cost){if(gold<cost)return;if(id==='ammo'){if(!flags.vaultComplete)return;p.ammo++;}else if(id==='heart'){if(!stock.heart||flags.heart5||!flags.heart4)return;stock.heart--;addHeart('heart5');}else{if(!stock[id])return;stock[id]--;inv[id]++;}gold-=cost;tone('buy');burst(p.x,p.y,'#ead8a0');updateUI();shop();}
function farm(){world[1].enemies=[enemy(705,300,6),enemy(755,425,6),enemy(820,342,7)];closeModal();notify('The archive’s iron memories stir again.');}
function death(){openModal(`<div class="eyebrow">THE CORROSION TAKES HOLD</div><h2>Even light can falter.</h2><p class="desc">${saveData?'Your last Sanctuary Stone remembers you. Unsaved discoveries return to the halls.':'No stone holds your memory yet. The threshold calls you back.'}</p><button class="primary" onclick="${saveData?'loadMemory()':'restart()'}">${saveData?'RETURN TO THE STONE':'RISE AT THE THRESHOLD'}</button>`);}
function restart(){initial();lastZone=0;flags.visited0=true;bolts=[];particles=[];floats=[];attackFX=null;flash=0;shake=0;paused=false;$('pauseLabel').style.display='none';$('worldStatus').textContent='THE BEACON IS DORMANT';$('saveStatus').textContent=saveData?'A MEMORY REMAINS · GUIDE TO RESUME':'NO MEMORY INSCRIBED';closeModal();updateUI();notify('Move with WASD · E to rest at the glowing Sanctuary Stone.',6);}
function guide(){openModal(modalHeader('FIELD NOTES · CONTROLS','A light worth carrying.','Explore six connected halls. Nothing is restored without being found.')+`<div class="help-grid"><div><b>Walk & fight</b>WASD / arrows to move. J swings toward your facing. Space evades with brief invulnerability. K fires Aether after its discovery. Aim by facing a target, or click the scene to swing toward it.</div><div><b>Remember & recover</b>E interacts nearby. Exactly three Sanctuary Stones rest in the Threshold, Archives and Spire. They restore vitality, refill Aether and save locally. Health never regenerates on its own.</div><div><b>Follow the fractures</b>Strike cracked walls and jars. Cross the Gallery’s two bronze plates within 3 seconds. Seek the Shard behind the Vault’s northern cracks, then fire at both mirror crystals.</div><div><b>Prepare for the ascent</b>I opens the satchel; equip relics and use supplies. H quickly uses a Stem. B uses a Seal. Return west to the Archive merchant. Escape pauses. M toggles sound.</div></div><div class="modal-foot"><button class="primary" onclick="closeModal()">CONTINUE EXPLORING</button>${saveData?'<button style="padding:12px" onclick="loadMemory()">LOAD LAST MEMORY</button>':''}<button style="padding:12px" onclick="confirmRestart()">NEW JOURNEY</button></div>`);}
function confirmRestart(){openModal('<div class="eyebrow">A NEW MEMORY</div><h2>Begin again?</h2><p class="desc">Your current journey will be reset. The old saved memory remains until you rest at a stone.</p><div class="modal-foot"><button class="primary" onclick="restart()">BEGIN AGAIN</button><button onclick="guide()" style="padding:12px">GO BACK</button></div>');}
function showEnding(){openModal(`<div class="eyebrow">VI · THE CORROSION RECEDES</div><h2>And the sun remembered.</h2><p class="desc" style="font:16px/1.9 Georgia;color:#c7cbbb">For a moment, every broken instrument points toward the light.<br>The gears turn. Beneath the clouds, the valley sees its first dawn.<br><br>You were not the last Warden of a dying world.<br>You were the first of the world that followed.</p><div class="item-row"><div class="item-symbol">☼</div><div class="item-info"><h3>The Core Beacon is restored</h3><p>${p.max/2} hearts · ${gold} Sigils remaining · Two ancient blades rekindled</p></div></div><div class="modal-foot"><button class="primary" onclick="closeModal()">WITNESS THE DAWN</button><button style="padding:12px" onclick="restart()">NEW JOURNEY</button></div>`);$('worldStatus').textContent='THE BEACON BURNS AGAIN';}

function update(dt){elapsed+=dt;toastTime-=dt;$('toast').style.opacity=toastTime>0?1:0;shimmerTime=Math.max(0,shimmerTime-dt);$('shimmer').style.opacity=shimmerTime>0?.6+Math.sin(elapsed*4)*.25:0;if(modal||paused)return;flash=Math.max(0,flash-dt*1.6);shake=Math.max(0,shake-dt*22);p.cool=Math.max(0,p.cool-dt);p.invul=Math.max(0,p.invul-dt);p.dash=Math.max(0,p.dash-dt);if(attackFX){attackFX.life-=dt;if(attackFX.life<=0)attackFX=null;}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 len=Math.hypot(dx,dy);dx/=len;dy/=len;p.dir=Math.atan2(dy,dx);let speed=p.dash>0?450:158;p.x+=dx*speed*dt;p.y+=dy*speed*dt;p.x=Math.max(101,Math.min(922,p.x));p.y=Math.max(214,Math.min(482,p.y));for(const o of world[zone].objects){if(o.type==='wall'&&!o.gone&&dist(p,o)<32){let a=Math.atan2(p.y-o.y,p.x-o.x);p.x=o.x+Math.cos(a)*32;p.y=Math.max(214,o.y+Math.sin(a)*32);}}footTime-=dt;if(footTime<0){tone(zone===1?'metal':zone===3?'water':'step');footTime=p.dash>0?.13:.34;if(p.weapon>0)burst(p.x,p.y+8,p.weapon===1?'#d67c40':'#e4c47a',2);}}
if(zone===2&&!world[2].solved&&p.x>550)p.x=550;if(keys.j)strike();if(keys.k)fire();let wz=world[zone];for(const e of wz.enemies){if(e.dead)continue;e.cool-=dt;e.hit=Math.max(0,e.hit-dt);if(e.freeze>0){e.freeze-=dt;continue;}let d=dist(e,p);let safe=wz.objects.some(o=>(o.type==='stone'||o.type==='shop')&&dist(p,o)<85);if(d<310&&!safe&&!win){if(d>34){let s=e.type==='boss'?45:e.type==='brute'?46:64;e.x+=(p.x-e.x)/d*s*dt;e.y+=(p.y-e.y)/d*s*dt;}else if(e.cool<=0){hurt(e.type==='brute'||e.type==='boss'?2:1);e.cool=e.type==='boss'?1.45:1.15;}if(e.type==='boss'&&e.cool<=0&&d>80){e.cool=2.3;let a=Math.atan2(p.y-e.y,p.x-e.x);bolts.push({x:e.x,y:e.y-10,vx:Math.cos(a)*190,vy:Math.sin(a)*190,life:4,hostile:true});tone('solar');}}}
for(const o of wz.objects){if(o.type==='plate'&&dist(p,o)<28){if(o.id==='plateA'&&!wz.solved){wz.plateUntil=elapsed+3;if(!flags.plateNotice){notify('First plate lowered. Reach its twin before the glow fades.',3);flags.plateNotice=true;}}if(o.id==='plateB'&&wz.plateUntil>elapsed&&!wz.solved){wz.solved=true;addGold(35,o.x,o.y);tone('rare');notify('The bronze bridge settles. The ember altar is awake.',5);}}if(o.type==='trap'&&Math.sin(elapsed*2+o.x)>.35&&dist(p,o)<26)hurt(1,true);}
for(let i=bolts.length-1;i>=0;i--){let b=bolts[i];b.x+=b.vx*dt;b.y+=b.vy*dt;b.life-=dt;if(b.hostile){if(dist(b,p)<24){hurt(2);b.life=0;}}else{for(const o of wz.objects){if(o.type==='switch'&&!flags[o.id]&&dist(b,o)<28){flags[o.id]=true;b.life=0;burst(o.x,o.y,'#a5e0ef',30);tone('rare');notify(zone===3?(flags.mirrorA&&flags.mirrorB?'The reflected light converges. A Phantom Lantern takes form.':'One mirror sings. A second waits in the dark.'):zone===4?'The distant latch opens. A heart of sunlight is revealed.':'A shield anchor shatters.',4);if(zone===3&&flags.mirrorA&&flags.mirrorB)addGold(25,o.x,o.y);}if(o.type==='wall'&&!o.gone&&dist(b,o)<25){o.hp-=2;b.life=0;burst(o.x,o.y,'#a4c9d1');if(o.hp<=0){o.gone=true;flags[o.id]=true;}}}for(const e of wz.enemies){if(!e.dead&&dist(b,e)<(e.type==='boss'?31:23)){damageEnemy(e,3);b.life=0;break;}}}if(b.life<=0){burst(b.x,b.y,b.hostile?'#da8a64':'#91d4e9',8);bolts.splice(i,1);}}
near=null;let nearest=66;for(const o of wz.objects){if(!visible(o)||['plate','trap','wall','switch','jar'].includes(o.type)||o.type==='altar'&&p.weapon>=o.level)continue;let d=dist(p,o);if(d<nearest){near=o;nearest=d;}}const labels={stone:'Rest & inscribe memory',shop:'Trade with the merchant',exit:gateReady()?'Enter '+(names[zone+1]||'the light'):'Inspect the sealed gate',back:'Return to '+names[zone-1],altar:'Rekindle the weapon altar',chest:'Open forgotten chest',cache:'Search the cache',heart:'Claim the heart of sunlight',shard:'Take the Aether Shard',lantern:'Take the Phantom Lantern',beacon:'Reignite the Core Beacon'};$('prompt').style.display=near?'block':'none';if(near)$('prompt').innerHTML='<b>[ E ]</b>'+labels[near.type];
for(let i=particles.length-1;i>=0;i--){let q=particles[i];q.x+=q.vx*dt;q.y+=q.vy*dt;q.vy-=4*dt;q.life-=dt;if(q.life<=0)particles.splice(i,1);}for(let i=floats.length-1;i>=0;i--){floats[i].y-=20*dt;floats[i].life-=dt;if(floats[i].life<=0)floats.splice(i,1);}if(Math.random()<dt*8){for(const o of wz.objects)if(o.type==='stone')particles.push({x:o.x+rnd(-24,24),y:o.y-15,vx:rnd(-3,3),vy:-rnd(8,25),life:1.5,max:1.5,color:'#e7d3a0',size:1.5});}updateMinorUI();}
function updateMinorUI(){$('hpText').textContent=`${p.hp} / ${p.max} VITALITY`;$('gold').textContent=gold;if(p.ranged)$('rangedType').textContent=`${p.ammo} CHARGES · K TO FIRE`;}
function poly(points,fill,stroke,width=1){ctx.beginPath();points.forEach((p,i)=>i?ctx.lineTo(...p):ctx.moveTo(...p));ctx.closePath();if(fill){ctx.fillStyle=fill;ctx.fill();}if(stroke){ctx.strokeStyle=stroke;ctx.lineWidth=width;ctx.stroke();}}
function line(points,col,width=1){ctx.beginPath();points.forEach((p,i)=>i?ctx.lineTo(...p):ctx.moveTo(...p));ctx.strokeStyle=col;ctx.lineWidth=width;ctx.stroke();}
function ellipse(x,y,rx,ry,fill,stroke){ctx.beginPath();ctx.ellipse(x,y,rx,ry,0,0,Math.PI*2);if(fill){ctx.fillStyle=fill;ctx.fill();}if(stroke){ctx.strokeStyle=stroke;ctx.lineWidth=1;ctx.stroke();}}
function glow(x,y,r,color){let g=ctx.createRadialGradient(x,y,0,x,y,r);g.addColorStop(0,color);g.addColorStop(1,'transparent');ctx.fillStyle=g;ctx.fillRect(x-r,y-r,r*2,r*2);}
function gear(x,y,r,rotation,col,opacity=1){ctx.save();ctx.globalAlpha=opacity;ctx.translate(x,y);ctx.rotate(rotation);let pts=[];for(let i=0;i<64;i++){let a=i/64*Math.PI*2,rr=r*(i%4===0||i%4===3?.85:1);pts.push([Math.cos(a)*rr,Math.sin(a)*rr]);}poly(pts,col,'#b6a16b42',1.3);ellipse(0,0,r*.71,r*.71,'#101f28','#ae986c55');ellipse(0,0,r*.6,r*.6,null,'#ae986c40');for(let i=0;i<8;i++){ctx.save();ctx.rotate(i*Math.PI/4);poly([[r*.12,-4],[r*.69,-8],[r*.69,8],[r*.12,4]],col,'#a38c6240');ctx.restore();}ellipse(0,0,r*.22,r*.22,col,'#baa17166');ellipse(0,0,r*.1,r*.1,'#101b23','#b49f6955');ctx.restore();}
let seed=4;function random(){seed=(seed*1664525+1013904223)>>>0;return seed/4294967296;}
function pillar(x,y,h=125,broken=false){ellipse(x+10,y+6,30,12,'#030b104d');poly([[x-21,y-6],[x,y-17],[x+23,y-5],[x+2,y+9]],'#5b5e53','#9b967146');poly([[x-21,y-6],[x+2,y+9],[x+2,y+20],[x-21,y+5]],'#2a3a3c','#8e886336');poly([[x+2,y+9],[x+23,y-5],[x+23,y+7],[x+2,y+20]],'#3e4a47');poly([[x-14,y-9],[x-14,y-h],[x,y-h+7],[x,y+1]],'#3f504e','#8d916956');poly([[x,y+1],[x,y-h+7],[x+14,y-h],[x+14,y-9]],'#263b40','#8f95643d');for(let i=0;i<3;i++)line([[x-11+i*4,y-15],[x-11+i*4,y-h+9]],'#a4a37a25');if(broken){poly([[x-17,y-h],[x-8,y-h-10],[x-2,y-h+1],[x+5,y-h-6],[x+17,y-h],[x,y-h+9]],'#6c7260','#9eaa8147');}else{poly([[x-23,y-h-8],[x,y-h-19],[x+23,y-h-7],[x,y-h+7]],'#737564','#aeab7e66');poly([[x-23,y-h-8],[x,y-h+7],[x,y-h+15],[x-23,y-h]],'#455450');poly([[x,y-h+7],[x+23,y-h-7],[x+23,y-h+1],[x,y-h+15]],'#2b4247');}for(let i=0;i<8;i++){let xx=x-15+random()*27,yy=y-14+random()*20;ellipse(xx,yy,random()*7+2,2,'#536c494d');}}
function architecture(){seed=zone+25;let bg=ctx.createLinearGradient(0,0,0,H);bg.addColorStop(0,zone===3?'#101f2c':'#152831');bg.addColorStop(.55,'#253b43');bg.addColorStop(1,'#0c1822');ctx.fillStyle=bg;ctx.fillRect(0,0,W,H);glow(610,160,410,zone===5&&win?'#e6b96150':'#d8b87816');for(let i=0;i<90;i++){let x=random()*W,y=random()*H;ellipse(x,y,random()*.8+.25,random()*.8+.25,'#d1d6bb44');}
// The storm valley, far below the archive.
for(let j=0;j<4;j++){let pts=[[0,H]];for(let x=0;x<=W;x+=35)pts.push([x,310+j*60+random()*85]);pts.push([W,H]);poly(pts,['#172d39','#132935','#122531','#0c1c29'][j]);}for(let i=0;i<10;i++){let x=random()*W,y=330+random()*240;ellipse(x,y,130+random()*150,12+random()*20,'#68828d08');}
// Suspended chains and the great ruined orrery.
for(const x of [115,847]){line([[x,0],[x+3,188]],'#090f14',7);for(let y=-8;y<192;y+=15){ctx.beginPath();ctx.ellipse(x,y,4,10,.15,0,Math.PI*2);ctx.strokeStyle='#7e80634d';ctx.lineWidth=2;ctx.stroke();}}
gear(805,138,117,-.15,'#45534c',.65);gear(926,199,65,.2,'#3b4e4c',.65);gear(169,495,93,.1,'#354740',.75);
ctx.save();ctx.translate(507,181);ctx.scale(1,.72);for(let r of [147,157,188]){ctx.beginPath();ctx.arc(0,0,r,Math.PI*1.03,Math.PI*1.93);ctx.strokeStyle=r===157?'#b99d606e':'#59665899';ctx.lineWidth=r===157?3:9;ctx.stroke();}for(let i=0;i<15;i++){let a=Math.PI*1.03+i/14*Math.PI*.9;line([[Math.cos(a)*154,Math.sin(a)*154],[Math.cos(a)*180,Math.sin(a)*180]],'#a28e5e5c',2);}ctx.restore();
// The floating foundation and cut bronze floor.
poly([[72,256],[238,148],[872,148],[947,238],[947,466],[799,556],[72,556]],'#15272d','#73807450',2);poly([[72,506],[799,506],[947,420],[947,466],[799,576],[72,576]],'#132329','#70725366');poly([[72,535],[799,535],[947,450],[947,477],[799,590],[72,590]],'#0d1d25','#53605266');for(let x=98;x<798;x+=55)line([[x,541],[x,578]],'#55615450',2);poly([[72,245],[238,145],[871,145],[947,233],[947,435],[799,535],[72,535]],'#394b4a','#958e664a',2);poly([[90,258],[246,164],[860,164],[927,240],[927,426],[793,513],[90,513]],zone===3?'#2d454a':'#344748','#a1936855');
// Individually scored slabs.
ctx.save();ctx.beginPath();[[91,258],[246,164],[860,164],[927,240],[927,426],[793,513],[91,513]].forEach((a,i)=>i?ctx.lineTo(...a):ctx.moveTo(...a));ctx.closePath();ctx.clip();for(let row=0;row<9;row++){let y=166+row*44;for(let col=-1;col<13;col++){let x=col*91+(row%2)*45;ctx.fillStyle=`rgba(${random()>.4?'151,151,112':'18,33,37'},${random()*.08})`;ctx.fillRect(x+2,y+2,87,40);ctx.strokeStyle='#142d3266';ctx.lineWidth=1;ctx.strokeRect(x,y,91,44);line([[x+2,y+1],[x+87,y+1]],'#b2b49414');if(random()>.78)line([[x+35,y],[x+42,y+17],[x+33,y+29],[x+51,y+44]],'#132c3299');}}
for(let y of [268,457]){line([[75,y],[930,y]],'#b5a27140',3);line([[75,y+6],[930,y+6]],'#182e3488');for(let x=100;x<930;x+=27)poly([[x,y-2],[x+4,y-5],[x+8,y-2],[x+4,y+1]],'#b1a17942');}
if(zone===1){for(let x=150;x<840;x+=150){ctx.fillStyle='#152b3199';ctx.fillRect(x,335,100,73);for(let y=339;y<407;y+=8)line([[x,y],[x+100,y]],'#8d927254',2);for(let xx=x+5;xx<x+100;xx+=14)line([[xx,335],[xx,408]],'#0e2028',2);}}
if(zone===3){for(let i=0;i<16;i++){let x=130+random()*700,y=250+random()*210;ellipse(x,y,25+random()*60,6+random()*14,'#62929b12','#92c2c117');}}
// The meridian etched in the stone.
ctx.save();ctx.translate(533,346);ctx.scale(1,.54);for(let r of [100,108,127]){ellipse(0,0,r,r,null,'#bca66a40');}for(let i=0;i<24;i++){let a=i*Math.PI/12;line([[Math.cos(a)*109,Math.sin(a)*109],[Math.cos(a)*125,Math.sin(a)*125]],'#b6a26e50');}poly([[0,-90],[17,-18],[82,0],[17,18],[0,90],[-17,18],[-82,0],[-17,-18]],null,'#b6a37340');ellipse(0,0,21,21,null,'#b6a37340');ctx.restore();ctx.restore();
// Ruined north wall.
poly([[219,169],[219,112],[877,112],[905,164],[865,194],[240,194]],'#293d41','#73846b52');for(let i=0;i<12;i++){let x=230+i*55;poly([[x,140],[x,108+random()*9],[x+49,109+random()*8],[x+49,163],[x+4,185]],i%3?'#3b504f':'#42554f','#7a89694b');line([[x+4,148],[x+44,148]],'#142f3580');}for(let i=0;i<33;i++){let x=240+random()*620,y=178+random()*20;ellipse(x,y,random()*16+3,random()*4+1,'#5a70443f');}
pillar(242,217,115);pillar(428,209,58,true);pillar(649,211,102,true);pillar(864,220,139);
// A broken telescope points at a sky it can no longer measure.
ctx.save();ctx.translate(355,183);ctx.rotate(-.37);poly([[-55,-13],[53,-20],[64,17],[-56,10]],'#647368','#b2a67b70',2);poly([[-51,-9],[50,-14],[50,-5],[-51,3]],'#87907a55');for(let x of [-44,-4,42]){ctx.fillStyle='#283f42';ctx.fillRect(x,-17,6,34);line([[x+1,-17],[x+1,17]],'#c1a97166',2);}ellipse(58,-1,8,20,'#152e35','#b29a6888');ctx.restore();line([[355,193],[331,230]],'#667260',7);line([[358,193],[379,231]],'#526456',6);
if(zone===1){for(const x of [275,530,745]){poly([[x-42,226],[x-42,180],[x+36,180],[x+36,226]],'#1a3035','#8f8c6466');for(let j=0;j<2;j++){let yy=182+j*23;for(let i=0;i<9;i++){ctx.fillStyle=['#8b7452','#616f58','#8f8b67','#536b69'][Math.floor(random()*4)];ctx.fillRect(x-38+i*8,yy+3+random()*4,5,17);}line([[x-40,yy+23],[x+36,yy+23]],'#a08e5b66',3);}}}
if(zone===2){poly([[370,220],[399,208],[491,232],[477,251]],'#526057','#89967466');for(let i=0;i<4;i++)poly([[402+i*14,220],[414+i*14,216],[435+i*14,237],[422+i*14,241]],'#43574f','#a5ab784a');}
// Edge rubble and moss, kept out of the traversal lane.
for(let i=0;i<70;i++){let x=110+random()*768,y=random()>.5?488+random()*22:215+random()*31;if(x>130&&x<230&&y<270)continue;let r=2+random()*9;poly([[x-r,y],[x-3,y-r*.6],[x+r,y-r*.4],[x+r*.7,y+r*.5],[x-r*.6,y+r*.4]],['#526358','#47554c','#647064','#2c4442'][Math.floor(random()*4)],'#82907533');}for(let i=0;i<25;i++){let x=100+random()*740,y=507+random()*13;line([[x,y],[x+7,y+16+random()*24],[x+3,y+32+random()*20]],'#526d4c70',2);}
pillar(101,480,77,true);pillar(839,490,60,true);
// Tarnished railing catches the last sunlight.
for(let x=227;x<780;x+=46){line([[x,522],[x,493]],'#243a3d',5);ellipse(x,491,4,3,'#899071');}line([[227,493],[779,493]],'#7c846664',3);line([[227,501],[779,501]],'#142c3280',2);
}
let backgrounds=[];
function getBackground(){if(!backgrounds[zone]){let cache=document.createElement('canvas');cache.width=W;cache.height=H;architecture();cache.getContext('2d').drawImage(canvas,0,0,W,H);backgrounds[zone]=cache;}return backgrounds[zone];}
function pedestal(x,y,col='#807c5d'){ellipse(x+3,y+10,35,13,'#05141b66');poly([[x-28,y],[x,y-14],[x+28,y],[x,y+15]],col,'#b4aa7d70');poly([[x-28,y],[x,y+15],[x,y+23],[x-28,y+7]],'#354744','#8a8f6150');poly([[x,y+15],[x+28,y],[x+28,y+7],[x,y+23]],'#233d40');}
function crystal(x,y,color='#add9db',size=15){glow(x,y,size*3,color+'30');poly([[x,y-size],[x+size*.52,y],[x,y+size],[x-size*.52,y]],color,'#dcf3de99');poly([[x,y-size],[x,y+size],[x-size*.52,y]],'#477f9099');line([[x,y-size],[x+size*.52,y]],'#edfff0',1.4);}
function drawObject(o){if(!visible(o))return;const x=o.x,y=o.y;ctx.save();let bob=Math.sin(elapsed*2+x)*3;
if(o.type==='stone'){glow(x,y-30,88,'#eecb7330');ellipse(x,y+10,45,21,null,'#d4c28136');pedestal(x,y,'#777e68');poly([[x-17,y-3],[x-15,y-55],[x+1,y-64],[x+16,y-53],[x+18,y-4],[x,y+6]],'#859485','#d3d2a08c');poly([[x+1,y-64],[x+16,y-53],[x+18,y-4],[x,y+6]],'#4d6869');line([[x,y-53],[x,y-12]],'#f3dda2',2);poly([[x,y-48],[x+8,y-36],[x,y-25],[x-8,y-36]],null,'#fae6aa',1.5);line([[x-8,y-19],[x+8,y-19]],'#e3d293',2);ellipse(x,y-72+bob,10,3,'#e6dbab44','#ebd99388');glow(x,y-34,30,'#ffe5a138');label(x,y+37,flags['stone'+o.id]?'MEMORY INSCRIBED':'SANCTUARY', '#d8c493');}
if(o.type==='exit'||o.type==='back'){let ready=o.type==='back'||gateReady();pedestal(x,y+9,'#52635a');poly([[x-23,y+1],[x-23,y-66],[x-10,y-87],[x+10,y-87],[x+23,y-66],[x+23,y+1]],'#132f3799',ready?'#c3b57966':'#895e4f77',4);ctx.beginPath();ctx.moveTo(x-16,y);ctx.lineTo(x-16,y-62);ctx.quadraticCurveTo(x,y-100,x+16,y-62);ctx.lineTo(x+16,y);ctx.strokeStyle=ready?'#c4bf8770':'#8c756065';ctx.lineWidth=2;ctx.stroke();if(ready){glow(x,y-30,55,'#c4d6b82a');for(let i=0;i<5;i++)line([[x-10+i*5,y-6],[x-10+i*5,y-60]],'#d6daa61c');}else{for(let yy=y-58;yy<y;yy+=13)line([[x-16,yy],[x+16,yy+15]],'#ad7b5966',2);}label(x,y+39,o.type==='back'?'RETURN':ready?'ASCEND':'SEALED',ready?'#c9be90':'#a9866e');}
if(o.type==='jar'){ellipse(x,y+4,13,6,'#07161b66');poly([[x-8,y-21],[x+7,y-21],[x+7,y-14],[x+13,y-6],[x+9,y+3],[x-9,y+3],[x-13,y-6],[x-7,y-14]],'#87765b','#b8a77a66');ellipse(x,y-21,9,3,'#273e3e','#ae9e7155');line([[x-3,y-13],[x+3,y-6],[x,y+1]],'#3a443f');}
if(o.type==='wall'){pedestal(x,y,'#4a5650');poly([[x-26,y],[x-26,y-46],[x-15,y-61],[x+22,y-52],[x+27,y-5],[x,y+8]],'#3a4d4b','#71846b80');poly([[x-26,y-46],[x-15,y-61],[x+22,y-52],[x+9,y-38]],'#617366','#91957866');line([[x-5,y-50],[x+2,y-37],[x-8,y-25],[x+3,y-14],[x-2,y+2]],equipped.lantern?'#9cdff2':'#b2a181',1.4);line([[x+2,y-37],[x+13,y-32],[x+17,y-19]],equipped.lantern?'#9cdff2':'#a59b7a',1);if(equipped.lantern)glow(x,y-22,45,'#8abbe12a');}
if(o.type==='altar'){let acquired=p.weapon>=o.level,active=o.level===2||world[2].solved;glow(x,y-26,70,active?'#e8a84b2a':'#7da4ab12');pedestal(x,y,'#676a54');poly([[x-18,y],[x-18,y-24],[x+18,y-24],[x+18,y],[x,y+10]],'#485b54','#b5a27577');ellipse(x,y-25,21,9,'#6d7055','#c9a46b88');if(!acquired){ctx.save();ctx.translate(x,y-41+bob);ctx.rotate(.2);drawSword(0,0,o.level,1.1);ctx.restore();}label(x,y+38,acquired?'ALTAR RESTORED':o.level===1?'IGNITED EDGE':'SOLAR FALCHION',active?'#d6b278':'#89958c');}
if(o.type==='plate'){let active=world[zone].solved||o.id==='plateA'&&world[zone].plateUntil>elapsed;ellipse(x,y+3,32,16,'#142b30');ellipse(x,y,30,15,active?'#9c9258':'#625f47',active?'#e0c17f':'#a89c6966');ellipse(x,y,22,10,null,active?'#e6c784':'#a18e5a');poly([[x,y-7],[x+11,y],[x,y+7],[x-11,y]],null,active?'#ffe4a1':'#b8a26b');if(active)glow(x,y,35,'#e4bd6528');}
if(o.type==='switch'){let active=flags[o.id];ellipse(x,y+6,25,12,'#142a31','#8eb0ad40');pedestal(x,y+1,'#445958');crystal(x,y-23+(active?0:bob),active?'#d9c686':'#97c9df',active?12:17);if(!active){ctx.save();ctx.translate(x,y-23);ctx.rotate(elapsed*.4);poly([[-26,0],[0,-26],[26,0],[0,26]],null,'#9ecad355');ctx.restore();}label(x,y+33,active?'ALIGNED':'AETHER RESONANCE',active?'#bea975':'#8cb8c5');}
if(o.type==='heart'){glow(x,y-15,50,'#ecc67440');pedestal(x,y,'#696d54');ctx.fillStyle='#f6cf88';ctx.font='31px Georgia';ctx.textAlign='center';ctx.shadowColor='#efb954';ctx.shadowBlur=20;ctx.fillText('♥',x,y-15+bob);ctx.shadowBlur=0;}
if(o.type==='shard'||o.type==='lantern'){pedestal(x,y,'#526763');glow(x,y-25,70,'#8dc7e335');crystal(x,y-29+bob,'#a8e0ef',o.type==='shard'?20:13);if(o.type==='lantern'){ctx.strokeStyle='#b7bf99';ctx.lineWidth=2;ctx.strokeRect(x-13,y-47+bob,26,34);ellipse(x,y-49+bob,8,5,null,'#b7bf99');}label(x,y+38,o.type==='shard'?'AETHER SHARD':'PHANTOM LANTERN','#b2d5dc');}
if(o.type==='chest'||o.type==='cache'){ellipse(x,y+7,24,9,'#091c2266');poly([[x-21,y-6],[x-21,y-22],[x+13,y-22],[x+22,y-14],[x+22,y+6],[x-12,y+6]],o.bonus?'#a78b4f':'#726346','#bca36a77');poly([[x-21,y-6],[x+12,y-6],[x+22,y-14],[x+22,y+6],[x-12,y+6]],'#3b4940','#b09c6855');line([[x-12,y-23],[x-12,y+5]],'#c0a66b',3);line([[x+9,y-22],[x+9,y+5]],'#ad965e',3);ctx.fillStyle='#d3b776';ctx.fillRect(x-3,y-7,7,7);if(equipped.lantern||o.bonus)glow(x,y-7,40,'#dab87624');}
if(o.type==='shop'){glow(x,y-25,86,'#dda76322');pedestal(x,y,'#6d6d54');poly([[x-31,y-27],[x,y-42],[x+33,y-25],[x,y-9]],'#918360','#bbab7a99');poly([[x-31,y-27],[x,y-9],[x,y+9],[x-31,y-8]],'#3e4d42','#8e845953');poly([[x,y-9],[x+33,y-25],[x+33,y-7],[x,y+9]],'#4f5847');line([[x,y-40],[x,y-80]],'#c0a372',2);line([[x-22,y-70],[x+22,y-70]],'#c0a372',2);for(let xx of [x-22,x+22]){line([[xx,y-70],[xx-9,y-50],[xx+9,y-50],[xx,y-70]],'#b49d6d');ellipse(xx,y-50,10,3,'#a79a6a');}crystal(x,y-84+bob,'#e3c286',7);label(x,y+34,'MERCHANT ALTAR','#d4b783');}
if(o.type==='trap'){let active=Math.sin(elapsed*2+x)>.35;ellipse(x,y,25,13,'#162b2f','#9b856042');for(let i=0;i<5;i++){let xx=x-16+i*8;poly([[xx-3,y+3],[xx,y-(active?18:3)],[xx+4,y+3]],active?'#ab8166':'#516059','#b5926955');}if(active)glow(x,y,30,'#d2764320');}
if(o.type==='beacon'){glow(x,y-55,win?260:90,win?'#ffd39188':'#8ca5ad22');pedestal(x,y,'#77775b');pedestal(x,y-14,'#6e7158');for(let s of [-1,1]){poly([[x+s*23,y-22],[x+s*30,y-90],[x+s*20,y-108],[x+s*14,y-29]],'#708075','#d3c69188');}crystal(x,y-78+Math.sin(elapsed)*4,win?'#ffe4a2':'#67828b',32);ellipse(x,y-85,57,18,null,win?'#ffe09b':'#a39c7088');ellipse(x,y-69,48,18,null,win?'#efc472':'#a39c7055');if(win){let g=ctx.createLinearGradient(x,0,x,y);g.addColorStop(0,'#ffe3a105');g.addColorStop(1,'#ffe3a199');poly([[x-36,0],[x+36,0],[x+13,y-76],[x-13,y-76]],g);}}
ctx.restore();}
function label(x,y,t,col){ctx.font='8px Arial';ctx.letterSpacing='1.4px';ctx.textAlign='center';ctx.fillStyle=col;ctx.fillText(t,x,y);ctx.letterSpacing='0px';}
function drawSword(x,y,level,scale=1){ctx.save();ctx.translate(x,y);ctx.scale(scale,scale);let c=['#c4c5aa','#eeac64','#ffdf8e'][level];if(level){ctx.shadowColor=c;ctx.shadowBlur=level===1?10:19;}poly([[-3,-7],[-4,-36],[0,-47],[4,-36],[3,-7]],c,'#fff0be80');poly([[0,-47],[4,-36],[3,-7],[0,-7]],level===0?'#657f84':level===1?'#b96538':'#c79944');ctx.shadowBlur=0;line([[-9,-7],[9,-7]],'#bda275',3);line([[0,-5],[0,6]],'#8b6d4f',4);ellipse(0,7,3,3,'#baa376');ctx.restore();}
function drawPlayer(){let x=p.x,y=p.y,bob=(keys.w||keys.a||keys.s||keys.d||keys.ArrowUp||keys.ArrowDown||keys.ArrowLeft||keys.ArrowRight)&&!modal?Math.sin(elapsed*12)*2:Math.sin(elapsed*2)*.7;ctx.save();if(p.invul>0&&Math.floor(elapsed*14)%2)ctx.globalAlpha=.6;ellipse(x,y+11,p.weapon===2?20:16,7,'#06141ba0');if(p.weapon)glow(x+19,y-17,50,p.weapon===1?'#eaa2581e':'#edd18a25');if(equipped.lantern)glow(x,y-15,100,'#8ecdeb1e');if(win)glow(x,y-20,100,'#ffda8933');let step=Math.sin(elapsed*12)*(keys.w||keys.a||keys.s||keys.d||keys.ArrowUp||keys.ArrowDown||keys.ArrowLeft||keys.ArrowRight?4:0),stance=p.weapon===2?10:6;line([[x-4,y-1],[x-stance-step,y+11]],'#132932',6);line([[x+5,y-1],[x+stance+step,y+11]],'#132932',6);line([[x-stance-step-2,y+12],[x-stance-step+4,y+12]],'#a39d79',3);line([[x+stance+step-2,y+12],[x+stance+step+5,y+12]],'#a39d79',3);let hem=equipped.cloak?'#141e30':'#b69b64';poly([[x-9,y-36+bob],[x+9,y-36+bob],[x+17,y+3],[x+8,y+7],[x,y+4],[x-13,y+8],[x-17,y+2]],'#a49166','#d6c59665');poly([[x-9,y-36+bob],[x-3,y-18],[x-6,y+3],[x-13,y+8],[x-17,y+2]],'#59685b');poly([[x+6,y-33+bob],[x+10,y-12],[x+17,y+3],[x+8,y+7],[x+3,y+3]],'#c7ae79');line([[x-16,y+3],[x-12,y+8],[x,y+4],[x+8,y+7],[x+16,y+3]],p.weapon===2?'#f3d98c':hem,equipped.cloak?5:2);poly([[x-9,y-34+bob],[x-10,y-42+bob],[x-5,y-51+bob],[x+6,y-50+bob],[x+12,y-41+bob],[x+8,y-33+bob]],'#c6b98e','#e0d3a087');poly([[x-4,y-45+bob],[x+6,y-45+bob],[x+8,y-35+bob],[x-3,y-36+bob]],'#20383d');line([[x,y-41+bob],[x+6,y-41+bob]],'#f3d397',1.2);line([[x-10,y-28],[x-19,y-18]],'#b0a47d',5);line([[x+11,y-28],[x+19,y-17]],'#bdac7d',5);if(equipped.band){ellipse(x+19,y-17,5,3,null,'#d7eafa');glow(x+19,y-17,15,'#c8eafa30');}if(equipped.lantern){ellipse(x-12,y-27,3,3,'#97d6ef');line([[x-19,y-13],[x-19,y-1]],'#819997');crystal(x-19,y-3,'#a3dbeb',6);}if(p.ranged)crystal(x-20,y-18+Math.sin(elapsed*4)*1.5,'#a3d7ee',7);ctx.save();ctx.translate(x+19,y-17);ctx.rotate(attackFX?p.dir+Math.PI/2+(1-attackFX.life/attackFX.max)*2-1:.48+(p.weapon===2?.3:0));drawSword(0,0,p.weapon,p.weapon===2?1.12:.88);ctx.restore();if(p.invul>1){glow(x,y-15,48,'#d751443e');}ctx.restore();}
function drawEnemy(e){if(e.dead){ellipse(e.x,e.y+3,16,7,'#09171d55');poly([[e.x-11,e.y],[e.x-4,e.y-8],[e.x+9,e.y-5],[e.x+14,e.y+3],[e.x-8,e.y+7]],'#273a3d','#65736840');return;}let x=e.x,y=e.y,s=e.type==='boss'?1.8:e.type==='brute'?1.3:1,bob=Math.sin(elapsed*3+x)*1.5;ctx.save();ctx.translate(x,y);ctx.scale(s,s);ellipse(0,7,19,8,'#07151b99');if(e.type==='boss'&&!(flags.coreA&&flags.coreB)){glow(0,-18,70,'#88b6df20');ellipse(0,-23,32,42,null,'#81adc566');}if(e.freeze>0.3){glow(0,-14,43,'#99d9f036');ellipse(0,6,26,12,null,'#9bd5ebbb');}let armor=e.hit>0?'#b49a72':e.freeze>.3?'#65898f':'#56615b';line([[-6,-4],[-10,8]],'#202f33',8);line([[6,-4],[10,8]],'#202f33',8);poly([[-12,-31+bob],[12,-31+bob],[14,-12],[8,0],[-8,0],[-14,-12]],armor,'#b19b6a77');poly([[-12,-31+bob],[0,-27],[0,-4],[-8,0],[-14,-12]],'#2e4244','#81907633');line([[-10,-17],[10,-17]],'#97855c77',2);poly([[-15,-34],[-22,-28],[-19,-17],[-9,-22]],armor,'#a79a7155');poly([[14,-34],[22,-28],[21,-16],[10,-21]],armor,'#a79a7155');line([[-19,-18],[-24,-4]],'#7e8064',5);line([[19,-19],[25,-6]],'#737958',5);poly([[-9,-34+bob],[-9,-44+bob],[-4,-50+bob],[7,-49+bob],[11,-41+bob],[8,-33+bob]],armor,'#b4a57566');poly([[1,-49],[11,-41],[8,-33],[1,-35]],'#23363a');line([[-6,-40+bob],[7,-40+bob]],e.freeze>.3?'#9ce7fa':'#e4a269',2);glow(1,-39,13,e.freeze>.3?'#78d6f13b':'#e088513b');line([[25,0],[28,-43]],'#b1996a',3);poly([[26,-43],[25,-52],[38,-43],[35,-30],[28,-31]],'#8c886a','#c0ad6e77');if(e.type==='boss'){for(let i=-1;i<=1;i++)poly([[i*7-3,-50],[i*8,-62-Math.abs(i)*3],[i*7+4,-50]],'#b09c69','#dbc68888');}ctx.restore();if(e.hp<e.max||dist(p,e)<150){let w=e.type==='boss'?90:33;ctx.fillStyle='#101e25';ctx.fillRect(x-w/2,y-61*s,w,3);ctx.fillStyle=e.freeze>0?'#9ed1df':'#c18a59';ctx.fillRect(x-w/2,y-61*s,w*e.hp/e.max,3);if(e.type==='boss')label(x,y-61*s-8,'ASTRAL CUSTODIAN','#c5a177');}}
function render(){ctx.setTransform(1,0,0,1,0,0);ctx.clearRect(0,0,W,H);ctx.drawImage(getBackground(),0,0);ctx.save();if(shake>0)ctx.translate(rnd(-shake,shake),rnd(-shake*.6,shake*.6));
// Light shafts and drifting dust over the quiet machine.
ctx.save();ctx.globalCompositeOperation='screen';poly([[440,0],[532,0],[341,469],[264,465]],'#cbb97a05');poly([[561,0],[577,0],[402,448],[379,444]],'#d7bb7510');ctx.restore();
if(equipped.lantern&&zone>=2){ctx.setLineDash([4,9]);let paths=zone===3?[[350,208],[603,202],[792,260],[694,230]]:zone===4?[[p.x,p.y],[667,165],[677,230]]:[[p.x,p.y],[755,208]];line(paths,'#98cce955',1.5);ctx.setLineDash([]);}
if(zone===2){let open=world[2].solved;poly([[554,270],[584,270],[584,460],[554,460]],open?'#777354':'#10242d','#b7a16c70');for(let y=280;y<459;y+=17){if(open)line([[555,y],[583,y]],'#b3a06b66',2);else{line([[557,y],[581,y-18]],'#83917650',2);}}if(!open){line([[563,270],[563,455]],'#b6a16d77',3);label(569,479,'BRIDGE RETRACTED','#b7a27e');}}
let entities=[...world[zone].objects.filter(visible).map(o=>({y:o.y,draw:()=>drawObject(o)})),...world[zone].enemies.map(e=>({y:e.y,draw:()=>drawEnemy(e)})),{y:p.y,draw:drawPlayer}];entities.sort((a,b)=>a.y-b.y);for(let ent of entities)ent.draw();
if(attackFX){const a=attackFX,t=1-a.life/a.max,r=[66,79,103][a.level];ctx.save();ctx.translate(a.x,a.y-12);ctx.scale(1,.8);ctx.globalAlpha=1-t;ctx.beginPath();ctx.arc(0,0,r,a.a-1.2+t*.5,a.a+.8+t*.5);ctx.strokeStyle=['#e7dabc','#efa25c','#ffe3a0'][a.level];ctx.lineWidth=a.level===2?9:5;ctx.shadowColor=ctx.strokeStyle;ctx.shadowBlur=14;ctx.stroke();ctx.beginPath();ctx.arc(0,0,r-8,a.a-1+t*.5,a.a+.7+t*.5);ctx.strokeStyle='#fff1c866';ctx.lineWidth=1;ctx.stroke();ctx.restore();}
for(let b of bolts){glow(b.x,b.y,23,b.hostile?'#e1a06444':'#95defd55');line([[b.x-b.vx*.035,b.y-b.vy*.035],[b.x,b.y]],b.hostile?'#e7a577':'#b5edff',3);ellipse(b.x,b.y,3,3,'#e4f9ed');}for(let q of particles){ctx.globalAlpha=Math.max(0,Math.min(1,q.life/q.max));ctx.fillStyle=q.color;ctx.fillRect(q.x,q.y,q.size,q.size);}ctx.globalAlpha=1;for(let f of floats){ctx.globalAlpha=Math.min(1,f.life);ctx.textAlign='center';ctx.font='15px Georgia';ctx.fillStyle=f.color;ctx.fillText(f.text,f.x,f.y);}ctx.globalAlpha=1;
for(let i=0;i<38;i++){let x=(i*137.6+elapsed*(zone===3?5:2))%W,y=(i*89.4-elapsed*(zone===3?8:3)+H*100)%H;ctx.globalAlpha=.14+Math.sin(elapsed+i)*.1;if(zone===3){ellipse(x,y,38,7,'#a2c9d1');}else{ellipse(x,y,i%3===0?1.5:.7,i%3===0?1.5:.7,'#dfd3a0');}}ctx.globalAlpha=1;
ctx.restore();let vignette=ctx.createRadialGradient(W*.5,H*.48,H*.15,W*.5,H*.48,W*.63);vignette.addColorStop(0,'transparent');vignette.addColorStop(.65,'#09151b16');vignette.addColorStop(1,'#061019d9');ctx.fillStyle=vignette;ctx.fillRect(0,0,W,H);if(flash>0){ctx.strokeStyle=`rgba(${p.invul>0?'197, sixty, 48'.replace('sixty','60'):'244, 211, 125'},${flash*.65})`;ctx.lineWidth=15;ctx.strokeRect(0,0,W,H);}if(zone===2&&!world[2].solved&&world[2].plateUntil>elapsed){label(385,436,`${(world[2].plateUntil-elapsed).toFixed(1)} s · REACH THE SECOND PLATE`,'#d8b97d');}}
function frame(t){let dt=Math.min(.035,(t-lastTime)/1000||.016);lastTime=t;update(dt);render();requestAnimationFrame(frame);}
function toggleSound(){soundOn=!soundOn;$('soundBtn').textContent=soundOn?'SOUND ON':'SOUND OFF';if(soundOn){tone('rare');notify('The observatory finds its voice.',2);}}
function keyDown(e){let key=e.key.length===1?e.key.toLowerCase():e.key;if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight',' ','Tab'].includes(key)&&key!=='Tab')e.preventDefault();if(e.repeat){keys[key]=true;return;}if(key==='Escape'){if(modal)closeModal();else{paused=!paused;$('pauseLabel').style.display=paused?'grid':'none';}return;}if(key==='m'){toggleSound();return;}if(key==='i'){if(modal)closeModal();else inventory();return;}if(modal||paused||win)return;keys[key]=true;if(key==='e')interact();if(key==='j')strike();if(key==='k')fire();if(key==='h')heal();if(key==='b')useSeal();if(key===' '&&p.dash<=0&&p.invul<=0){p.dash=.2;p.invul=.5;burst(p.x,p.y,'#c1b78e',6);tone('swing');} }
window.addEventListener('keydown',keyDown);window.addEventListener('keyup',e=>{keys[e.key.length===1?e.key.toLowerCase():e.key]=false;});window.addEventListener('blur',()=>{keys={};});canvas.addEventListener('pointerdown',e=>{if(modal||paused)return;let rect=canvas.getBoundingClientRect(),scale=Math.min(rect.width/W,rect.height/H),ox=(rect.width-W*scale)/2,oy=(rect.height-H*scale)/2;let x=(e.clientX-rect.left-ox)/scale,y=(e.clientY-rect.top-oy)/scale;p.dir=Math.atan2(y-p.y,x-p.x);if(e.button===2)fire();else strike();});canvas.addEventListener('contextmenu',e=>e.preventDefault());$('inventoryBtn').onclick=inventory;$('soundBtn').onclick=toggleSound;$('helpBtn').onclick=guide;$('modalWrap').addEventListener('click',e=>{if(e.target===$('modalWrap')&&p.hp>0)closeModal();});document.querySelectorAll('[data-key]').forEach(b=>{b.addEventListener('pointerdown',e=>{e.preventDefault();b.setPointerCapture(e.pointerId);keyDown({key:b.dataset.key,preventDefault(){},repeat:false});});for(let ev of ['pointerup','pointercancel'])b.addEventListener(ev,e=>{keys[b.dataset.key]=false;});});
canvas.width=W;canvas.height=H;initial();flags.visited0=true;try{let data=JSON.parse(localStorage.getItem('wardens-corrosion-v1'));if(data&&data.world&&data.p){saveData=data;$('saveStatus').textContent='A MEMORY REMAINS · GUIDE TO RESUME';}}catch(e){}updateUI();notify('You are the Last Warden. Move with WASD. E to rest at the golden stone.',7);requestAnimationFrame(frame);
</script></body></html>
<!-- agent-meta {"model":"openai/gpt-6-astra","provider":"openrouter","persona":"full","sessionId":"f25e27e0-f703-402f-b0c1-d5c20697ce1f","tokensIn":316092,"tokensOut":25377,"tokensTotal":341469,"cost":2.031416,"turns":11,"toolCalls":10,"failedToolCalls":0,"timestamp":"2026-09-05T15:05:43.870Z"} -->