暗黑魔碟重设计:美术大改=8扇区缝合装甲碟(扇区纹饰缓转眼不转)+红色六边形能量窗呼吸+扇区红裂纹+12节分节环红发光条+紫红竖瞳魔眼(追踪玩家,P3收竖缝);变形沿用环裂成犄角+爆甲机制;攻击小改=签名弹环P2起加内层反向橙弹(双层编织紫+橙)/ringBig改橙弹。另:drawProgenitor/drawAssault迁入boss_art.js(render.js降至477行),AGENTS.md同步。仅语法检查通过,未运行验证

This commit is contained in:
JOJO 2026-08-06 03:31:34 +08:00
parent a9610dfed1
commit 10e5744da7
4 changed files with 143 additions and 118 deletions

View File

@ -42,7 +42,8 @@ star-raid/
│ ├── core.js # 游戏状态 G、发射逻辑、explodeFx 爆炸特效库
│ ├── combat.js # 子弹运动/碰撞/光束判定/zones 区域效果
│ ├── enemies.js # ENEMIES 杂兵表、BOSSES 表、bossMain/bossMinor 攻击库
│ ├── render.js # 全部渲染drawBoss 各 Boss 绘制、zones、子弹
│ ├── render.js # 世界渲染drawBoss 分支调度、血条、子弹、zones
│ ├── boss_art.js # Boss 美术库drawProgenitor/drawAssault/drawPiano/drawUsurper/drawSaucer
│ ├── sprites.js # 像素字符画 bake 系统、SPR 精灵表、BHALO 弹光晕
│ ├── audio.js # SFX 音效WebAudio 合成)
│ └── ui.js # 菜单/图鉴/rush 选择/老虎机/结算

View File

@ -128,3 +128,138 @@ function drawUsurper(b,t,fl,ph2,mp){
ctx.restore();ctx.globalAlpha=1;}
}
}
/* ---- 8+/+ ----
变形沿用原机制mp优先P2圆环绕顶轴外旋裂开+魔眼点亮P3爆甲壳板飞离+铆钉伸长成刺+犄角炮台 */
function drawSaucer(b,t,fl,ph2,mp){
let open,fo,eyeG;
if(ph2===0){open=0;fo=0;eyeG=0;}
else if(ph2===1){open=mp!==null?mp:1;fo=0;eyeG=mp!==null?mp:1;}
else{open=1;fo=mp!==null?mp:1;eyeG=1;}
const R=30+open*3;
// —— 碟心基盘+8装甲扇区扇区纹饰缓转眼不转=诡异感;扇区间红裂纹随阶段增亮) ——
circ(0,0,20,"#1a1030");circ(0,0,15,"#12081c");
ctx.save();ctx.rotate(t*0.004);
for(let k=0;k<8;k++){ctx.save();ctx.rotate(k*TAU/8);
poly([0,-19,6,-14,4,-8,-4,-8,-6,-14],k%2?"#241430":"#2a1a3a"); // 扇区装甲(明暗交替)
px(-0.5,-19,1,11,open>0?"#ff3355":"#7a1622"); // 扇区间裂纹P2转亮红
ctx.restore();}
for(let k=0;k<8;k++){const a=(k+0.5)*TAU/8; // 红色六边形能量窗呼吸明灭P2+更亮)
ctx.globalAlpha=(0.3+0.3*Math.sin(t*0.08+k*1.1))*(0.5+0.5*eyeG);
circ(Math.sin(a)*12.5,-Math.cos(a)*12.5,2.2,"#ff3355");ctx.globalAlpha=1;}
ctx.restore();
// —— 眼窝(常存)+紫红魔眼P2点亮竖瞳追踪玩家P3瞳孔收成竖缝 ——
px(-11,-5,7,6,"#241430");px(4,-5,7,6,"#241430");
if(eyeG>0){ctx.globalAlpha=eyeG;
px(-10,-4,5,4,"#c86bff");px(5,-4,5,4,"#c86bff");
const pu=clamp((G.p.x-b.x)*0.02,-1.5,1.5);
px(-8.5+pu,-4,ph2>=2?1:1.8,4,fl||"#ff3355");px(6.5+pu,-4,ph2>=2?1:1.8,4,fl||"#ff3355");
ctx.globalAlpha=1;}
// —— 外环12节分节环节间红发光条呼吸变形=绕顶轴外旋裂开、下端伸长变犄角(同一几何体连续演化) ——
const rot=open*0.55;
for(const sd of[-1,1]){ctx.save();
ctx.translate(0,-R);ctx.rotate(-sd*rot);ctx.translate(0,R);
const a0=sd<0?Math.PI/2:-Math.PI/2,a1=sd<0?Math.PI*1.5:Math.PI/2;
for(let sg=0;sg<6;sg++){const sa0=a0+sg*(a1-a0)/6+0.04,sa1=a0+(sg+1)*(a1-a0)/6-0.04; // 6节/半环
ctx.strokeStyle="#2a1a3a";ctx.lineWidth=11;
ctx.beginPath();ctx.arc(0,0,R,sa0,sa1);ctx.stroke();
ctx.strokeStyle="#55336e";ctx.lineWidth=4.5;
ctx.beginPath();ctx.arc(0,0,R,sa0,sa1);ctx.stroke();}
for(let k=0;k<5;k++){const ag=a0+(k+1)*(a1-a0)/6; // 节间红发光条(呼吸明灭)
ctx.globalAlpha=0.45+0.4*Math.sin(t*0.09+k);
px(Math.cos(ag)*R-1,Math.sin(ag)*R-4,2,8,"#ff3355");ctx.globalAlpha=1;}
const hL=3+open*26; // 半环下端伸长变尖:圆环下半部分延续为朝下的锥刺(同一几何体)
poly([-6.5,R,6.5,R,2.5,R+hL*0.55,0,R+hL,-2.5,R+hL*0.55],"#2a1a3a");
poly([-3.5,R,3.5,R,1.2,R+hL*0.5,0,R+hL*0.8,-1.2,R+hL*0.5],"#55336e");
for(let k=0;k<3;k++){const aa=a0+(0.35+k*0.2)*(a1-a0);
const rx=Math.cos(aa),ry=Math.sin(aa),tx2=-ry,ty2=rx;
const bl=2+9*fo; // 铆钉→尖刺连续伸长
poly([rx*(R+5)+tx2*2.5,ry*(R+5)+ty2*2.5, rx*(R+5)-tx2*2.5,ry*(R+5)-ty2*2.5, rx*(R+5+bl),ry*(R+5+bl)],fo>0.6?(fl||"#ff3355"):"#241430");
if(fo<0.99){ctx.save(); // 外壳板:爆甲时旋转飞离(有运动轨迹,非消失)
ctx.translate(rx*(R+9+fo*22)+tx2*fo*sd*9,ry*(R+9+fo*22)+ty2*fo*sd*9);
ctx.rotate(aa+Math.PI/2+fo*sd*1.7);ctx.globalAlpha=1-fo;
px(-5,-3,10,6,"#2a1a3a");px(-5,-3,10,2,"#55336e");
ctx.restore();ctx.globalAlpha=1;}}
{const ta=a0+0.5*(a1-a0),bl2=2+6*fo; // 红色犄角炮台:基座常存,爆甲后炮管伸出点亮
ctx.save();ctx.translate(Math.cos(ta)*(R+2),Math.sin(ta)*(R+2));ctx.rotate(ta+Math.PI/2);
px(-2.5,-3,5,4,"#241430");
px(-1.5,-3-bl2,3,bl2,fo>0.4?(fl||"#ff3355"):"#3a2248");
ctx.restore();}
ctx.restore();}
circ(0,-R,4,"#55336e"); // 顶部铰链节
}
/* ---- 太空原种/暗黑突击绘制从render.js迁入集中管理Boss美术 ---- */
function drawProgenitor(b,t,fl,tf){ // 太空原种·装甲虫兽tf 0=装甲态→1=血肉态P1小翅芽→P2骨膜巨翼无复眼大颚+感器红缝=虫形压迫感);摆尾衔接于躯体之下(尾根被加长的身体覆住,无缝)
ctx.rotate(Math.sin(t*0.05)*0.04); // 悬停微摇
const wS=0.22+0.78*tf; // 翼展P1小翅芽→P2全展同一对翼连续缩放
// —— 骨膜翼一对3根骨刺撑起膜面P1灰甲小翼→P2血红膜+暗红骨,同一几何体罩色) ——
for(const sd of[-1,1]){ctx.save();
const flut=b.rainT>0?Math.sin(t*0.62)*0.13:Math.sin(t*0.07)*0.03; // 红雨前摇/洒落期双翼高频抖动
ctx.translate(sd*7,-8);ctx.scale(sd*wS,1+flut); // 呼吸/抖动+镜像
poly([0,-4,14,-16,34,-16,54,-10,48,0,34,6,16,4,0,2],"#c9d6e2"); // 膜面主甲
poly([0,-4,14,-16,34,-16,54,-10,40,-8,18,-8,0,-1],"#e8f0f6"); // 上缘受光
poly([0,2,16,4,34,6,48,0,36,0,14,2],"#8498ac"); // 下缘暗面
for(const tp of[[14,-15],[34,-15],[54,-9]])drawSeg(2,-2,tp[0],tp[1],2,"#8498ac",1); // 3根骨刺辐射
if(tf>0){ctx.globalAlpha=tf*0.85;
poly([0,-4,14,-16,34,-16,54,-10,48,0,34,6,16,4,0,2],"#c02030"); // P2膜面转血红同面罩色
for(const tp of[[14,-15],[34,-15],[54,-9]])drawSeg(2,-2,tp[0],tp[1],2,"#7a1622",tf); // P2骨刺转暗红
ctx.globalAlpha=1;}
ctx.restore();}
// —— 摆尾先画在躯体下层尾根被加长的身体覆住无缝衔接5节逐节相位摆动P2节间血红肉显露+整体伸长 ——
const segH=5.2+tf*1.6,gap=11;
for(let i=4;i>=0;i--){const sw=Math.sin(t*0.055+i*0.7)*(0.8+i),sy=gap+i*segH,hw=9-i*1.5;
if(tf>0&&i<4){ctx.globalAlpha=tf*0.85;px(sw-hw+2,sy+segH-2,(hw-2)*2,2,"#c8202e");ctx.globalAlpha=1;} // 节间血肉环
poly([sw-hw,sy,sw+hw,sy,sw+hw-2,sy+segH,sw-hw+2,sy+segH],i%2?"#b8c8d8":"#c9d6e2"); // 尾节甲片
px(sw-hw,sy,3,segH,"#e8f0f6");px(sw+hw-3,sy,3,segH,"#8498ac"); // 左亮右暗
for(const sd of[-1,1])poly([sw+sd*(hw+1),sy+1,sw+sd*(hw+4),sy+2.5,sw+sd*(hw+1),sy+4],"#e8dcc0"); // 节侧骨刺
}
const swT=Math.sin(t*0.055+2.8)*4.8,tipY=gap+4*segH; // 尾末生殖腔花弹发射口与enemies.js同源公式
circ(swT,tipY+2,2.6+Math.sin(t*0.11)*0.6,fl||(tf>0.5?"#ff2a3c":"#ff5566"));
circ(swT,tipY+2,1.1,"#ffdfe5");
// —— 壳下血肉(常存于背甲之下,滑开才见;血红,非粉) ——
if(tf>0){ctx.globalAlpha=tf*0.9;
poly([0,-36,9,-29,11,-14,10,2,9,14,7,16,-7,16,-9,14,-10,2,-11,-14,-9,-29],"#c8202e");
px(-2,-30,4,45,"#e8393a");ctx.globalAlpha=1;}
// —— 背甲两瓣:绕尾端枢轴楔开(顶端外摆露血肉、尾端原地不动,衔接处不变宽;同一几何体旋转) ——
for(const sd of[-1,1]){ctx.save();ctx.translate(0,14);ctx.rotate(sd*tf*0.28);ctx.translate(0,-14);
poly([0,-40,sd*11,-37,sd*15,-28,sd*13,-12,sd*12,4,sd*11,14,0,14],sd<0?"#c9d6e2":"#9fb0c2"); // 末端平边与尾节同材质(#c9d6e2
px(sd<0?-11:0,12,11,2,sd<0?"#e8f0f6":"#8498ac"); // 底缘左亮右暗(与尾节描边一致,接缝读作甲片缝)
poly([0,-40,sd*11,-37,sd*15,-28,sd*7,-26,sd*5,-36],"#e8f0f6"); // 受光斜面
for(let i=0;i<4;i++)px(sd*(4+i*2.8)-1,-24+i*9,2,5,"#5a6c80"); // 甲片板缝
const crk=Math.max(0,Math.min(1,(tf-0.15)/0.4))*10; // 裂纹随tf生长延长非新增
if(crk>0.5)px(sd*6-1,-18,2,crk,"#8a2a3a");
ctx.restore();}
// —— 头部:圆顶甲+锯齿犄角+下弯大颚(无复眼=虫形压迫感)+一线感器红缝 ——
circ(0,-34,10,"#dde7f0");px(-9,-36,18,3,"#9fb0c2");
for(const sd of[-1,1]){poly([sd*6,-40,sd*15,-49,sd*12,-51,sd*4,-43],"#8498ac");
poly([sd*10,-44,sd*13,-48,sd*11,-43],"#5a6c80"); // 犄角锯齿棱
px(sd*9-1,-46,2,4,"#7dff8a");} // 犄角绿纹
px(-5,-33,10,1.6,t%30<15?"#ff3355":"#7a1622"); // 感器红缝(明灭)
for(const sd of[-1,1]){poly([sd*3,-26,sd*9,-22,sd*8,-14,sd*5,-13,sd*2,-20],"#e8dcc0"); // 下弯大颚(骨白)
px(sd*5,-16,2,3,"#8a6a4a");} // 颚尖暗色
if(b.eyeGlow>0){ctx.globalAlpha=b.eyeGlow/20; // 击发余辉:大颚+感器缝闪白
px(-8,-22,4,3,"#fff");px(4,-22,4,3,"#fff");px(-5,-33,10,1.6,"#fff");ctx.globalAlpha=1;}
// —— 头侧青色管囊P2转荧绿 ——
for(const sd of[-1,1]){px(sd*15-2.5,-18,5,12,tf>0.5?"#7dff8a":(t%40<20?"#7fd8ff":"#43b8ff"));
px(sd*15-1.5,-20,3,3,"#55648a");}
}
function drawAssault(t,fl,swp,ph,mp){ // 暗黑突击swp=翼展角;炮管/推进器随阶段增多)
const sw=swp*Math.PI/180;
for(const sd of[-1,1]){
const tx=sd*(8+52*Math.sin(sw)),ty=14-52*Math.cos(sw);
poly([sd*4,6,tx,ty,tx-sd*9,ty+11,sd*4,20],ph>=2?"#c01a2e":"#a01224");
poly([tx-sd*2,ty-6,tx+sd*3,ty-14,tx+sd*7,ty-3],"#6e0c1a");}
poly([0,38,-5,6,-3,-26,3,-26,5,6],"#d4233a");
px(-2,8,4,8,fl||"#ffd23d");
const guns=[[16,4],[-16,4],[30,-4],[-30,-4],[42,-10],[-42,-10]];
const gn=ph>=2?6:ph>=1?4:2,pgn=ph===2?4:ph===1?2:0;
for(let i=0;i<gn;i++){let gl=8;if(mp!==null&&i>=pgn)gl=Math.max(1,8*mp); // 新炮管生长
px(guns[i][0]-1.5,guns[i][1],3,gl,"#3a0a10");px(guns[i][0]-1,guns[i][1]+gl-2,2,2,"#ff9a3d");}
const eng=[[7,-26],[-7,-26],[0,-30],[14,-22]];
const en=ph>=2?4:ph>=1?3:2,pen=ph===2?3:ph===1?2:0;
for(let i=0;i<en;i++){let el=5+(t%4<2?2:0);if(mp!==null&&i>=pen)el=Math.max(1,el*mp); // 新推进器
px(eng[i][0]-2,eng[i][1],4,3,"#5a0a14");
px(eng[i][0]-1.5,eng[i][1]-3-el,3,el,t%6<3?"#ff9a3d":"#ffcf5c");}
if(ph>=1){poly([-52,-14,-24,-3,-22,-1,-50,-11],"#ffdfe5");poly([52,-14,24,-3,22,-1,50,-11],"#ffdfe5");}
if(ph>=2){px(-4,-16,8,12,fl||"#ff3355");px(-2,-14,4,8,"#ffdfe5");}
}
/* ---- Boss光束与浮游炮渲染 ---- */

View File

@ -446,6 +446,9 @@ function bossMain(b,def,fm){
const n2=12+ph*5;
for(let i=0;i<n2;i++){const ea=(b.rotA||0)+i/n2*TAU;
ebul(b.x+Math.cos(ea)*30,b.y+Math.sin(ea)*30,ea,1.9+0.15*ph,"dartP",15);}
if(ph>=1){const n3=6+ph*3; // P2起内层反向橙弹环双层反向编织致敬参考图紫+橙)
for(let i=0;i<n3;i++){const ea2=-(b.rotA||0)+i/n3*TAU+TAU/(n3*2);
ebul(b.x+Math.cos(ea2)*22,b.y+Math.sin(ea2)*22,ea2,1.5+0.15*ph,"orbO",13);}}
b.rotA=(b.rotA||0)+0.22;
b.mainT=Math.round((ph>=2?24:ph===1?30:36)/fm);
if(ph>=1&&(!b.eyeT||b.eyeT<=0)){const ex=ap2.x;
@ -485,7 +488,7 @@ function bossMinor(b,mk,fm){
case "deckVolley": if(b.phase>=1){b.minorT=80;break;}b.deckT=26;b.minorT=Math.round(320/fm);break; // 夺权者X甲板齐射P1限定——最下甲板炮口逐个点亮前摇→6发垂直弹雨
case "pillar": {const px2=rnd(40,W-40);bossBeam(px2,-6,px2,H+8,4,"#b98cff",26,26,18);b.minorT=Math.round(220/fm);break;}
case "eyeBeam": bossBeam(b.x,b.y+6,ap2.x,H+8,4,"#ff3355",22,24,20);b.minorT=Math.round(180/fm);break;
case "ringBig": for(let i=0;i<18;i++)ebul(b.x,b.y,i/18*TAU-b.t*0.03,1.8,"orbP",15);b.minorT=Math.round(230/fm);break;
case "ringBig": for(let i=0;i<18;i++)ebul(b.x,b.y,i/18*TAU-b.t*0.03,1.8,"orbO",15);b.minorT=Math.round(230/fm);break; // 橙环(与签名紫锥拉开)
case "sporeArc": for(let i=0;i<7;i++)ebul(b.x,b.y+14,Math.PI/2+(i-3)*0.26,1.2,"orbG",13);b.minorT=Math.round(210/fm);break; // 原种:慢速孢子幕(绿,与花弹差异化)
case "tentBeam": { // 原种双触手鞭击光束——从头侧管囊伸出锚定相位从垂直向下逐帧向外鞭展sweep连续判定扫到哪判定到哪
const ph0=-G.time*0.014;

View File

@ -171,47 +171,8 @@ function drawBoss(){
for(let i=0;i<8;i++){const a=i*TAU/8+t*0.02,tx3=Math.cos(a)*mw*0.82,ty3=Math.sin(a)*mw*0.82;
poly([tx3-1.5,30+ty3,tx3+1.5,30+ty3,tx3*0.6,30+ty3*0.6],"#e8dcc0");}}
ctx.restore();
}else{ // 暗黑魔碟:核心+圆环;变形=圆环前后裂开、两半各自收缩成犄角(同一几何体连续演化);爆甲=壳板飞离、铆钉伸长成刺
let open,fo,eyeG;
if(b.phase===0){open=0;fo=0;eyeG=0;}
else if(b.phase===1){open=mp!==null?mp:1;fo=0;eyeG=mp!==null?mp:1;}
else{open=1;fo=mp!==null?mp:1;eyeG=1;}
const R=30+open*3; // 外径微扩
circ(0,0,20,"#1a1030");circ(0,0,15,"#12081c");
px(-11,-5,7,6,"#241430");px(4,-5,7,6,"#241430"); // 眼窝常存
if(eyeG>0){ctx.globalAlpha=eyeG; // 赤瞳点亮(非凭空出现)
px(-10,-4,5,4,fl||"#ff3355");px(5,-4,5,4,fl||"#ff3355");
px(-9,-3,2,1,"#ffdfe5");px(6,-3,2,1,"#ffdfe5");
ctx.globalAlpha=1;}
const rot=open*0.55; // 外壳下边分开,以顶部为轴外旋,下端打开
for(const sd of[-1,1]){ctx.save();
ctx.translate(0,-R);ctx.rotate(-sd*rot);ctx.translate(0,R); // 绕顶端轴旋转:下端向外打开
const a0=sd<0?Math.PI/2:-Math.PI/2,a1=sd<0?Math.PI*1.5:Math.PI/2; // 左半环/右半环
ctx.strokeStyle="#55336e";ctx.lineWidth=13;
ctx.beginPath();ctx.arc(0,0,R,a0,a1);ctx.stroke();
ctx.strokeStyle="#6e4490";ctx.lineWidth=5;
ctx.beginPath();ctx.arc(0,0,R,a0,a1);ctx.stroke();
const hL=3+open*26; // 半环下端伸长变尖:圆环下半部分延续为朝下的锥刺(同一几何体)
poly([-6.5,R,6.5,R,2.5,R+hL*0.55,0,R+hL,-2.5,R+hL*0.55],"#55336e");
poly([-3.5,R,3.5,R,1.2,R+hL*0.5,0,R+hL*0.8,-1.2,R+hL*0.5],"#6e4490");
for(let k=0;k<3;k++){const aa=a0+(0.35+k*0.2)*(a1-a0);
const rx=Math.cos(aa),ry=Math.sin(aa),tx2=-ry,ty2=rx;
const bl=2+9*fo; // 铆钉→尖刺连续伸长
poly([rx*(R+5)+tx2*2.5,ry*(R+5)+ty2*2.5, rx*(R+5)-tx2*2.5,ry*(R+5)-ty2*2.5, rx*(R+5+bl),ry*(R+5+bl)],fo>0.6?"#c86bff":"#2a1a3a");
if(fo<0.99){ctx.save(); // 外壳板:爆甲时旋转飞离(有运动轨迹,非消失)
ctx.translate(rx*(R+9+fo*22)+tx2*fo*sd*9,ry*(R+9+fo*22)+ty2*fo*sd*9);
ctx.rotate(aa+Math.PI/2+fo*sd*1.7);ctx.globalAlpha=1-fo;
px(-5,-3,10,6,"#55336e");px(-5,-3,10,2,"#6e4490");
ctx.restore();ctx.globalAlpha=1;}}
{const ta=a0+0.5*(a1-a0),bl2=2+6*fo; // 红色炮台:基座常存,爆甲后炮管伸出点亮
ctx.save();ctx.translate(Math.cos(ta)*(R+2),Math.sin(ta)*(R+2));ctx.rotate(ta+Math.PI/2);
px(-2.5,-3,5,4,"#2a1a3a");
px(-1.5,-3-bl2,3,bl2,fo>0.4?(fl||"#ff3355"):"#3a2248");
ctx.restore();}
for(let k=0;k<2;k++){const aa2=a0+(0.35+k*0.3)*(a1-a0)+Math.sin(t*0.012+k*2)*0.05; // 环面光点
px(Math.cos(aa2)*R-2,Math.sin(aa2)*R-2,4,4,"#c86bff");}
ctx.restore();}
circ(0,-R,4,"#6e4490"); // 顶部铰链节
}else{ // 暗黑魔碟8扇区缝合装甲碟+红能量窗裂纹+紫红竖瞳魔眼变形沿用mp优先
drawSaucer(b,t,fl,ph2,mp);
}
if(flashA>0){ctx.globalAlpha=flashA;ctx.strokeStyle="#fff";
ctx.beginPath();ctx.arc(0,0,b.r+6,0,TAU);ctx.stroke();ctx.globalAlpha=1;}
@ -238,81 +199,6 @@ function drawBoss(){
ctx.fillText(b.transT>0?b.name:(b.name+" "+Math.ceil(pc*100)+"%"),W/2,22);
}
/* ---- Boss 分体绘制助手 ---- */
function drawProgenitor(b,t,fl,tf){ // 太空原种·装甲虫兽tf 0=装甲态→1=血肉态P1小翅芽→P2骨膜巨翼无复眼大颚+感器红缝=虫形压迫感);摆尾衔接于躯体之下(尾根被加长的身体覆住,无缝)
ctx.rotate(Math.sin(t*0.05)*0.04); // 悬停微摇
const wS=0.22+0.78*tf; // 翼展P1小翅芽→P2全展同一对翼连续缩放
// —— 骨膜翼一对3根骨刺撑起膜面P1灰甲小翼→P2血红膜+暗红骨,同一几何体罩色) ——
for(const sd of[-1,1]){ctx.save();
const flut=b.rainT>0?Math.sin(t*0.62)*0.13:Math.sin(t*0.07)*0.03; // 红雨前摇/洒落期双翼高频抖动
ctx.translate(sd*7,-8);ctx.scale(sd*wS,1+flut); // 呼吸/抖动+镜像
poly([0,-4,14,-16,34,-16,54,-10,48,0,34,6,16,4,0,2],"#c9d6e2"); // 膜面主甲
poly([0,-4,14,-16,34,-16,54,-10,40,-8,18,-8,0,-1],"#e8f0f6"); // 上缘受光
poly([0,2,16,4,34,6,48,0,36,0,14,2],"#8498ac"); // 下缘暗面
for(const tp of[[14,-15],[34,-15],[54,-9]])drawSeg(2,-2,tp[0],tp[1],2,"#8498ac",1); // 3根骨刺辐射
if(tf>0){ctx.globalAlpha=tf*0.85;
poly([0,-4,14,-16,34,-16,54,-10,48,0,34,6,16,4,0,2],"#c02030"); // P2膜面转血红同面罩色
for(const tp of[[14,-15],[34,-15],[54,-9]])drawSeg(2,-2,tp[0],tp[1],2,"#7a1622",tf); // P2骨刺转暗红
ctx.globalAlpha=1;}
ctx.restore();}
// —— 摆尾先画在躯体下层尾根被加长的身体覆住无缝衔接5节逐节相位摆动P2节间血红肉显露+整体伸长 ——
const segH=5.2+tf*1.6,gap=11;
for(let i=4;i>=0;i--){const sw=Math.sin(t*0.055+i*0.7)*(0.8+i),sy=gap+i*segH,hw=9-i*1.5;
if(tf>0&&i<4){ctx.globalAlpha=tf*0.85;px(sw-hw+2,sy+segH-2,(hw-2)*2,2,"#c8202e");ctx.globalAlpha=1;} // 节间血肉环
poly([sw-hw,sy,sw+hw,sy,sw+hw-2,sy+segH,sw-hw+2,sy+segH],i%2?"#b8c8d8":"#c9d6e2"); // 尾节甲片
px(sw-hw,sy,3,segH,"#e8f0f6");px(sw+hw-3,sy,3,segH,"#8498ac"); // 左亮右暗
for(const sd of[-1,1])poly([sw+sd*(hw+1),sy+1,sw+sd*(hw+4),sy+2.5,sw+sd*(hw+1),sy+4],"#e8dcc0"); // 节侧骨刺
}
const swT=Math.sin(t*0.055+2.8)*4.8,tipY=gap+4*segH; // 尾末生殖腔花弹发射口与enemies.js同源公式
circ(swT,tipY+2,2.6+Math.sin(t*0.11)*0.6,fl||(tf>0.5?"#ff2a3c":"#ff5566"));
circ(swT,tipY+2,1.1,"#ffdfe5");
// —— 壳下血肉(常存于背甲之下,滑开才见;血红,非粉) ——
if(tf>0){ctx.globalAlpha=tf*0.9;
poly([0,-36,9,-29,11,-14,10,2,9,14,7,16,-7,16,-9,14,-10,2,-11,-14,-9,-29],"#c8202e");
px(-2,-30,4,45,"#e8393a");ctx.globalAlpha=1;}
// —— 背甲两瓣:绕尾端枢轴楔开(顶端外摆露血肉、尾端原地不动,衔接处不变宽;同一几何体旋转) ——
for(const sd of[-1,1]){ctx.save();ctx.translate(0,14);ctx.rotate(sd*tf*0.28);ctx.translate(0,-14);
poly([0,-40,sd*11,-37,sd*15,-28,sd*13,-12,sd*12,4,sd*11,14,0,14],sd<0?"#c9d6e2":"#9fb0c2"); // 末端平边与尾节同材质(#c9d6e2
px(sd<0?-11:0,12,11,2,sd<0?"#e8f0f6":"#8498ac"); // 底缘左亮右暗(与尾节描边一致,接缝读作甲片缝)
poly([0,-40,sd*11,-37,sd*15,-28,sd*7,-26,sd*5,-36],"#e8f0f6"); // 受光斜面
for(let i=0;i<4;i++)px(sd*(4+i*2.8)-1,-24+i*9,2,5,"#5a6c80"); // 甲片板缝
const crk=Math.max(0,Math.min(1,(tf-0.15)/0.4))*10; // 裂纹随tf生长延长非新增
if(crk>0.5)px(sd*6-1,-18,2,crk,"#8a2a3a");
ctx.restore();}
// —— 头部:圆顶甲+锯齿犄角+下弯大颚(无复眼=虫形压迫感)+一线感器红缝 ——
circ(0,-34,10,"#dde7f0");px(-9,-36,18,3,"#9fb0c2");
for(const sd of[-1,1]){poly([sd*6,-40,sd*15,-49,sd*12,-51,sd*4,-43],"#8498ac");
poly([sd*10,-44,sd*13,-48,sd*11,-43],"#5a6c80"); // 犄角锯齿棱
px(sd*9-1,-46,2,4,"#7dff8a");} // 犄角绿纹
px(-5,-33,10,1.6,t%30<15?"#ff3355":"#7a1622"); // 感器红缝(明灭)
for(const sd of[-1,1]){poly([sd*3,-26,sd*9,-22,sd*8,-14,sd*5,-13,sd*2,-20],"#e8dcc0"); // 下弯大颚(骨白)
px(sd*5,-16,2,3,"#8a6a4a");} // 颚尖暗色
if(b.eyeGlow>0){ctx.globalAlpha=b.eyeGlow/20; // 击发余辉:大颚+感器缝闪白
px(-8,-22,4,3,"#fff");px(4,-22,4,3,"#fff");px(-5,-33,10,1.6,"#fff");ctx.globalAlpha=1;}
// —— 头侧青色管囊P2转荧绿 ——
for(const sd of[-1,1]){px(sd*15-2.5,-18,5,12,tf>0.5?"#7dff8a":(t%40<20?"#7fd8ff":"#43b8ff"));
px(sd*15-1.5,-20,3,3,"#55648a");}
}
function drawAssault(t,fl,swp,ph,mp){ // 暗黑突击swp=翼展角;炮管/推进器随阶段增多)
const sw=swp*Math.PI/180;
for(const sd of[-1,1]){
const tx=sd*(8+52*Math.sin(sw)),ty=14-52*Math.cos(sw);
poly([sd*4,6,tx,ty,tx-sd*9,ty+11,sd*4,20],ph>=2?"#c01a2e":"#a01224");
poly([tx-sd*2,ty-6,tx+sd*3,ty-14,tx+sd*7,ty-3],"#6e0c1a");}
poly([0,38,-5,6,-3,-26,3,-26,5,6],"#d4233a");
px(-2,8,4,8,fl||"#ffd23d");
const guns=[[16,4],[-16,4],[30,-4],[-30,-4],[42,-10],[-42,-10]];
const gn=ph>=2?6:ph>=1?4:2,pgn=ph===2?4:ph===1?2:0;
for(let i=0;i<gn;i++){let gl=8;if(mp!==null&&i>=pgn)gl=Math.max(1,8*mp); // 新炮管生长
px(guns[i][0]-1.5,guns[i][1],3,gl,"#3a0a10");px(guns[i][0]-1,guns[i][1]+gl-2,2,2,"#ff9a3d");}
const eng=[[7,-26],[-7,-26],[0,-30],[14,-22]];
const en=ph>=2?4:ph>=1?3:2,pen=ph===2?3:ph===1?2:0;
for(let i=0;i<en;i++){let el=5+(t%4<2?2:0);if(mp!==null&&i>=pen)el=Math.max(1,el*mp); // 新推进器
px(eng[i][0]-2,eng[i][1],4,3,"#5a0a14");
px(eng[i][0]-1.5,eng[i][1]-3-el,3,el,t%6<3?"#ff9a3d":"#ffcf5c");}
if(ph>=1){poly([-52,-14,-24,-3,-22,-1,-50,-11],"#ffdfe5");poly([52,-14,24,-3,22,-1,50,-11],"#ffdfe5");}
if(ph>=2){px(-4,-16,8,12,fl||"#ff3355");px(-2,-14,4,8,"#ffdfe5");}
}
/* ---- Boss光束与浮游炮渲染 ---- */
function drawFunnel(f){ // 蓝白长钉无人机(单架,带光环)
ctx.strokeStyle="rgba(127,216,255,0.7)";ctx.lineWidth=1;
ctx.beginPath();ctx.ellipse(f.x,f.y-1,6,2.5,0,0,TAU);ctx.stroke(); // 光环