starraid/star-raid/js/boss_art.js
JOJO f830edd0c6 钢琴Boss重设计:6种激光模式乱序随机、小攻击改动、大炮塔惩罚激光
- 激光攻击改为6种模式全部乱序随机,局间无间隔:
  顺扫/反扫/顺滑奏/反滑奏(快速逐键),乱序(同时4/8键),齐射(P1:4连不射其余全射;P2保持原随机多键)
- topRain: 同时随机3个炮塔发射绿色弹
- pedal: 两边子弹统一为紫色orbP
- 新增大炮塔惩罚激光:玩家站在炮塔正下方蓄力1秒后发射99999伤害大激光
- drawPiano: 支持多炮塔同时发光
2026-08-06 14:31:53 +08:00

314 lines
21 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
/* ================= Boss 美术库(大改重设计) =================
激光钢琴 drawPiano / 夺权者X drawUsurper / 暗黑魔碟 drawSaucer
设计标准见 .astrion/memory/boss_design_standard.md
大剪影+3~4层配色递进+多层结构+受光逻辑+发光焦点+微动效;变形=同一几何体连续演化 */
/* ---- 激光钢琴穹顶琴键要塞split=裂壳进度0~1 ----
4层同心装甲环带+像素琴键炮口带;变形=环带沿中线阶梯滑开(内层滑更远)露红色管线网+红核 */
function drawPiano(b,t,fl,split){
const ks=b.keys,n=ks?ks.n:21;
// —— 红色管线网(壳下常存,裂壳才见;锯齿线非圆) ——
if(split>0){ctx.globalAlpha=split*0.85;
for(let k=0;k<5;k++){const lx=-40+k*20;
px(lx,-14,1,10,"#ff3355");px(lx,-5,4,1,"#ff3355");px(lx+3,-4,1,8,"#ff3355");}
ctx.globalAlpha=1;}
// —— 红色核心(裂壳显现,搏动;收在穹顶基线之上) ——
if(split>0){ctx.globalAlpha=split;
const cr=8+Math.sin(t*0.12)*1.5;
circ(0,-8,cr+4,"#7a0e1e");circ(0,-8,cr,fl||"#ff3355");circ(0,-8,cr*0.45,"#ffdfe5");
ctx.globalAlpha=1;}
if(split<1){ctx.globalAlpha=1-split; // 一阶段青色核心窗
px(-7,-14,14,9,fl||"#43e8ff");px(-4,-12,8,5,"#bfefff");
ctx.globalAlpha=1;}
// —— 穹顶4层装甲环带沿中线剖半内层滑得更远=阶梯裂口) ——
const BANDS=[[118,34,"#10151f"],[98,28,"#1b2434"],[74,21,"#33436a"],[50,14,"#55648a"]];
for(let bi=0;bi<4;bi++){const off=split*(16+bi*6),rx=BANDS[bi][0],ry=BANDS[bi][1];
for(const sd of[-1,1]){ctx.save();ctx.translate(sd*off,0);
const q0=sd<0?Math.PI:Math.PI*1.5,q1=sd<0?Math.PI*1.5:Math.PI*2;
ctx.fillStyle=BANDS[bi][2];ctx.beginPath();ctx.ellipse(0,6,rx,ry,0,q0,q1);ctx.lineTo(0,6);ctx.closePath();ctx.fill();
ctx.restore();}}
// —— 外环窗火+壳沿铆钉(随最外半壳滑开) ——
for(const sd of[-1,1]){ctx.save();ctx.translate(sd*split*16,0);
for(let k=0;k<4;k++)px(sd*(20+k*22)-2,-2,4,3,t%40<20?"#3d5a73":"#24304a"); // 窗火明暗
for(let i=0;i<7;i++){const a=Math.PI+(i+0.5)/7*Math.PI; // 壳沿铆钉
if((sd<0)!==(a<Math.PI*1.5))continue;
px(Math.cos(a)*110-1.5,6+Math.sin(a)*30-1.5,3,3,"#9fb4d8");}
ctx.restore();}
// —— 6座穹顶小炮塔topRain炮位与enemies.js同源x=-75+ti*30P2随第3环带滑开28 ——
{const off2=split*28;
for(let ti=0;ti<6;ti++){const sd2=ti<3?-1:1,tx=-75+ti*30+sd2*off2;
circ(tx,-4,3.5,"#33436a");circ(tx-1,-5,1.5,"#55648a");
px(tx-1,-2,2,5,b.turrT>0&&((b.turrIs&&b.turrIs.includes(ti))||b.turrI===ti)?"#e8fbff":"#1b2434");}}
// —— 左右大炮塔踏板重音炮位左紫右蓝P2随最外环带滑开16 ——
{const off0=split*16;
for(const sd of[-1,1]){const px2=sd*(112+off0);
circ(px2,2,5.5,"#33436a");circ(px2-1.5,0,2.5,"#55648a");
px(px2-1.5,4,3,8,"#1b2434");
px(px2-1,10,2,3,b.pedT>0?"#ffffff":(sd<0?"#b98cff":"#43e8ff"));}}
// —— 舰桥+雷达(随半壳滑开) ——
for(const sd of[-1,1]){ctx.save();ctx.translate(sd*split*30,0);
px(sd<0?-10:0,-32,10,10,"#1b2434");
px(sd<0?-6:0,-37,6,5,"#33436a");
ctx.restore();}
ctx.save();ctx.translate(-split*30,0); // 雷达随左半壳
circ(0,-34,4,"#33436a");
drawSeg(0,-34,Math.cos(t*0.05)*8,-34+Math.sin(t*0.05)*8,1,"#43e8ff",0.55); // 雷达扫面
px(-1,-38,2,2,t%30<15?"#ff3355":"#7a1622");
ctx.restore();
// —— 发光纹路(暗纹常存,蓄力时向键带点亮蔓延) ——
{const pr=(ks&&ks.hotT>0&&ks.hotT0)?1-ks.hotT/ks.hotT0:0;
for(let k=0;k<5;k++){const tx2=-60+k*30;
px(tx2,-8,1,12,"#134152");
if(pr>0){ctx.globalAlpha=pr*0.7;px(tx2,-8,1,12,"#43e8ff");ctx.globalAlpha=1;}}}
// —— 琴键炮口带21键像素钢琴白键+黑键+键缝;键位公式 -(n-1)*5+k*10 与enemies.js同步 ——
px(-(n-1)*5-5,6,(n-1)*10+10,19,"#0a0e16"); // 键槽
for(let i=0;i<n;i++){const kx=-(n-1)*5+i*10;
const hot=ks&&(ks.hot===i||(ks.hotList&&ks.hotList.indexOf(i)>=0));
px(kx-4,8,8,14,hot?(t%6<3?"#e8fbff":"#43e8ff"):"#d8e4f0");
px(kx-4,20,8,2,hot?"#43e8ff":"#8a9ab8");
if(i<n-1&&[0,1,3,4,5].indexOf(i%7)>=0)px(kx+3,8,4,7,"#101318");} // 黑键(钢琴纹样)
if(ks&&ks.hotT>0&&ks.hotT0){const pr=1-ks.hotT/ks.hotT0; // 蓄力前摇:即将发射的键下方圆形蓄力(批准的例外)
const list=ks.hotList||(ks.hot>=0?[ks.hot]:[]);
for(const kh of list){const kx=-(n-1)*5+kh*10;
ctx.globalAlpha=0.3+0.55*pr;ctx.strokeStyle="#43e8ff";ctx.lineWidth=1.5;
ctx.beginPath();ctx.arc(kx,28,2+3.5*pr,0,TAU);ctx.stroke();
ctx.globalAlpha=0.22*pr;ctx.fillStyle="#43e8ff";
ctx.beginPath();ctx.arc(kx,28,2+3.5*pr,0,TAU);ctx.fill();
ctx.globalAlpha=1;}}
}
/* ---- 夺权者X几何装甲板拼装宽体楔形母舰P1→ 抛侧舱同风格壮实战机P2 ----
参考图核心特征:交错装甲板+深板缝+蓝色发光条纹格栅翼+三联红炮口武器舱+橙/黄小标识;无生物眼(机械反应环) */
function drawUsurper(b,t,fl,ph2,mp){
const jt=mp||0;
// —— P2同风格壮实战机壳下常存mp渐显宽翼板+小武器舱+粗箭形机身) ——
if(ph2>=1){const di=mp!==null?mp:1;
ctx.save();ctx.globalAlpha=di;const ds=0.75+0.25*di;ctx.scale(ds,ds);
for(const sd of[-1,1]){ctx.save();ctx.scale(sd,1); // 后掠翼板(蓝条纹+橙标识+翼根炮口)
poly([10,-16,34,-24,40,-14,34,-6,12,-6],"#c9d6e2");
poly([10,-16,34,-24,30,-19,12,-11],"#e8f4ff"); // 翼面受光
drawSeg(14,-10,30,-19,2,"#43b8ff",0.85); // 蓝色翼纹
px(30,-22,4,3,"#ff9a3d");px(12,-5,3,3,"#ff3355");
px(18,4,12,12,"#c9d6e2");px(20,6,8,6,"#2a3444"); // 小武器舱
px(21,7,3,3,"#ff3355");px(25,7,3,3,"#ff3355"); // 双联红炮口
px(21,16,3,6,"#5a6c80");px(25,16,3,6,"#5a6c80"); // 舱下炮管
ctx.restore();}
poly([0,46,-15,12,-13,-24,-8,-34,8,-34,13,-24,15,12],"#e8f4ff"); // 粗箭形机身
poly([0,46,15,12,13,-24,8,-34,0,-34],"#9fb0c6");
px(-1,-22,2,52,t%50<25?"#43b8ff":"#2a5a78"); // 中脊蓝色光带(搏动)
px(-13,-24,26,2,"#5a6c80");px(-11,-4,22,2,"#5a6c80"); // 装甲板缝
circ(0,-14,7,"#0d2244"); // 中央反应环(机械环非眼睛)
ctx.strokeStyle=fl||"#43b8ff";ctx.lineWidth=2;ctx.beginPath();ctx.arc(0,-14,7,0,TAU);ctx.stroke();
for(let k=0;k<3;k++){const ra=t*0.03+k*TAU/3;ctx.beginPath();ctx.arc(0,-14,7,ra,ra+0.8);ctx.stroke();}
circ(0,-14,3+Math.sin(t*0.1)*0.5,"#bfefff");
if(b.laserT>0){ctx.globalAlpha=di*0.5;circ(0,-14,10,"#7ce9ff");ctx.globalAlpha=di;}
px(-2,2,4,16,"#0d2244");px(-1,4,2,12,t%40<20?"#7fd8ff":"#2a5a78"); // 中线长条蓝窗(参考图特征)
for(const sd of[-1,1]){px(sd*6-2,-36,4,3,"#5a6c80"); // 双引擎尾焰
px(sd*6-1.5,-40-(t%4),3,3+(t%4),t%6<3?"#7fd8ff":"#43b8ff");}
ctx.restore();ctx.globalAlpha=1;}
// —— P1母舰变形=翼板/武器舱抛离飞走+舰体淡出) ——
if(ph2===0||mp!==null){
// —— 后翼面(格栅翼+蓝条纹×3 ——
for(const sd of[-1,1]){const ja=(1-jt)*(1-jt*0.9);if(ja<=0.02)continue;
ctx.save();ctx.translate(sd*jt*110,jt*50);ctx.rotate(sd*jt*0.6);ctx.scale(sd,1);ctx.globalAlpha=ja;
poly([20,-38,66,-50,76,-40,72,-28,26,-22],"#c9d6e2");
poly([20,-38,66,-50,60,-44,24,-32],"#e8f4ff"); // 受光面
for(let k=0;k<3;k++)drawSeg(34+k*12,-34-k*4,44+k*12,-44-k*3,2,"#43b8ff",0.85); // 蓝色翼纹×3
for(let k=0;k<4;k++)px(30+k*11,-30-k*2,2,8,"#9fb0c6"); // 格栅
ctx.restore();ctx.globalAlpha=1;}
// —— 中层装甲翼板×3/侧(交错层叠) ——
const PL=[[14,-30],[30,-22],[44,-14]];
for(const sd of[-1,1])for(let L=0;L<3;L++){
const ja=(1-jt)*(1-jt*0.85);if(ja<=0.02)continue;
ctx.save();ctx.translate(sd*jt*(80+L*26),jt*(36+L*12));ctx.rotate(sd*jt*(0.45+L*0.2));ctx.scale(sd,1);ctx.globalAlpha=ja;
const x0=PL[L][0],y0=PL[L][1];
poly([x0,y0,x0+26,y0+4,x0+22,y0+16,x0+4,y0+14],"#d8e4f0");
poly([x0+4,y0+14,x0+22,y0+16,x0+26,y0+4,x0+20,y0+8],"#9fb0c6"); // 右下暗边
poly([x0,y0,x0+26,y0+4,x0+24,y0+6,x0+2,y0+2],"#e8f4ff"); // 上缘受光
if(L===1)px(x0+16,y0+6,4,3,"#ff9a3d"); // 橙色小标识
if(L===2)drawSeg(x0+6,y0+3,x0+20,y0+6,2,"#43b8ff",0.8); // 板面蓝纹
ctx.restore();ctx.globalAlpha=1;}
// —— 武器舱段(三联红炮口深槽+黄标识+舱下双炮管deckVolley逐个点亮 ——
for(const sd of[-1,1]){const ja=(1-jt)*(1-jt*0.85);if(ja<=0.02)continue;
ctx.save();ctx.translate(sd*jt*95,jt*44);ctx.rotate(sd*jt*0.55);ctx.scale(sd,1);ctx.globalAlpha=ja;
px(44,0,24,22,"#c9d6e2");px(44,0,24,3,"#e8f4ff");
px(46,4,20,9,"#2a3444"); // 炮座深槽
for(let g=0;g<3;g++)px(48+g*6,6,4,5,(b.deckT>0&&(26-b.deckT)>g*6)?"#ffffff":"#ff3355"); // 三联红炮口
px(45,15,4,3,"#ffcf5c"); // 黄标识
px(48,22,3,9,"#5a6c80");px(59,22,3,9,"#5a6c80"); // 舱下双炮管
px(48.5,29,2,3,"#43b8ff");px(59.5,29,2,3,"#43b8ff");
ctx.restore();ctx.globalAlpha=1;}
// —— 中央箭形主舰体(最前层:宽顶→尖下巴,中脊光带+反应环+长条蓝窗) ——
const ha=1-jt;if(ha>0.02){ctx.save();ctx.globalAlpha=ha;
poly([0,54,-17,10,-15,-26,-9,-40,9,-40,15,-26,17,10],"#e8f4ff");
poly([0,54,17,10,15,-26,9,-40,0,-40],"#9fb0c6"); // 右半暗面
px(-1,-28,2,66,t%50<25?"#43b8ff":"#2a5a78"); // 中脊蓝色光带(搏动)
px(-15,-26,30,2,"#5a6c80");px(-13,-2,26,2,"#5a6c80"); // 板缝
for(let k=0;k<4;k++)px(-12+k*7,-25,2,2,"#8a9ab8"); // 铆钉
circ(0,-18,8,"#0d2244"); // 中央反应环(机械环非眼睛)
ctx.strokeStyle=fl||"#43b8ff";ctx.lineWidth=2;ctx.beginPath();ctx.arc(0,-18,8,0,TAU);ctx.stroke();
for(let k=0;k<3;k++){const ra=t*0.02+k*TAU/3;ctx.beginPath();ctx.arc(0,-18,8,ra,ra+0.7);ctx.stroke();}
circ(0,-18,3.5+Math.sin(t*0.08)*0.5,"#bfefff");
if(b.laserT>0){ctx.globalAlpha=ha*0.5;circ(0,-18,11,"#7ce9ff");ctx.globalAlpha=ha;}
px(-2.5,4,5,22,"#0d2244");px(-1,6,2,18,t%40<20?"#7fd8ff":"#2a5a78"); // 中线长条蓝窗(参考图特征)
for(let e=0;e<3;e++){const ex2=-10+e*10; // 顶部引擎列
px(ex2-2,-42,4,3,"#5a6c80");
px(ex2-1.5,-46-(t+e*2)%4,3,3+(t+e*2)%4,e%2?"#7fd8ff":"#43b8ff");}
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; // 红色六边形能量窗(随扇区角度旋转,呼吸明灭)
ctx.globalAlpha=(0.3+0.3*Math.sin(t*0.08+k*1.1))*(0.5+0.5*eyeG);
ctx.save();ctx.translate(Math.sin(a)*12.5,-Math.cos(a)*12.5);ctx.rotate(a);
px(-2,-1,4,2,"#ff3355");px(-1,-2,2,4,"#ff3355");
ctx.restore();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);
ctx.save();ctx.translate(Math.cos(ag)*R,Math.sin(ag)*R);ctx.rotate(ag);
px(-4,-1,8,2,"#ff3355");ctx.restore();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+(sd<0?0.35+k*0.2:0.65-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");}
}
function drawAssaultGhost(swp,ph){ // 暗黑突击冲撞红色残影与机体同轮廓同线条纯红系供dashTrail叠画
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],"#ff3344"); // 主翼
poly([tx-sd*2,ty-6,tx+sd*3,ty-14,tx+sd*7,ty-3],"#ff3344");} // 翼尖
poly([0,38,-5,6,-3,-26,3,-26,5,6],"#ff3344"); // 机身
px(-2,8,4,8,"#ff8899"); // 中脊亮线
const guns=[[16,4],[-16,4],[30,-4],[-30,-4],[42,-10],[-42,-10]];
const gn=ph>=2?6:ph>=1?4:2;
for(let i=0;i<gn;i++)px(guns[i][0]-1.5,guns[i][1],3,8,"#ff8899"); // 炮管
const eng=[[7,-26],[-7,-26],[0,-30],[14,-22]];
const en=ph>=2?4:ph>=1?3:2;
for(let i=0;i<en;i++)px(eng[i][0]-1.5,eng[i][1]-10,3,7,"#ff8899"); // 推进器
if(ph>=1){poly([-52,-14,-24,-3,-22,-1,-50,-11],"#ff3344");poly([52,-14,24,-3,22,-1,50,-11],"#ff3344");} // 翼展线条
if(ph>=2)px(-4,-16,8,12,"#ff8899"); // 中核亮块
}
/* ---- Boss光束与浮游炮渲染 ---- */