quoxcli 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api/endpoints/actions.js +15 -0
- package/lib/api/endpoints/admin.js +20 -0
- package/lib/api/endpoints/blog.js +16 -0
- package/lib/api/endpoints/chat.js +7 -0
- package/lib/api/endpoints/contactsq.js +4 -0
- package/lib/api/endpoints/devServerRegistry.js +25 -0
- package/lib/api/endpoints/engagement.js +26 -0
- package/lib/api/endpoints/evidence.js +20 -0
- package/lib/api/endpoints/hat.js +14 -0
- package/lib/api/endpoints/index.js +12 -0
- package/lib/api/endpoints/loops.js +26 -0
- package/lib/api/endpoints/mon.js +173 -0
- package/lib/api/endpoints/objectives.js +6 -0
- package/lib/api/endpoints/quoxseo.js +6 -0
- package/lib/api/endpoints/server.js +21 -0
- package/lib/api/endpoints/setup.js +6 -0
- package/lib/api/endpoints/v1.js +34 -0
- package/lib/commands/actions.js +81 -11
- package/lib/commands/agent.js +11 -4
- package/lib/commands/agentAttach.js +46 -7
- package/lib/commands/approvals.js +5 -0
- package/lib/commands/ask.js +3 -1
- package/lib/commands/blog.js +110 -0
- package/lib/commands/chat.js +127 -28
- package/lib/commands/chatCompanion.js +27 -0
- package/lib/commands/chatState.js +45 -0
- package/lib/commands/claude.js +3 -32
- package/lib/commands/contacts.js +85 -4
- package/lib/commands/contactsSendAt.js +21 -0
- package/lib/commands/engagement.js +90 -0
- package/lib/commands/evidence.js +154 -0
- package/lib/commands/ext.js +23 -0
- package/lib/commands/fleet.js +25 -0
- package/lib/commands/hat.js +76 -0
- package/lib/commands/image.js +68 -0
- package/lib/commands/kuma.js +1050 -0
- package/lib/commands/kumaSla.js +174 -0
- package/lib/commands/licence.js +39 -1
- package/lib/commands/loop.js +291 -0
- package/lib/commands/objective.js +48 -1
- package/lib/commands/org.js +21 -4
- package/lib/commands/provider.js +58 -0
- package/lib/commands/quoxseo.js +31 -0
- package/lib/commands/research.js +79 -0
- package/lib/commands/rows.js +9 -0
- package/lib/commands/run.js +18 -1
- package/lib/commands/scope.js +2 -2
- package/lib/commands/server.js +113 -0
- package/lib/commands/shortcut.js +41 -0
- package/lib/commands/skills.js +20 -4
- package/lib/commands/vm.js +2 -2
- package/lib/commands/youtube.js +172 -0
- package/lib/core/actionItems.js +115 -0
- package/lib/core/agentAlias.js +2 -0
- package/lib/core/agentFamilies.js +7 -0
- package/lib/core/client.js +10 -2
- package/lib/core/commandRegistrars.js +9 -0
- package/lib/core/config.js +1 -1
- package/lib/core/findingReconcile.js +90 -0
- package/lib/core/global-flags.js +1 -1
- package/lib/core/governedSession.js +1 -3
- package/lib/core/liveSessionsReport.js +1 -1
- package/lib/core/loopGlyph.js +97 -0
- package/lib/core/offload.js +36 -7
- package/lib/core/output.js +7 -7
- package/lib/core/sessionHeartbeat.js +12 -8
- package/lib/core/shortcut.js +83 -0
- package/lib/core/version.js +1 -1
- package/lib/core/vm.js +5 -0
- package/lib/dragon/companionView.js +34 -0
- package/lib/dragon/frames.js +118 -0
- package/lib/dragon/jobs.js +123 -0
- package/lib/dragon/renderWorker.js +15 -0
- package/lib/dragon/select.js +72 -0
- package/lib/evidence/bundleExport.js +47 -0
- package/lib/evidence/bundleVerify.js +77 -0
- package/lib/quoxon/EXTENSIONS.md +23 -0
- package/lib/quoxon/api.js +192 -6
- package/lib/quoxon/app.js +655 -89
- package/lib/quoxon/approvalFacts.js +8 -6
- package/lib/quoxon/approvalSentence.js +44 -0
- package/lib/quoxon/approvalsView.js +63 -4
- package/lib/quoxon/commandShort.js +15 -3
- package/lib/quoxon/commands.js +75 -10
- package/lib/quoxon/countdownLine.js +10 -1
- package/lib/quoxon/deck.js +30 -0
- package/lib/quoxon/demo.autoplay.js +1 -1
- package/lib/quoxon/demo.js +3 -1
- package/lib/quoxon/dividerAocl.js +4 -3
- package/lib/quoxon/dividerRule.js +3 -3
- package/lib/quoxon/dragonCompanion.js +255 -0
- package/lib/quoxon/dragonModes.js +1 -0
- package/lib/quoxon/dragonMoments.js +82 -0
- package/lib/quoxon/dragonReads.js +68 -0
- package/lib/quoxon/dragonView.js +2 -2
- package/lib/quoxon/events.js +24 -3
- package/lib/quoxon/extensions.js +276 -0
- package/lib/quoxon/inboxView.js +79 -0
- package/lib/quoxon/installCli.js +79 -0
- package/lib/quoxon/keys.js +2 -2
- package/lib/quoxon/loopCard.js +456 -0
- package/lib/quoxon/loopCards.js +180 -0
- package/lib/quoxon/loopStream.js +87 -0
- package/lib/quoxon/main.js +12 -3
- package/lib/quoxon/mouse.js +12 -1
- package/lib/quoxon/objectiveOffer.js +82 -0
- package/lib/quoxon/objects/index.js +22 -4
- package/lib/quoxon/objects/model.js +33 -0
- package/lib/quoxon/objects/proof.js +12 -4
- package/lib/quoxon/onboarding/shortcutOffer.js +28 -0
- package/lib/quoxon/onboarding/view.js +1 -1
- package/lib/quoxon/orgSwitch.js +27 -1
- package/lib/quoxon/planSteps.js +51 -0
- package/lib/quoxon/print.js +6 -3
- package/lib/quoxon/qdragonFly.js +7 -6
- package/lib/quoxon/qmotion.js +17 -0
- package/lib/quoxon/render.js +130 -33
- package/lib/quoxon/roster.js +26 -12
- package/lib/quoxon/rows.js +244 -1
- package/lib/quoxon/serverChip.js +42 -0
- package/lib/quoxon/session.js +10 -0
- package/lib/quoxon/slash/cliCommand.js +42 -1
- package/lib/quoxon/slash/cliLeaves.json +76 -1
- package/lib/quoxon/slash/evidenceCmd.js +34 -0
- package/lib/quoxon/slash/palette.js +1 -1
- package/lib/quoxon/slash/qpeCmd.js +35 -0
- package/lib/quoxon/slash/restart.js +25 -1
- package/lib/quoxon/slash/resume.js +18 -1
- package/lib/quoxon/slash/todoCmd.js +122 -0
- package/lib/quoxon/suggestCommand.js +33 -2
- package/lib/quoxon/toolCards.js +30 -0
- package/lib/quoxon/whatsnew.jsonl +45 -0
- package/lib/quoxon/windowOrg.js +11 -1
- package/lib/server/devServers.js +137 -0
- package/package.json +2 -1
- package/vendor/dragonlab/SOURCE +4 -0
- package/vendor/dragonlab/design/cipher/v1/cipher-master.svg +46 -0
- package/vendor/dragonlab/design/cipher/v2/character.mjs +49 -0
- package/vendor/dragonlab/design/cipher/v2/review.mjs +33 -0
- package/vendor/dragonlab/design/cipher/v2/watch.mjs +56 -0
- package/vendor/dragonlab/design/drago/v1/character.mjs +19 -0
- package/vendor/dragonlab/design/drago/v1/drago-master.svg +56 -0
- package/vendor/dragonlab/design/drago/v2/character.mjs +87 -0
- package/vendor/dragonlab/design/drago/v4/character.mjs +67 -0
- package/vendor/dragonlab/design/drago/v4/profile.mjs +35 -0
- package/vendor/dragonlab/design/drago/v4/review.mjs +33 -0
- package/vendor/dragonlab/design/drago/v4/watch.mjs +56 -0
- package/vendor/dragonlab/design/hyperion/v1/hyperion-master.svg +56 -0
- package/vendor/dragonlab/design/hyperion/v2/character.mjs +79 -0
- package/vendor/dragonlab/design/hyperion/v2/review.mjs +33 -0
- package/vendor/dragonlab/design/hyperion/v2/watch.mjs +56 -0
- package/vendor/dragonlab/design/lucid/recovery/arrival.mjs +22 -0
- package/vendor/dragonlab/design/lucid/recovery/character.mjs +106 -0
- package/vendor/dragonlab/design/lucid/recovery/layout.mjs +50 -0
- package/vendor/dragonlab/design/lucid/recovery/lucid-master.svg +78 -0
- package/vendor/dragonlab/design/lucid/recovery/motion.mjs +42 -0
- package/vendor/dragonlab/design/lucid/recovery/render.mjs +121 -0
- package/vendor/dragonlab/design/lucid/recovery/review.mjs +68 -0
- package/vendor/dragonlab/design/lucid/recovery/verify.mjs +84 -0
- package/vendor/dragonlab/design/lucid/recovery/watch.mjs +56 -0
- package/vendor/dragonlab/design/seer/v1/seer-master.svg +53 -0
- package/vendor/dragonlab/design/seer/v2/character.mjs +88 -0
- package/vendor/dragonlab/design/seer/v3/character.mjs +33 -0
- package/vendor/dragonlab/design/seer/v4/character.mjs +66 -0
- package/vendor/dragonlab/design/seer/v4/review.mjs +33 -0
- package/vendor/dragonlab/design/seer/v4/watch.mjs +56 -0
- package/vendor/dragonlab/design/stage3/companion-review.mjs +28 -0
- package/vendor/dragonlab/design/stage3/sentinel-art.js +39 -0
- package/vendor/dragonlab/design/stage3/sentinel-braille.js +216 -0
- package/vendor/dragonlab/design/stage3/sentinel-character.js +257 -0
- package/vendor/dragonlab/design/stage3/sentinel-icon.js +103 -0
- package/vendor/dragonlab/design/stage3/sentinel-warden-eyes.js +95 -0
- package/vendor/dragonlab/design/stage3/svg-coverage.js +148 -0
- package/vendor/dragonlab/factory/svg-rig.mjs +63 -0
- package/vendor/dragonlab/integration/companions-v1/characters.mjs +88 -0
- package/vendor/dragonlab/integration/companions-v1/cipher.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/companion.mjs +63 -0
- package/vendor/dragonlab/integration/companions-v1/drago.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/hyperion.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/lucid.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/seer.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/sentinel.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/view.mjs +28 -0
- package/vendor/dragonlab/integration/hyperion-v3/activity.mjs +76 -0
- package/vendor/dragonlab/lib/power-effects.js +294 -0
- package/vendor/dragonlab/lib/power-registry.js +74 -0
- package/vendor/dragonlab/vendor/quox-dragon/qart.js +348 -0
- package/vendor/dragonlab/vendor/quox-dragon/qeyes.js +213 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Host facts select expressions; the renderer never infers operational truth.
|
|
2
|
+
export function initialActivity(now = 0) {
|
|
3
|
+
return { started: now, changed: now, received: now, turnId: null, phase: 'connecting',
|
|
4
|
+
approvals: [], tools: [], issue: false, terminal: false };
|
|
5
|
+
}
|
|
6
|
+
const id = x => typeof x === 'string' && x.length <= 256 ? x : null;
|
|
7
|
+
export function activityEvent(s, e, now) {
|
|
8
|
+
if (!e || typeof e !== 'object' || !Number.isFinite(now) || now < s.received) return s;
|
|
9
|
+
if (e.turn_id && s.turnId && e.turn_id !== s.turnId && e.type !== 'turn.started') return s;
|
|
10
|
+
let n = { ...s, received: now };
|
|
11
|
+
const phase = p => { if (n.phase !== p) { n.phase = p; n.changed = now; } };
|
|
12
|
+
const key = id(e.call_id) || id(e.approval_id);
|
|
13
|
+
switch (e.type) {
|
|
14
|
+
case 'turn.started':
|
|
15
|
+
if (s.terminal || (id(e.turn_id) && e.turn_id !== s.turnId)) n = { ...initialActivity(now), approvals: s.approvals };
|
|
16
|
+
n.turnId = id(e.turn_id) || s.turnId; phase('thinking'); break;
|
|
17
|
+
case 'connected': phase('thinking'); break;
|
|
18
|
+
case 'thinking':
|
|
19
|
+
if (!s.terminal) phase(String(e.stage || '').includes('tool') ? 'working' : 'thinking'); break;
|
|
20
|
+
case 'chunk': case 'text.delta':
|
|
21
|
+
if (!s.terminal) phase('speaking'); break;
|
|
22
|
+
case 'tool.started':
|
|
23
|
+
if (s.terminal) return s;
|
|
24
|
+
if (key && !n.tools.includes(key)) n.tools = [...n.tools, key].slice(-256);
|
|
25
|
+
phase('working'); break;
|
|
26
|
+
case 'tool.finished':
|
|
27
|
+
if (s.terminal) return s;
|
|
28
|
+
n.tools = n.tools.filter(k => k !== key);
|
|
29
|
+
// Unknown and effect_unknown results are never promoted to success.
|
|
30
|
+
if (e.status !== 'ok') n.issue = true;
|
|
31
|
+
phase(n.tools.length ? 'working' : 'thinking'); break;
|
|
32
|
+
case 'approval.requested':
|
|
33
|
+
if (!key) return s;
|
|
34
|
+
if (!n.approvals.includes(key)) n.approvals = [...n.approvals, key].slice(-256);
|
|
35
|
+
break;
|
|
36
|
+
case 'approval.resolved':
|
|
37
|
+
if (!key || !n.approvals.includes(key)) return s;
|
|
38
|
+
n.approvals = n.approvals.filter(k => k !== key);
|
|
39
|
+
if (e.decision !== 'approved') n.issue = true;
|
|
40
|
+
break;
|
|
41
|
+
case 'error': n.issue = true; n.terminal = true; phase('error'); break;
|
|
42
|
+
case 'turn.finished':
|
|
43
|
+
if (s.terminal) return s;
|
|
44
|
+
n.terminal = true;
|
|
45
|
+
if (e.status !== 'completed' || n.tools.length) n.issue = true;
|
|
46
|
+
phase(e.status === 'completed' ? 'complete' : 'error'); break;
|
|
47
|
+
case 'done':
|
|
48
|
+
// Legacy text completion is not a verified task outcome.
|
|
49
|
+
if (!s.terminal) phase('response-ready'); break;
|
|
50
|
+
case 'stream.closed':
|
|
51
|
+
if (!s.terminal) { n.terminal = true; phase(s.phase === 'response-ready' ? 'response-ready' : 'disconnected'); }
|
|
52
|
+
break;
|
|
53
|
+
default: return s;
|
|
54
|
+
}
|
|
55
|
+
return n;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function activityView(s, now) {
|
|
59
|
+
const age = Math.max(0, now - s.changed);
|
|
60
|
+
const view = (scene, caption, time = age) => ({ scene, caption, time, motionTime: Math.max(0, now - s.started) });
|
|
61
|
+
if (s.approvals.length) return view('curious', 'Waiting for your approval.');
|
|
62
|
+
if (s.issue) return view('concerned', 'Something needs attention.');
|
|
63
|
+
if (s.phase === 'disconnected') return view('standby', 'Stream ended; outcome unknown.');
|
|
64
|
+
if (!s.terminal && now - s.received > 30000) return view('standby', 'No recent activity update.');
|
|
65
|
+
if (s.phase === 'complete') return age < 2600 ? view('proud', 'Turn completed.') : view('idle', 'Ready for the next message.');
|
|
66
|
+
if (s.phase === 'response-ready') return view('idle', 'Response received.');
|
|
67
|
+
if (s.phase === 'connecting') return view('standby', 'Connecting…');
|
|
68
|
+
if (age < 250) return view('idle', 'Following the conversation.');
|
|
69
|
+
if (s.tools.length || s.phase === 'working') {
|
|
70
|
+
// A restrained visual flourish, at most once each 30 seconds of work.
|
|
71
|
+
const t = age % 30000;
|
|
72
|
+
return t >= 5000 && t < 9800 ? view('cluster', 'Working · Cluster Forge visual', t - 5000) : view('focus', 'Working.');
|
|
73
|
+
}
|
|
74
|
+
if (s.phase === 'thinking') return view('curious', 'Thinking.');
|
|
75
|
+
return view('focus', 'Responding.');
|
|
76
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transparent, terminal-cell-native signature power renderer.
|
|
3
|
+
*
|
|
4
|
+
* Draws Braille dots in a separate compositing layer. Does not alter Quox's
|
|
5
|
+
* vendored renderer, write terminal escapes, set backgrounds, use randomness
|
|
6
|
+
* or perform actions. Times are deterministic milliseconds.
|
|
7
|
+
*
|
|
8
|
+
* Coordinates are in terminal cells; 2x4 Braille dots per cell.
|
|
9
|
+
*/
|
|
10
|
+
import { POWER_BY_SCENE } from './power-registry.js';
|
|
11
|
+
|
|
12
|
+
const PI=Math.PI, TAU=2*PI;
|
|
13
|
+
const clamp=(x,a=0,b=1)=>Math.max(a,Math.min(b,x));
|
|
14
|
+
const ease=(u)=>{const q=clamp(u);return q*q*(3-2*q);};
|
|
15
|
+
const phase=(t,a,b)=>ease((t-a)/Math.max(1,b-a));
|
|
16
|
+
const rgb={
|
|
17
|
+
azure:[73,163,255],shield:[100,205,255],burn:[91,224,255],
|
|
18
|
+
threat:[250,87,91],ember:[255,165,78],gold:[255,190,86],
|
|
19
|
+
teal:[76,232,182],green:[85,245,153],
|
|
20
|
+
violet:[183,140,255],muted:[147,155,184],white:[225,236,255],
|
|
21
|
+
};
|
|
22
|
+
const bit=[
|
|
23
|
+
[0x01,0x08],
|
|
24
|
+
[0x02,0x10],
|
|
25
|
+
[0x04,0x20],
|
|
26
|
+
[0x40,0x80],
|
|
27
|
+
];
|
|
28
|
+
const fakeHash=n=>{
|
|
29
|
+
let x=Math.imul((n|0)^0x9e3779b9,0x85ebca6b);
|
|
30
|
+
x^=x>>>13;x=Math.imul(x,0xc2b2ae35);x^=x>>>16;
|
|
31
|
+
return (x>>>0)/4294967296;
|
|
32
|
+
};
|
|
33
|
+
function empty(c){return !c||c.ch===' '||c.ch==='\u2800';}
|
|
34
|
+
|
|
35
|
+
export function spriteBounds(frame){
|
|
36
|
+
const w=frame[0]?.length||0,h=frame.length;
|
|
37
|
+
let minX=w,maxX=0,minY=h,maxY=0,n=0;
|
|
38
|
+
for(let y=0;y<h;y++)for(let x=0;x<w;x++){
|
|
39
|
+
if(empty(frame[y][x]))continue;
|
|
40
|
+
minX=Math.min(minX,x);maxX=Math.max(maxX,x);
|
|
41
|
+
minY=Math.min(minY,y);maxY=Math.max(maxY,y);n++;
|
|
42
|
+
}
|
|
43
|
+
return n?{minX,maxX,minY,maxY,centerX:(minX+maxX)/2,centerY:(minY+maxY)/2,visible:n}:
|
|
44
|
+
{minX:Math.floor(w*.35),maxX:Math.floor(w*.65),minY:Math.floor(h*.2),maxY:Math.floor(h*.8),centerX:w/2,centerY:h/2,visible:0};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createCanvas(w,h,{mono=false,theme='dark'}={}){
|
|
48
|
+
const masks=new Uint8Array(w*h);
|
|
49
|
+
const ink=Array(w*h).fill(null);
|
|
50
|
+
const labels=new Map();
|
|
51
|
+
const col=c=>{
|
|
52
|
+
if(mono)return null;
|
|
53
|
+
if(theme==='light')return c.map(v=>Math.round(v*.68));
|
|
54
|
+
return c;
|
|
55
|
+
};
|
|
56
|
+
function dot(dx,dy,c=rgb.white){
|
|
57
|
+
const x=Math.round(dx),y=Math.round(dy);
|
|
58
|
+
// Keep a blank perimeter. Never put anything in the final column.
|
|
59
|
+
if(x<2||y<1||x>=w*2-2||y>=h*4-1)return;
|
|
60
|
+
const cx=Math.floor(x/2),cy=Math.floor(y/4),i=cy*w+cx;
|
|
61
|
+
masks[i]|=bit[y%4][x%2];ink[i]=col(c);
|
|
62
|
+
}
|
|
63
|
+
function point(x,y,c=rgb.white,r=0){
|
|
64
|
+
const xx=x*2,yy=y*4,rr=Math.max(0,r);
|
|
65
|
+
if(rr===0){dot(xx,yy,c);return;}
|
|
66
|
+
for(let dx=-rr;dx<=rr;dx++)for(let dy=-rr;dy<=rr;dy++){
|
|
67
|
+
if(dx*dx+dy*dy<=rr*rr+.15)dot(xx+dx,yy+dy,c);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function line(ax,ay,bx,by,c=rgb.white,thickness=0){
|
|
71
|
+
const dx=(bx-ax)*2,dy=(by-ay)*4,steps=Math.max(1,Math.ceil(Math.hypot(dx,dy)*1.8));
|
|
72
|
+
for(let i=0;i<=steps;i++){
|
|
73
|
+
const p=i/steps;
|
|
74
|
+
point(ax+(bx-ax)*p,ay+(by-ay)*p,c,thickness);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function arc(cx,cy,rx,ry,a0,a1,c=rgb.white,thickness=0){
|
|
78
|
+
const steps=Math.max(12,Math.ceil((Math.abs(rx)+Math.abs(ry)*2)*Math.abs(a1-a0)*2));
|
|
79
|
+
for(let i=0;i<=steps;i++){
|
|
80
|
+
const a=a0+(a1-a0)*(i/steps);
|
|
81
|
+
point(cx+Math.cos(a)*rx,cy+Math.sin(a)*ry,c,thickness);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function spark(cx,cy,t,r,c=rgb.white){
|
|
85
|
+
if(r<=0)return;
|
|
86
|
+
for(let k=0;k<7;k++){
|
|
87
|
+
const a=k*TAU/7+t*.0023;
|
|
88
|
+
line(cx+Math.cos(a)*.2,cy+Math.sin(a)*.14,
|
|
89
|
+
cx+Math.cos(a)*r,cy+Math.sin(a)*r*.5,c);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function glyph(x,y,ch,c=rgb.white){
|
|
93
|
+
const xx=Math.round(x),yy=Math.round(y);
|
|
94
|
+
if(xx<1||yy<0||xx>=w-1||yy>=h)return;
|
|
95
|
+
labels.set(yy*w+xx,{ch,rgb:col(c),bg:null});
|
|
96
|
+
}
|
|
97
|
+
function compose(base){
|
|
98
|
+
return base.map((row,y)=>row.map((cell,x)=>{
|
|
99
|
+
if(!empty(cell))return cell;
|
|
100
|
+
const i=y*w+x,mark=labels.get(i);
|
|
101
|
+
if(mark)return mark;
|
|
102
|
+
return masks[i]?{ch:String.fromCodePoint(0x2800+masks[i]),rgb:ink[i],bg:null}:cell;
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
return {w,h,point,line,arc,spark,glyph,compose};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function positions(g){
|
|
109
|
+
const {minX,maxX,centerY}=g;
|
|
110
|
+
// Accent zone on left of the body, where the Q usually has room for fire.
|
|
111
|
+
return {
|
|
112
|
+
left:minX,
|
|
113
|
+
near:Math.max(3,minX-3),
|
|
114
|
+
mouth:minX+Math.max(5,Math.min(12,(maxX-minX)*.29)),
|
|
115
|
+
y:centerY+1,
|
|
116
|
+
mx:(minX+maxX)/2,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function sentinel(canvas,g,t,anchors){
|
|
120
|
+
const {point,line,arc,spark}=canvas,p=positions(g);
|
|
121
|
+
const {left}=p,y=anchors?.mouth?.y??p.y;
|
|
122
|
+
const mouthX=anchors?.mouth?.x??p.mouth;
|
|
123
|
+
const h=canvas.h;
|
|
124
|
+
const approach=phase(t,100,920),barrier=phase(t,580,1060),flame=phase(t,1200,2800);
|
|
125
|
+
// Two approaching abstract threat fragments, never a person or actual target.
|
|
126
|
+
for(let k=0;k<3;k++){
|
|
127
|
+
const dx=Math.max(3,left-20+k*2), dest=Math.max(3,left-5);
|
|
128
|
+
const px=dx+(dest-dx)*approach;
|
|
129
|
+
const py=h*(.37+k*.115)+Math.sin(t*.003+k)*.3;
|
|
130
|
+
if(t<1650){
|
|
131
|
+
line(px-.7,py-.27,px+.7,py+.27,rgb.threat,1);
|
|
132
|
+
line(px-.3,py+.45,px+.65,py-.35,rgb.threat);
|
|
133
|
+
}else if(t<3200){
|
|
134
|
+
for(let j=0;j<7;j++){
|
|
135
|
+
const a=j*TAU/7+k*2, dist=(t-1600)/180*(.3+fakeHash(k*73+j)*.6);
|
|
136
|
+
const glow=t<2300?rgb.ember:rgb.muted;
|
|
137
|
+
point(dest+Math.cos(a)*dist,py+Math.sin(a)*dist*.42,glow);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Azure crescent shields the vulnerable LEFT face and flickers on collision.
|
|
142
|
+
if(t>480&&t<3480){
|
|
143
|
+
const sx=Math.max(4,Math.min(left+5,mouthX-2)),
|
|
144
|
+
rx=Math.max(2.5,Math.min(11,left*.34)),ry=Math.max(2.4,h*.36);
|
|
145
|
+
const sweep=phase(t,480,1000);
|
|
146
|
+
arc(sx,y,rx,ry,PI/2+(.6*(1-sweep)),3*PI/2-(.6*(1-sweep)),
|
|
147
|
+
t<1050?rgb.azure:rgb.shield, t>800&&t<1180?1:0);
|
|
148
|
+
if(t>800&&t<1520){
|
|
149
|
+
const intensity=1-phase(t,1120,1550);
|
|
150
|
+
spark(Math.max(2,sx-rx),y-.5,t,2.0*intensity,rgb.shield);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Forward-facing defensive blue fire. The Q's eyes/body are not overwritten.
|
|
154
|
+
// It is a DEMO of scorched hostile symbols, never an asserted mitigation.
|
|
155
|
+
if(t>1200&&t<3300){
|
|
156
|
+
const reach=Math.max(3,Math.min(25,mouthX-3));
|
|
157
|
+
for(let i=0;i<65;i++){
|
|
158
|
+
const f=i/65, born=1230+f*1550+(fakeHash(i*11+5)-.5)*180, age=t-born;
|
|
159
|
+
if(age<0||age>750)continue;
|
|
160
|
+
const u=clamp(age/700),dist=reach*u*(.8+fakeHash(i+9)*.3);
|
|
161
|
+
// Emitted at the actual dragon's snout, not at an assumed Q-ring.
|
|
162
|
+
const px=mouthX-dist,py=y-.2+Math.sin(i*3.17+u*11)*(.2+u*.85);
|
|
163
|
+
point(px,py,u<.45?rgb.burn:rgb.azure,(i%11===0)?1:0);
|
|
164
|
+
if(i%9===0)point(px-1,py-.2,rgb.white);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if(t>3250)point(left+5,y,rgb.azure,1);
|
|
168
|
+
}
|
|
169
|
+
function lucid(c,g,t){
|
|
170
|
+
const {point,arc,line,glyph}=c,p=positions(g),l=Math.max(3,p.left-4),y=c.h*.52;
|
|
171
|
+
const sweep=phase(t,450,2600);
|
|
172
|
+
// Observations radiate outward; the 'unknown' remains explicitly marked.
|
|
173
|
+
if(t>300){
|
|
174
|
+
for(let k=1;k<=3;k++){
|
|
175
|
+
const radius=2+k*2.2*sweep;
|
|
176
|
+
arc(l+1,y,radius,radius*.35,PI*.85,PI*1.9,rgb.violet);
|
|
177
|
+
}
|
|
178
|
+
line(l+1,y,l+1+Math.min(9,p.left*.42)*Math.cos(-1.9+sweep*TAU),
|
|
179
|
+
y+Math.min(3,c.h*.19)*Math.sin(-1.9+sweep*TAU),rgb.white);
|
|
180
|
+
if(t>1750){glyph(Math.max(2,l-4),Math.round(y)-2,'?',rgb.muted);point(Math.max(3,l-1),y+.4,rgb.violet);}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function hyperion(c,g,t){
|
|
184
|
+
const {line,point,arc,glyph}=c,p=positions(g);
|
|
185
|
+
const left=Math.max(3,p.left-12),y=c.h*.49;
|
|
186
|
+
const lift=phase(t,250,1100),built=phase(t,1100,2650);
|
|
187
|
+
// A tiny 3-D guest container built one edge at a time.
|
|
188
|
+
if(lift>0){
|
|
189
|
+
arc(p.left+3,y-1,Math.min(8,p.left*.25),Math.min(3,c.h*.2),PI,2*PI,rgb.gold);
|
|
190
|
+
}
|
|
191
|
+
const w=6+Math.min(5,p.left*.18),h=Math.min(3.2,c.h*.16);
|
|
192
|
+
line(left,y,left+w*built,y,rgb.gold);
|
|
193
|
+
line(left,y,left,y+h*built,rgb.gold);
|
|
194
|
+
if(t>1500){
|
|
195
|
+
line(left+w*built,y,left+w*built,y+h*built,rgb.ember);
|
|
196
|
+
line(left,y+h*built,left+w*built,y+h*built,rgb.ember);
|
|
197
|
+
}
|
|
198
|
+
if(t>2400&&t<3650){
|
|
199
|
+
const x=left+w/2,yy=y+h/2;point(x,yy,rgb.gold,1);glyph(Math.round(x),Math.round(yy),'◆',rgb.white);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function seer(c,g,t){
|
|
203
|
+
const {line,arc,point,glyph}=c,p=positions(g);
|
|
204
|
+
const x0=Math.max(2,p.left-21),x1=Math.max(x0+7,p.left-3),y=c.h*.53;
|
|
205
|
+
const progress=phase(t,350,2850);
|
|
206
|
+
const gap=x0+(x1-x0)*.58;
|
|
207
|
+
for(let i=0;i<7;i++){
|
|
208
|
+
const x=x0+(x1-x0)*i/6;
|
|
209
|
+
if((x-x0)/(x1-x0)>progress)continue;
|
|
210
|
+
if(Math.abs(x-gap)<2&&t<2200)continue;
|
|
211
|
+
point(x,y+Math.sin(i*.9)*.55,rgb.teal,1);
|
|
212
|
+
if(i&&i!==4&&progress>i/6)line(x-1.4,y+.1,x-.5,y+.1,rgb.green);
|
|
213
|
+
}
|
|
214
|
+
if(t>1600){
|
|
215
|
+
const rx=1.4+Math.sin(t/380)*.18;
|
|
216
|
+
arc(gap,y,rx,rx*.72,0,TAU,rgb.white);
|
|
217
|
+
if(t>2450)glyph(Math.round(gap),Math.round(y),'•',rgb.green);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function nova(c,g,t){
|
|
221
|
+
const {line,point,arc,spark}=c,p=positions(g);
|
|
222
|
+
const x0=Math.max(2,p.left-25),x1=Math.max(x0+8,p.left-2),y=c.h*.55;
|
|
223
|
+
const thrust=phase(t,500,2250),travel=x0+(x1-x0)*thrust;
|
|
224
|
+
for(let i=0;i<5;i++){
|
|
225
|
+
const x=x0+(x1-x0)*i/4;
|
|
226
|
+
point(x,y+(i%2)*.5,rgb.green,1);
|
|
227
|
+
if(i>0&&t>350)line(x-2,y,x-1,y,rgb.teal);
|
|
228
|
+
}
|
|
229
|
+
if(t>450&&t<2750){
|
|
230
|
+
spark(travel,y+.1,t,1.4,rgb.green);
|
|
231
|
+
for(let i=0;i<9;i++){
|
|
232
|
+
const trail=travel-i*1.1;
|
|
233
|
+
if(trail<=x0-2)continue;
|
|
234
|
+
point(trail,y+.4*Math.sin(t*.013+i),i%3?rgb.green:rgb.white);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if(t>2200)arc(x1,y,2.2,1.2,0,TAU*.8,rgb.green);
|
|
238
|
+
}
|
|
239
|
+
function vulcan(c,g,t){
|
|
240
|
+
const {point,line,spark,glyph}=c,p=positions(g);
|
|
241
|
+
const x=Math.max(4,p.left-13),y=c.h*.49;
|
|
242
|
+
glyph(x,Math.round(y),'〈',rgb.teal);glyph(x+8,Math.round(y),'〉',rgb.teal);
|
|
243
|
+
line(x+1,y+2,x+7,y+2,rgb.muted);
|
|
244
|
+
if(t>500&&t<1900){
|
|
245
|
+
const q=phase(t,500,1600);
|
|
246
|
+
line(x+4,y-3,x+4-q*2,y-1+q*3,rgb.teal,1);
|
|
247
|
+
point(x+4-q*2,y-1+q*3,rgb.white);
|
|
248
|
+
}
|
|
249
|
+
if(t>1600&&t<2700){
|
|
250
|
+
spark(x+4,y+2,t,2.1*(1-phase(t,2100,2700)),rgb.ember);
|
|
251
|
+
point(x+4,y+2,rgb.white,1);
|
|
252
|
+
}
|
|
253
|
+
if(t>2750) {
|
|
254
|
+
for(let i=0;i<3;i++)point(x+2+i*2,y+1.5,rgb.teal);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function quox(c,g,t){
|
|
258
|
+
const {arc,point,line,spark}=c,p=positions(g);
|
|
259
|
+
const left=Math.max(4,p.left-13),cx=Math.max(5,p.left-6),cy=c.h*.5;
|
|
260
|
+
for(let i=0;i<4;i++){
|
|
261
|
+
const a=i*TAU/4+phase(t,450,2850)*TAU*.8;
|
|
262
|
+
const r=3.5+phase(t,300,1650)*3;
|
|
263
|
+
const tx=cx+Math.cos(a)*r*1.7,ty=cy+Math.sin(a)*r*.8;
|
|
264
|
+
if(t>350) {
|
|
265
|
+
point(tx,ty,i%2?rgb.violet:rgb.azure,1);
|
|
266
|
+
if(t>1200&&t<3100)line(tx,ty,cx,cy,rgb.muted);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if(t>2100&&t<3300)spark(cx,cy,t,2.4,rgb.white);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const DRAWERS=Object.freeze({
|
|
273
|
+
quox,lucid,hyperion,seer,sentinel,nova,vulcan,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Visual-only signature action. Base frame is immutable.
|
|
278
|
+
* Even at 32 columns, clamps effects to a blank perimeter.
|
|
279
|
+
* A static image can be requested by using still=true.
|
|
280
|
+
*/
|
|
281
|
+
export function renderPower(scene,t,base,{still=false,theme='dark',mono=false,anchors=null}={}){
|
|
282
|
+
const profile=POWER_BY_SCENE.get(scene);
|
|
283
|
+
if(!profile)throw new RangeError('Unknown power: '+scene);
|
|
284
|
+
if(!Array.isArray(base)||!base.length||!Array.isArray(base[0]))
|
|
285
|
+
throw new TypeError('Power base must be a nonempty matrix of terminal cells.');
|
|
286
|
+
const h=base.length,w=base[0].length;
|
|
287
|
+
if(h<9||w<32||base.some(row=>row.length!==w))
|
|
288
|
+
throw new RangeError('Power viewport must be rectangular, at least 32×9.');
|
|
289
|
+
const canvas=createCanvas(w,h,{theme,mono});
|
|
290
|
+
const time=still?Math.min(profile.duration*.6,2100):clamp(Number.isFinite(t)?t:0,0,profile.duration);
|
|
291
|
+
DRAWERS[profile.id](canvas,spriteBounds(base),time,anchors);
|
|
292
|
+
const result=canvas.compose(base);
|
|
293
|
+
return result;
|
|
294
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signature powers: visual-only, source-of-truth DISPLAY NAMES.
|
|
3
|
+
*
|
|
4
|
+
* No power performs an agent action. The Quox host may select a power on a
|
|
5
|
+
* witnessed event, but must NEVER infer success or mitigation from the art.
|
|
6
|
+
* Non-animated small/still fallbacks are supported by the same pure renderer.
|
|
7
|
+
*/
|
|
8
|
+
export const POWERS = Object.freeze([
|
|
9
|
+
{
|
|
10
|
+
displayName:'Quox',id:'quox',scene:'power-quox',base:'hero',duration:3600,
|
|
11
|
+
name:'Convergence',tagline:'Calls the constellation together',
|
|
12
|
+
effect:'Cyan and violet threads bring distant sparks into an orbit around the original Q.',
|
|
13
|
+
attach:'Quox here. Who shall we bring together?',
|
|
14
|
+
phases:['summon','connect','converge','settle'],
|
|
15
|
+
visualOnly:true,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
displayName:'Lucid',id:'lucid',scene:'power-lucid',base:'situation',duration:3600,
|
|
19
|
+
name:'The Unveiling',tagline:'Reveals what is known, and the gaps',
|
|
20
|
+
effect:'Violet survey arcs sweep across a hazy unknown, lighting facts without hiding blind spots.',
|
|
21
|
+
attach:'Lucid here. This is what I can see.',
|
|
22
|
+
phases:['listen','sweep','unveil','acknowledge unknown'],
|
|
23
|
+
visualOnly:true,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
displayName:'Hyperion',id:'hyperion',scene:'power-hyperion',base:'provision',duration:3800,
|
|
27
|
+
name:'Worldforge',tagline:'Builds a new safe little world',
|
|
28
|
+
effect:'Amber geometry assembles into a miniature VM capsule under Hyperion’s protective wings.',
|
|
29
|
+
attach:'Hyperion standing by. Shall I prepare a machine?',
|
|
30
|
+
phases:['survey','raise wings','assemble capsule','hold for authority'],
|
|
31
|
+
visualOnly:true,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
displayName:'Seer',id:'seer',scene:'power-seer',base:'inspect',duration:3500,
|
|
35
|
+
name:'Pathfinder',tagline:'Illuminates the missing path',
|
|
36
|
+
effect:'Emerald tracer dots follow a route, stop at a gap, and illuminate the missing link.',
|
|
37
|
+
attach:'Seer here. Show me the trail of evidence.',
|
|
38
|
+
phases:['scan','trace','find the gap','report'],
|
|
39
|
+
visualOnly:true,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
displayName:'Sentinel',id:'sentinel',scene:'power-sentinel',base:'guard',duration:3800,
|
|
43
|
+
name:'Aegis Inferno',tagline:'Shields, intercepts, scorches',
|
|
44
|
+
effect:'Incoming red threat sparks meet an azure shield. Wings brace, then a fierce blue defensive flame incinerates the abstract intruders.',
|
|
45
|
+
attach:'Sentinel attached. Perimeter ready.',
|
|
46
|
+
phases:['detect','intercept','scorch','guard'],
|
|
47
|
+
visualOnly:true,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
displayName:'Nova',id:'nova',scene:'power-nova',base:'deploy',duration:3200,
|
|
51
|
+
name:'Greenline',tagline:'Carries work through the pipeline',
|
|
52
|
+
effect:'Electric-green wingbeats streak past luminous pipeline nodes, without claiming a deploy succeeded.',
|
|
53
|
+
attach:'Nova ready. Give me the go-ahead.',
|
|
54
|
+
phases:['check','ignite','dash','await result'],
|
|
55
|
+
visualOnly:true,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
displayName:'Vulcan',id:'vulcan',scene:'power-vulcan',base:'review',duration:3700,
|
|
59
|
+
name:'Codeforge',tagline:'Hammers rough code into shape',
|
|
60
|
+
effect:'A small glowing code fragment meets precise teal sparks, a tiny anvil-strike and a careful inspection.',
|
|
61
|
+
attach:'Vulcan ready. Let me examine the code.',
|
|
62
|
+
phases:['inspect','heat','strike','inspect again'],
|
|
63
|
+
visualOnly:true,
|
|
64
|
+
},
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
export const POWER_BY_NAME=new Map(POWERS.map(p=>[p.displayName,p]));
|
|
68
|
+
// Internal keys exist solely to bridge scene IDs; user-facing identity is displayName.
|
|
69
|
+
export const POWER_BY_ID=new Map(POWERS.map(p=>[p.id,p]));
|
|
70
|
+
export const POWER_BY_SCENE=new Map(POWERS.map(p=>[p.scene,p]));
|
|
71
|
+
export const POWER_SCENE_NAMES=Object.freeze(POWERS.map(p=>p.scene));
|
|
72
|
+
export function powerFor(displayName) {
|
|
73
|
+
return POWER_BY_NAME.get(displayName)||null;
|
|
74
|
+
}
|