โ back to AbramsEgo
feat(dungeon): inter-room handoff journeys (workers carry work to other chambers + return, blue trail) + canary-vs-glitch combat (๐พ monsters spawn on alerts, nearest sentry charges + trades hit-sparks, explosion on new alert, poof+Slain tally on clear)
b64165ab26d1246d48bd4e4b53db8df64270149c ยท 2026-07-02 07:47:34 -0700 ยท Steve
Files touched
M data/local-fleet.jsonM public/dungeon.html
Diff
commit b64165ab26d1246d48bd4e4b53db8df64270149c
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Jul 2 07:47:34 2026 -0700
feat(dungeon): inter-room handoff journeys (workers carry work to other chambers + return, blue trail) + canary-vs-glitch combat (๐พ monsters spawn on alerts, nearest sentry charges + trades hit-sparks, explosion on new alert, poof+Slain tally on clear)
---
data/local-fleet.json | 2 +-
public/dungeon.html | 69 +++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 68 insertions(+), 3 deletions(-)
diff --git a/data/local-fleet.json b/data/local-fleet.json
index 50f19a1..4117abd 100644
--- a/data/local-fleet.json
+++ b/data/local-fleet.json
@@ -1 +1 @@
-{"fetchedAt":"2026-07-02T14:46:12.050Z","procs":[{"name":"pm2-logrotate","status":"online","cpu":0.1,"memMB":27,"restarts":0,"uptimeMs":1842953,"startedAt":"2026-07-02T14:15:29.097Z"},{"name":"abramsego","status":"online","cpu":1.1,"memMB":66,"restarts":9,"uptimeMs":181390,"startedAt":"2026-07-02T14:43:10.660Z"},{"name":"api-token-dashboard","status":"online","cpu":0.2,"memMB":27,"restarts":0,"uptimeMs":1721339,"startedAt":"2026-07-02T14:17:30.711Z"},{"name":"pattern-vault","status":"online","cpu":0.2,"memMB":28,"restarts":1,"uptimeMs":1210569,"startedAt":"2026-07-02T14:26:01.481Z"},{"name":"agent-cabinet","status":"online","cpu":0.1,"memMB":27,"restarts":0,"uptimeMs":1718615,"startedAt":"2026-07-02T14:17:33.435Z"},{"name":"astek-landing","status":"online","cpu":0.1,"memMB":52,"restarts":0,"uptimeMs":215853,"startedAt":"2026-07-02T14:42:36.197Z"}]}
\ No newline at end of file
+{"fetchedAt":"2026-07-02T14:47:12.101Z","procs":[{"name":"pm2-logrotate","status":"online","cpu":0.3,"memMB":28,"restarts":0,"uptimeMs":1903004,"startedAt":"2026-07-02T14:15:29.097Z"},{"name":"abramsego","status":"online","cpu":0.4,"memMB":64,"restarts":9,"uptimeMs":241441,"startedAt":"2026-07-02T14:43:10.660Z"},{"name":"api-token-dashboard","status":"online","cpu":0.2,"memMB":26,"restarts":0,"uptimeMs":1781390,"startedAt":"2026-07-02T14:17:30.711Z"},{"name":"pattern-vault","status":"online","cpu":0.2,"memMB":29,"restarts":1,"uptimeMs":1270620,"startedAt":"2026-07-02T14:26:01.481Z"},{"name":"agent-cabinet","status":"online","cpu":0.2,"memMB":28,"restarts":0,"uptimeMs":1778666,"startedAt":"2026-07-02T14:17:33.435Z"},{"name":"astek-landing","status":"online","cpu":0.1,"memMB":51,"restarts":0,"uptimeMs":275904,"startedAt":"2026-07-02T14:42:36.197Z"}]}
\ No newline at end of file
diff --git a/public/dungeon.html b/public/dungeon.html
index b152151..37f4556 100644
--- a/public/dungeon.html
+++ b/public/dungeon.html
@@ -44,6 +44,7 @@ canvas{display:block;cursor:grab}canvas:active{cursor:grabbing}
<div class="stat">Agents <b id="sAgents">โ</b></div>
<div class="stat">Working <b id="sWork">โ</b></div>
<div class="stat">Alerts <b id="sBad">โ</b></div>
+ <div class="stat">Slain <b id="sSlain">0</b></div>
<div class="stat" style="color:var(--gold)">๐ <b id="sClock">โ</b></div>
<div class="legend"><span class="lg-online">online</span><span class="lg-work">working</span><span class="lg-idle">idle</span><span class="lg-bad">alert</span></div>
</div>
@@ -59,8 +60,17 @@ addEventListener('resize',resize);resize();
// ---- world state ----
let cam={x:0,y:0,z:1},chambers=[],agents=[],selected=null,hover=null,t=0;
+let particles=[],monsters=[],couriers=[],prevAlertKeys=new Set(),slain=0;
const AUTH=''; // same-origin, cookie/basic-auth already present
+// ---- FX: particle bursts (explosions, sparks, poofs) ----
+function burst(x,y,color,n,spd){for(let i=0;i<n;i++){const a=Math.random()*6.28,s=(spd||2)*(.4+Math.random());
+ particles.push({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s,life:1,color,r:1.5+Math.random()*2.5});}}
+function updateParticles(){for(const p of particles){p.x+=p.vx;p.y+=p.vy;p.vy+=0.04;p.vx*=.96;p.life-=0.02;}
+ particles=particles.filter(p=>p.life>0);}
+function drawParticles(){for(const p of particles){ctx.globalAlpha=Math.max(0,p.life);ctx.fillStyle=p.color;
+ ctx.beginPath();ctx.arc(p.x,p.y,p.r,0,6.28);ctx.fill();}ctx.globalAlpha=1;}
+
// status โ color/kind
function statusOf(a){
if(a.kind==='canary') return a.bad?'bad':'online';
@@ -145,6 +155,23 @@ function build(d){
vx:(Math.random()-.5)*.5, vy:(Math.random()-.5)*.5, ph:Math.random()*6.28, r:16});
});
});
+ // ---- alerts โ spawn glitch monsters + explosions on NEW alerts ----
+ const alerts=agents.filter(a=>statusOf(a)==='bad');
+ const keyOf=a=>a.kind+':'+a.name;
+ const curKeys=new Set(alerts.map(keyOf));
+ // new alerts โ explosion at that agent's chamber
+ alerts.forEach(a=>{if(!prevAlertKeys.has(keyOf(a))){burst(a.x,a.y,'#e5484d',26,3.2);burst(a.x,a.y,'#ffb020',14,2.2);}});
+ // build monster list (one per active alert), engaged by the nearest sentry
+ const sentries=agents.filter(a=>a.kind==='canary'&&!a.bad);
+ monsters=alerts.map(a=>{
+ const prev=monsters.find(m=>m.key===keyOf(a));
+ const foe=sentries.length?sentries.reduce((b,s)=>Math.hypot(s.x-a.x,s.y-a.y)<Math.hypot(b.x-a.x,b.y-a.y)?s:b):null;
+ return prev? Object.assign(prev,{target:a,foe}):
+ {key:keyOf(a),target:a,foe,x:a.x+30,y:a.y-30,ph:Math.random()*6.28,hp:1};});
+ // cleared alerts โ poof + tally
+ prevAlertKeys.forEach(k=>{if(!curKeys.has(k)){slain++;}});
+ prevAlertKeys=curKeys;
+ S('sSlain',slain);
// hud
S('sChambers',chambers.length);S('sAgents',agents.length);
S('sWork',agents.filter(a=>statusOf(a)==='work').length);
@@ -217,14 +244,32 @@ function draw(){
ctx.fillStyle='#8a7fa8';ctx.font='12px Avenir Next,sans-serif';
ctx.fillText((ch.domain||'').slice(0,54),ch.x+16,ch.y+32);
});
- // agents (wander + draw)
+ // handoff journeys: occasionally a working agent carries work to another chamber
+ if(t%40===0){const workers=agents.filter(a=>statusOf(a)==='work'&&!a.travel&&a.kind==='proc');
+ if(workers.length&&chambers.length>1&&Math.random()<0.6){
+ const a=workers[(Math.random()*workers.length)|0];
+ let dest=chambers[(Math.random()*chambers.length)|0];let g=0;
+ while(dest===a.chamber&&g++<5)dest=chambers[(Math.random()*chambers.length)|0];
+ a.travel={dest,home:a.chamber,phase:'go',hx:a.x,hy:a.y};}}
+ // agents (wander/travel + draw)
agents.forEach(a=>{
+ const ch=a.chamber,pad=40;
+ if(a.travel){
+ const tg=a.travel.phase==='go'?a.travel.dest:a.travel.home;
+ const tx=tg.x+tg.w/2, ty=tg.y+tg.h/2;
+ const dx=tx-a.x, dy=ty-a.y, dist=Math.hypot(dx,dy);
+ a.x+=dx/dist*3.2; a.y+=dy/dist*3.2;
+ if((t>>1)%2===0)particles.push({x:a.x,y:a.y,vx:0,vy:0,life:.5,color:'#5b8cff',r:2}); // trail
+ if(dist<26){
+ if(a.travel.phase==='go'){a.travel.phase='return';burst(tx,ty,'#5b8cff',10,1.6);} // deliver
+ else{a.chamber=a.travel.home;a.x=a.travel.hx;a.y=a.travel.hy;a.travel=null;}}
+ } else {
// wander within chamber
a.ph+=0.03;a.vx+=Math.cos(a.ph)*.03+(Math.random()-.5)*.04;a.vy+=Math.sin(a.ph*1.3)*.03+(Math.random()-.5)*.04;
a.vx*=.94;a.vy*=.94;a.x+=a.vx;a.y+=a.vy;
- const ch=a.chamber,pad=40;
if(a.x<ch.x+pad){a.x=ch.x+pad;a.vx=Math.abs(a.vx);}if(a.x>ch.x+ch.w-pad){a.x=ch.x+ch.w-pad;a.vx=-Math.abs(a.vx);}
if(a.y<ch.y+pad+18){a.y=ch.y+pad+18;a.vy=Math.abs(a.vy);}if(a.y>ch.y+ch.h-pad){a.y=ch.y+ch.h-pad;a.vy=-Math.abs(a.vy);}
+ }
const st=statusOf(a),col=COL[st];
// status glow / working pulse
const pulse=st==='work'?(1+Math.sin(t*.12+a.ph)*.25):1;
@@ -245,6 +290,26 @@ function draw(){
if(cam.z>0.5){ctx.fillStyle='#8a7fa8';ctx.font='10px Avenir Next,sans-serif';ctx.textAlign='center';
ctx.fillText(a.name.slice(0,16),a.x,a.y+a.r+11);ctx.textAlign='left';}
});
+ // ---- monsters + combat ----
+ monsters.forEach(m=>{
+ const tg=m.target;m.ph+=0.08;m.x+=((tg.x+34)-m.x)*.06;m.y+=((tg.y-34)-m.y)*.06;
+ const by=m.y+Math.sin(m.ph)*4;
+ // a sentry engages: it advances on the monster and trades hit-sparks
+ if(m.foe){const f=m.foe;const dx=m.x-f.x,dy=by-f.y,d=Math.hypot(dx,dy);
+ if(d>60){f.x+=dx/d*1.6;f.y+=dy/d*1.6;} // charge in
+ else if(t%18===0){ // clash
+ const mx=(f.x+m.x)/2,my=(f.y+by)/2;burst(mx,my,'#ffd24a',6,2);burst(mx,my,'#e5484d',4,1.6);}
+ // sword line
+ ctx.strokeStyle='#ffd24a55';ctx.lineWidth=1.5;ctx.beginPath();ctx.moveTo(f.x,f.y);ctx.lineTo(m.x,by);ctx.stroke();}
+ // menace aura + body
+ ctx.beginPath();ctx.arc(m.x,by,20,0,6.28);ctx.fillStyle='#e5484d22';ctx.fill();
+ ctx.font='26px sans-serif';ctx.textAlign='center';ctx.textBaseline='middle';
+ ctx.fillText('๐พ',m.x,by);
+ // threat label
+ ctx.fillStyle='#e5484d';ctx.font='700 10px Avenir Next,sans-serif';
+ ctx.fillText((tg.verdict||tg.status||'ALERT').toString().toUpperCase(),m.x,by-24);ctx.textAlign='left';
+ });
+ drawParticles();updateParticles();
requestAnimationFrame(draw);
}
function roundRect(x,y,w,h,r){ctx.beginPath();ctx.moveTo(x+r,y);ctx.arcTo(x+w,y,x+w,y+h,r);
โ 97535d2 auto-save: 2026-07-02T07:46:12 (1 files) โ data/local-fleet.
ยท
back to AbramsEgo
ยท
feat(dungeon): sound FX (WebAudio synth โ combat clash, deli 08e18a2 โ