# aardio 范例: 自绘小游戏 - 星灵结界：守护最后的光



```aardio
import win.ui;
/*DSG{{*/
var winform = win.form(text="自绘小游戏 - 星灵结界：守护最后的光";right=1060;bottom=720;bgcolor=0x100B22;border="dialog frame";max=false)
winform.add(
wardCanvas={cls="canvas";left=0;top=0;right=1060;bottom=720;anchors={left=1;top=1;right=1;bottom=1};z=1}
)
/*}}*/

/*
星灵结界：守护最后的光
- 按住左键，在星灵周围画出一道发光结界；松开后暗影兽会从传送门涌入。
- 暗影撞上完整结界会炸开真实缺口并短暂弹退；缺口不再参与碰撞，暗影可由此穿过并伤害星灵。
- 单击暗影兽可让它明显缩小、减速并降低破坏力；连续点击可将其净化（消灭目标，进攻是最好的防守）。
- 每次结界挡住冲击都会获得 1 份修复星尘；单击缺口消耗 1 份星尘，从两端重新织合结界。
- 结界使用有宽度的折线碰撞体。敌人高速移动时按小步连续检测，避免穿透。
- 法阵充满后由玩家单击星灵或按空格亲手释放终焉净化。
- H 显示玩法提示，M 切换事件音效，R 或右键重置；5 个关卡包含普通、疾行、重甲与首领暗影兽。
- 使用 DPI 原尺寸静态场景缓存；真实缺口最多 4 个，短寿命特效限制数量，回避 GDI+ 大量持续重绘。
*/

var CANVAS_W=1060;
var CANVAS_H=720;
var ARENA_X=24;
var ARENA_Y=110;
var ARENA_W=748;
var ARENA_H=586;
var PANEL_X=795;
var PANEL_Y=110;
var PANEL_W=240;
var PANEL_H=586;
var WARD_WIDTH=6;

//难度总开关：1 舒缓、2 标准、3 艰难。
var GAME_DIFFICULTY=2;
var DIFFICULTIES=[
    {name="舒缓";speed=0.70;spawn=1.0;breach=0.80;damage=0.60;power=-1;repairCooldown=0.10;repairMax=6;guardianBonus=6},
    {name="标准";speed=0.80;spawn=0.82;breach=1.08;damage=0.82;power=0;repairCooldown=0.20;repairMax=5;guardianBonus=4},
    {name="艰难";speed=0.98;spawn=0.68;breach=1.30;damage=1.12;power=1;repairCooldown=0.32;repairMax=3;guardianBonus=2}
];
GAME_DIFFICULTY=math.clamp(GAME_DIFFICULTY,1,#DIFFICULTIES);
var difficulty=DIFFICULTIES[GAME_DIFFICULTY];

var STATE_READY=1;
var STATE_DEFEND=2;
var STATE_SOLVED=3;
var STATE_FAILED=4;

var levels=[
    {
        name="初光苏醒";subtitle="环住星灵，挡住来自两侧的暗影";accent=0xFF5EEBFF;
        duration=7.5;ink=520;ward=52;guardianHp=3;spawnInterval=0.92;
        pattern=["wisp","wisp","wisp","fast"];
        targets=[{x=398;y=401;r=27}];
        portals=[{x=44;y=300;angle=0},{x=752;y=510;angle=180}];
        anchors=[{x=398;y=314},{x=398;y=488}]
    },
    {
        name="三门夜袭";subtitle="疾行暗影会从第三座传送门突袭";accent=0xFFA886FF;
        duration=8.5;ink=560;ward=64;guardianHp=3;spawnInterval=0.78;
        pattern=["wisp","fast","wisp","wisp","fast"];
        targets=[{x=420;y=397;r=28}];
        portals=[{x=52;y=236;angle=0},{x=742;y=246;angle=180},{x=610;y=676;angle=270}];
        anchors=[{x=350;y=339},{x=489;y=339},{x=420;y=489}]
    },
    {
        name="双星共鸣";subtitle="一道结界必须同时守护两颗星核";accent=0xFFFF72C8;
        duration=9.5;ink=700;ward=80;guardianHp=4;spawnInterval=0.70;
        pattern=["wisp","fast","wisp","brute","wisp"];
        targets=[{x=360;y=384;r=23},{x=456;y=414;r=23}];
        portals=[{x=42;y=220;angle=0},{x=750;y=220;angle=180},{x=42;y=590;angle=0},{x=750;y=590;angle=180}];
        anchors=[{x=319;y=321},{x=487;y=334},{x=505;y=480},{x=333;y=477}]
    },
    {
        name="重影压境";subtitle="重甲暗影会连续撞击结界薄弱处";accent=0xFFFFB85E;
        duration=10.5;ink=600;ward=94;guardianHp=4;spawnInterval=0.64;
        pattern=["fast","wisp","brute","wisp","fast","brute"];
        targets=[{x=402;y=403;r=30}];
        portals=[{x=50;y=200;angle=0},{x=746;y=200;angle=180},{x=50;y=610;angle=0},{x=746;y=610;angle=180}];
        anchors=[{x=322;y=349},{x=482;y=349},{x=482;y=457},{x=322;y=457}]
    },
    {
        name="最后的光";subtitle="暗影首领降临，坚持到终焉法阵完成";accent=0xFFFFD66F;
        duration=12;ink=650;ward=122;guardianHp=5;spawnInterval=0.68;bossAt=3.0;
        pattern=["wisp","fast","brute","fast","wisp","brute"];
        targets=[{x=402;y=402;r=33}];
        portals=[{x=43;y=190;angle=0},{x=753;y=190;angle=180},{x=43;y=620;angle=0},{x=753;y=620;angle=180},{x=398;y=126;angle=90}];
        anchors=[{x=402;y=300},{x=505;y=402},{x=402;y=504},{x=299;y=402}]
    }
];

/*
r：敌人体型/碰撞半径（调大更容易用鼠标点中，调小更难点中）。
speed：基础移动速度（像素/秒）。
power：被点击几次才会被消灭（血量）。
damage：撞击星灵一次扣几点血。
breach：撞击结界时炸开的缺口基础宽度（像素），如 Boss 一下炸开 104px。
turn：转向星灵的机动性/拐弯灵敏度（值越小拐弯越笨拙，越容易被结界弹开甩远）
*/
var enemyDefs={
    wisp={r=13;speed=110;power=2;damage=1;breach=44;turn=3.3;color=0xFF9B6CFF;name="暗影兽"};
    fast={r=10;speed=205;power=1;damage=1;breach=38;turn=5.2;color=0xFFFF5EA8;name="疾行影"};
    brute={r=20;speed=110;power=3;damage=2;breach=74;turn=2.5;color=0xFFFF875C;name="重甲影"};
    boss={r=30;speed=94;power=4;damage=3;breach=104;turn=2.0;color=0xFFFFD05A;name="暗影领主"}
};

var game={
    level=1;state=STATE_READY;pulse=0;
    targets=[];portals=[];anchors=[];
    wardPoints=[];rawPoints=[];inkUsed=0;drawing=false;closed=false;
    wardEnergy=0;wardMax=0;wardActive=true;wardBreak=0;
    guardianHp=3;guardianMax=3;targetFlash=0;
    defendTime=0;spawnClock=0;spawned=0;portalCursor=0;bossSpawned=false;
    enemies=[];particles=[];shocks=[];hits=[];breaches=[];
    repairCharges=1;repairMax=3;repairCooldown=0;hoverBreach=0;
    repairs=0;breachesMade=0;enemyClicks=0;
    hintTime=0;statusText="";statusTime=0;
    resultTime=0;purifyRadius=0;purifyClock=0;purifyReady=false;
    impacts=0;banished=0;soundCooldown=0;muted=false;
};

var CACHE_SCALE=winform.dpiScale(1);
var sceneCache;
var sceneDirty=true;

var stars=[];
for(i=1;88){
    table.push(stars,{
        x=math.random()*CANVAS_W;y=math.random()*CANVAS_H;
        size=0.5+math.random()*1.5;alpha=26+math.random(0,72);
        twinkle=math.random()*math.pi*2
    });
}
var arenaDust=[];
for(i=1;52){
    table.push(arenaDust,{
        x=ARENA_X+35+math.random()*(ARENA_W-70);
        y=ARENA_Y+30+math.random()*(ARENA_H-60);
        size=0.35+math.random()*1.1;
        alpha=22+math.random(0,38)
    });
}

import sys.midiOut;
var midiOut=sys.midiOut();

// -------------------- 通用辅助 --------------------
var alphaColor=function(color,alpha){
    return (math.clamp(math.floor(alpha),0,255)<<24)|(color&0xFFFFFF);
}

var shadeColor=function(color,amount){
    var r=math.clamp(((color>>16)&0xFF)+amount,0,255);
    var g=math.clamp(((color>>8)&0xFF)+amount,0,255);
    var b=math.clamp((color&0xFF)+amount,0,255);
    return 0xFF000000|(math.floor(r)<<16)|(math.floor(g)<<8)|math.floor(b);
}

var drawText=function(graphics,text,size,x,y,w,h,color,bold,align){
    var font=gdip.font("Microsoft YaHei UI",size,bold ? 1 : 0,2/*_UnitPixel*/);
    var brush=gdip.solidBrush(color);
    var format=gdip.stringFormat();
    format.align=align : 0;
    format.lineAlign=1;
    graphics.drawString(text,font,::RECTF(x,y,w,h),format,brush);
    format.delete();brush.delete();font.delete();
}

var drawBar=function(graphics,x,y,w,h,ratio,color){
    ratio=math.clamp(ratio,0,1);
    var back=gdip.solidBrush(0xA0191830);
    var fill=gdip.lineBrush(::POINTF(x,y),::POINTF(x+w,y),shadeColor(color,35),shadeColor(color,-30));
    graphics.fillRoundRect(back,x,y,w,h,h/2);
    var fillWidth=w*ratio;
    if(fillWidth>=h) graphics.fillRoundRect(fill,x,y,fillWidth,h,h/2);
    elseif(fillWidth>0) graphics.fillCircle(fill,x+h/2,y+h/2,math.max(1,fillWidth/2));
    fill.delete();back.delete();
    var shine=gdip.pen(0x35FFFFFF,1);
    graphics.drawRoundRect(shine,x+0.5,y+0.5,w-1,h-1,h/2);
    shine.delete();
}

//只播放离散事件型短音；不创建背景旋律，也不按帧或按充能进度发声。
var playNote=function(instrument,note,duration){
    if(midiOut && !game.muted) midiOut.play("changeInstrument("++instrument++"), 1_,5_",note,duration);
}

var levelData=function(){ return levels[game.level]; }

var defaultStatus=function(){
    select(game.state){
        case STATE_READY { return "按住左键绕着星灵绘制结界，松开后迎战"; }
        case STATE_DEFEND {
            if(game.purifyReady) return "终焉法阵已就绪：单击星灵或按空格释放净化";
            return "点暗影使其缩小；点结界缺口消耗 1 份星尘修复";
        }
        case STATE_SOLVED { return "终焉净化由你释放：暗影正在消散"; }
        case STATE_FAILED { return "最后的光熄灭了，单击或按空格重新守护"; }
    }
}

var setStatus=function(text,duration){
    game.statusText=text;
    game.statusTime=duration;
}

var copyPoints=function(source){
    var result=[];
    for(i,p in source) table.push(result,{x=p.x;y=p.y;r=p.r;angle=p.angle});
    return result;
}

var dist2=function(x1,y1,x2,y2){
    var dx=x2-x1;var dy=y2-y1;
    return dx*dx+dy*dy;
}

// -------------------- 结界几何与连续碰撞 --------------------
var pointSegment=function(px,py,ax,ay,bx,by){
    var dx=bx-ax;var dy=by-ay;
    var len2=dx*dx+dy*dy;
    var t=len2>0 ? ((px-ax)*dx+(py-ay)*dy)/len2 : 0;
    t=math.clamp(t,0,1);
    var qx=ax+dx*t;var qy=ay+dy*t;
    var ox=px-qx;var oy=py-qy;
    return math.sqrt(ox*ox+oy*oy),qx,qy,t;
}

var polylineLength=function(points){
    var total=0;
    for(i=1;#points-1) total+=math.dist(points[i].x,points[i].y,points[i+1].x,points[i+1].y);
    return total;
}

var nearestPolyline=function(points,x,y){
    if(#points<2) return null;
    var best=100000;var result;
    for(i=1;#points-1){
        var a=points[i];var b=points[i+1];
        var d,qx,qy,t=pointSegment(x,y,a.x,a.y,b.x,b.y);
        if(d<best){
            best=d;
            result={distance=d;qx=qx;qy=qy;segment=i;t=t};
        }
    }
    return result;
}

var effectiveBreachRange=function(breach){
    var first=breach.first;var last=breach.last;
    if(breach.repairing){
        var span=last-first+1;
        var restored=math.floor(span*math.clamp(breach.repair,0,0.999)/2);
        first+=restored;last-=restored;
    }
    return first,last;
}

var isSegmentBroken=function(segment){
    for(i,breach in game.breaches){
        var first,last=effectiveBreachRange(breach);
        if(segment>=first && segment<=last) return true;
    }
    return false;
}

//永久结界的真实缺口完全跳过碰撞；暗影兽可以从缺口穿入。
var nearestWard=function(x,y){
    if(#game.wardPoints<2) return null;
    var best=100000;var result;
    for(i=1;#game.wardPoints-1){
        if(!isSegmentBroken(i)){
            var a=game.wardPoints[i];var b=game.wardPoints[i+1];
            var d,qx,qy,t=pointSegment(x,y,a.x,a.y,b.x,b.y);
            if(d<best){best=d;result={distance=d;qx=qx;qy=qy;segment=i;t=t;kind="ward"};}
        }
    }
    return result;
}

var nearestBarrier=function(x,y){
    return nearestWard(x,y);
}

var wardIntegrity=function(){
    var total=polylineLength(game.wardPoints);
    if(total<=0) return 1;
    var broken=0;
    for(i=1;#game.wardPoints-1){
        if(isSegmentBroken(i)){
            var a=game.wardPoints[i];var b=game.wardPoints[i+1];
            broken+=math.dist(a.x,a.y,b.x,b.y);
        }
    }
    return math.clamp(1-broken/total,0,1);
}

//按缺口把结界拆为少量连续折线，一次 drawLines 绘制一段，避免逐段创建 GDI+ 对象。
var getWardRuns=function(){
    var runs=[];var run;
    for(i=1;#game.wardPoints-1){
        if(isSegmentBroken(i)){
            if(run && #run>=2) table.push(runs,run);
            run=null;
        }
        else{
            if(!run) run=[game.wardPoints[i]];
            table.push(run,game.wardPoints[i+1]);
        }
    }
    if(run && #run>=2) table.push(runs,run);
    return runs;
}

var rdp;
rdp=function(points,tolerance){
    if(#points<=2) return copyPoints(points);
    var a=points[1];var b=points[#points];
    var best=0;var index=0;
    for(i=2;#points-1){
        var d=pointSegment(points[i].x,points[i].y,a.x,a.y,b.x,b.y);
        if(d>best){best=d;index=i;}
    }
    if(best<=tolerance) return [{x=a.x;y=a.y},{x=b.x;y=b.y}];
    var left=rdp(table.slice(points,1,index),tolerance);
    var right=rdp(table.slice(points,index,#points),tolerance);
    table.pop(left);
    table.append(left,right);
    return left;
}

var smoothWard=function(points,closed){
    var smoothed=[];
    if(closed){
        for(i=1;#points){
            var prev=points[i==1 ? #points : i-1];
            var curr=points[i];
            var next=points[i==#points ? 1 : i+1];
            table.push(smoothed,{x=(prev.x+curr.x*2+next.x)/4;y=(prev.y+curr.y*2+next.y)/4});
        }
        var reduced=[];
        for(i=1;#smoothed;2) table.push(reduced,smoothed[i]);
        if(#reduced<6) reduced=smoothed;
        table.push(reduced,{x=reduced[1].x;y=reduced[1].y});
        return reduced;
    }
    for(i=1;#points){
        if(i==1 || i==#points) table.push(smoothed,{x=points[i].x;y=points[i].y});
        else table.push(smoothed,{
            x=(points[i-1].x+points[i].x*2+points[i+1].x)/4;
            y=(points[i-1].y+points[i].y*2+points[i+1].y)/4
        });
    }
    return rdp(smoothed,2.3);
}

var pointOnWard=function(distance){
    if(#game.wardPoints<2) return null;
    var total=polylineLength(game.wardPoints);
    if(total<=0) return game.wardPoints[1];
    distance=distance%total;
    for(i=1;#game.wardPoints-1){
        var a=game.wardPoints[i];var b=game.wardPoints[i+1];
        var len=math.dist(a.x,a.y,b.x,b.y);
        if(distance<=len){
            var t=len>0 ? distance/len : 0;
            return {x=a.x+(b.x-a.x)*t;y=a.y+(b.y-a.y)*t;segment=i};
        }
        distance-=len;
    }
    return game.wardPoints[#game.wardPoints];
}

var countChargedAnchors=function(){
    var count=0;
    for(i,anchor in game.anchors){
        anchor.charged=false;
        for(j=1;#game.wardPoints-1){
            var a=game.wardPoints[j];var b=game.wardPoints[j+1];
            var d=pointSegment(anchor.x,anchor.y,a.x,a.y,b.x,b.y);
            if(d<=19){anchor.charged=true;count++;break;}
        }
    }
    return count;
}

// -------------------- 状态与输入 --------------------
var initLevel;
var startDefense;
var finishWard;
var beginPurify;
var failLevel;
var repairBreach;
var findBreachAt;
var hitEnemyAt;

initLevel=function(index){
    game.level=math.clamp(index,1,#levels);
    var data=levelData();
    game.state=STATE_READY;
    game.targets=copyPoints(data.targets);
    game.portals=copyPoints(data.portals);
    for(i,p in game.portals){
        p.x=math.clamp(p.x,ARENA_X+34,ARENA_X+ARENA_W-34);
        p.y=math.clamp(p.y,ARENA_Y+34,ARENA_Y+ARENA_H-34);
    }
    game.anchors=copyPoints(data.anchors);
    game.wardPoints=[];game.rawPoints=[];game.inkUsed=0;game.drawing=false;game.closed=false;
    game.wardMax=data.ward;game.wardEnergy=data.ward;game.wardActive=true;game.wardBreak=0;
    game.guardianMax=math.max(1,data.guardianHp+difficulty.guardianBonus);
    game.guardianHp=game.guardianMax;game.targetFlash=0;
    game.defendTime=0;game.spawnClock=0.32;game.spawned=0;game.portalCursor=0;game.bossSpawned=false;
    game.enemies=[];game.particles=[];game.shocks=[];game.hits=[];game.breaches=[];
    game.repairMax=difficulty.repairMax;game.repairCharges=1;game.repairCooldown=0;game.hoverBreach=0;
    game.repairs=0;game.breachesMade=0;game.enemyClicks=0;
    game.hintTime=0;game.resultTime=0;game.purifyRadius=0;game.purifyClock=0;
    game.purifyReady=false;
    game.impacts=0;game.banished=0;game.soundCooldown=0;
    game.statusText=defaultStatus();game.statusTime=0;
    sceneDirty=true;
}

var isForbiddenPoint=function(x,y){
    for(i,target in game.targets){
        if(math.dist(x,y,target.x,target.y)<target.r+25) return true;
    }
    return false;
}

var appendWardPoint=function(x,y){
    if(!game.drawing) return;
    x=math.clamp(x,ARENA_X+8,ARENA_X+ARENA_W-8);
    y=math.clamp(y,ARENA_Y+8,ARENA_Y+ARENA_H-8);
    if(isForbiddenPoint(x,y)){
        setStatus("结界不能穿过星灵，请从外侧重新勾勒",1500);
        finishWard();
        return;
    }
    var last=game.rawPoints[#game.rawPoints];
    var distance=math.dist(last.x,last.y,x,y);
    if(distance<3.5) return;
    var left=levelData().ink-game.inkUsed;
    if(left<=0){finishWard();return;}
    if(distance>left){
        var t=left/distance;
        x=last.x+(x-last.x)*t;
        y=last.y+(y-last.y)*t;
        distance=left;
    }
    table.push(game.rawPoints,{x=x;y=y});
    game.inkUsed+=distance;
    game.wardPoints=game.rawPoints;
    if(game.inkUsed>=levelData().ink-0.1){
        setStatus("魔力墨水已用尽，结界自动成形",1100);
        finishWard();
    }
}

startDefense=function(){
    if(#game.wardPoints<2) return;
    var charged=countChargedAnchors();
    game.wardMax=levelData().ward+charged*6;
    game.wardEnergy=game.wardMax;
    game.repairCharges=math.min(game.repairMax,1+(charged>0 ? 1 : 0));
    game.state=STATE_DEFEND;
    game.defendTime=0;
    game.spawnClock=0.24;
    game.statusText=defaultStatus();game.statusTime=0;
    if(charged>0) setStatus("符文点亮：额外获得 1 份修复星尘",1500);
    playNote(89,"C6 E6",52);
}

finishWard=function(){
    if(!game.drawing) return;
    game.drawing=false;
    win.releaseCapture();
    if(#game.rawPoints<2 || game.inkUsed<65){
        game.rawPoints=[];game.wardPoints=[];game.inkUsed=0;
        setStatus("结界太短了：请按住左键画出完整防线",1700);
        return;
    }
    var first=game.rawPoints[1];var last=game.rawPoints[#game.rawPoints];
    game.closed=#game.rawPoints>=8 && math.dist(first.x,first.y,last.x,last.y)<=44;
    game.wardPoints=smoothWard(game.rawPoints,game.closed);
    startDefense();
}

var logicalMousePos=function(lParam){
    var x,y=win.getMessagePos(lParam);
    var dpiX,dpiY=winform.dpiScale(1,1);
    return x/dpiX,y/dpiY;
}

winform.wardCanvas.onMouseDown=function(wParam,lParam){
    if(game.state==STATE_SOLVED){
        initLevel(game.level==#levels ? 1 : game.level+1);
        return;
    }
    if(game.state==STATE_FAILED){initLevel(game.level);return;}
    var x,y=logicalMousePos(lParam);

    if(game.state==STATE_DEFEND){
        if(x<ARENA_X || x>ARENA_X+ARENA_W || y<ARENA_Y || y>ARENA_Y+ARENA_H) return;
        if(game.purifyReady){
            for(i,target in game.targets){
                if(math.dist(x,y,target.x,target.y)<=target.r+34){beginPurify();return;}
            }
        }
        //防守阶段只有两种明确动作：点实体暗影削弱，或点大片缺口修复；空白乱点没有收益。
        if(repairBreach(x,y)) return;
        hitEnemyAt(x,y);
        return;
    }

    if(game.state!=STATE_READY || game.drawing) return;
    if(x<ARENA_X || x>ARENA_X+ARENA_W || y<ARENA_Y || y>ARENA_Y+ARENA_H){
        setStatus("请在左侧星域中绘制结界",1000);
        return;
    }
    if(isForbiddenPoint(x,y)){
        setStatus("请从星灵外侧开始绘制",1200);
        return;
    }
    game.rawPoints=[{x=x;y=y}];game.wardPoints=game.rawPoints;
    game.inkUsed=0;game.drawing=true;game.hintTime=0;
    win.setCapture(winform.wardCanvas.hwnd);
    playNote(98,"C6",35);
}

winform.wardCanvas.onMouseMove=function(wParam,lParam){
    var x,y=logicalMousePos(lParam);
    if(game.drawing && (wParam&1/*_MK_LBUTTON*/)) appendWardPoint(x,y);
    elseif(game.state==STATE_DEFEND){
        var breachIndex=findBreachAt(x,y,34);
        game.hoverBreach=breachIndex : 0;
    }
}

winform.wardCanvas.onMouseUp=function(wParam,lParam){
    if(game.drawing){
        var x,y=logicalMousePos(lParam);
        appendWardPoint(x,y);
        finishWard();
    }
}

winform.wardCanvas.onRightMouseUp=function(){initLevel(game.level);}

winform.wardCanvas.dlgCode=4/*_DLGC_WANTALLKEYS*/;
winform.wardCanvas.onKeyDown=function(keyCode,lParam,repeat){
    if(repeat) return;
    if(keyCode==0x52/*_VK_R*/){initLevel(game.level);return;}
    if(keyCode==0x4D/*_VK_M*/){
        game.muted=!game.muted;
        setStatus(game.muted ? "音效已关闭" : "音效已开启",1100);
        if(!game.muted) playNote(10,"E6 G6",35);
        return;
    }
    if(keyCode==0x48/*_VK_H*/){
        if(game.state==STATE_READY){
            game.hintTime=2600;
            setStatus("提示：沿虚线环住全部星灵，经过符文可强化结界",2600);
        }
        elseif(game.state==STATE_DEFEND){
            setStatus("点暗影让它缩小变弱；缺口亮起时点一下消耗星尘修复",3200);
        }
        playNote(10,"G6",42);
        return;
    }
    if((keyCode==0x20/*_VK_SPACE*/ || keyCode==0x0D/*_VK_RETURN*/) && game.state==STATE_DEFEND && game.purifyReady){
        beginPurify();return;
    }
    if((keyCode==0x20 || keyCode==0x0D) && game.state==STATE_SOLVED){
        initLevel(game.level==#levels ? 1 : game.level+1);
    }
    elseif((keyCode==0x20 || keyCode==0x0D) && game.state==STATE_FAILED){
        initLevel(game.level);
    }
}

initLevel(1);

// -------------------- 敌人、粒子与有限状态物理 --------------------
var burst=function(x,y,count,color,speed,kind){
    for(n=1;count){
        if(#game.particles>=120) break;
        var angle=math.random()*math.pi*2;
        var velocity=speed*(0.35+math.random()*0.65);
        var life=0.30+math.random()*0.48;
        table.push(game.particles,{
            x=x;y=y;vx=math.cos(angle)*velocity;vy=math.sin(angle)*velocity;
            life=life;maxLife=life;color=color;size=1+math.random()*2.4;
            kind=kind;spin=angle
        });
    }
}

var addShock=function(x,y,color,size){
    table.push(game.shocks,{x=x;y=y;color=color;life=0.42;maxLife=0.42;size=size});
}

var breachDistance=function(breach,x,y){
    var first,last=effectiveBreachRange(breach);
    var best=100000;
    for(segment=first;last){
        var a=game.wardPoints[segment];var b=game.wardPoints[segment+1];
        if(a && b){
            var distance=pointSegment(x,y,a.x,a.y,b.x,b.y);
            if(distance<best) best=distance;
        }
    }
    return best;
}

findBreachAt=function(x,y,threshold){
    var bestIndex=0;var best=threshold : 36;
    for(i,breach in game.breaches){
        var distance=breachDistance(breach,x,y);
        if(distance<=best){best=distance;bestIndex=i;}
    }
    return bestIndex,best;
}

repairBreach=function(x,y){
    var index=findBreachAt(x,y,38);
    if(!index) return false;
    var breach=game.breaches[index];
    if(breach.repairing) return true;
    if(game.repairCooldown>0){
        setStatus("修复术正在冷却",500);
        return true;
    }
    if(game.repairCharges<=0){
        setStatus("缺少修复星尘：先让完整结界挡住一次冲击",900);
        playNote(115,"D4",30);
        return true;
    }
    game.repairCharges--;game.repairs++;game.repairCooldown=difficulty.repairCooldown;
    breach.repairing=true;breach.repair=0;breach.flash=1;
    game.hoverBreach=0;
    setStatus("结界正在从两端重新织合",620);
    burst(breach.x,breach.y,10,0xFF8DFFF0,125,"spark");
    addShock(breach.x,breach.y,0xFF8DFFF0,24);
    playNote(11,"E5 A5 E6",45);
    return true;
}

var segmentLength=function(segment){
    var a=game.wardPoints[segment];var b=game.wardPoints[segment+1];
    return a && b ? math.dist(a.x,a.y,b.x,b.y) : 0;
}

//按实际折线长度向碰撞点两侧扩张，普通暗影约 40px，首领可一次撕开 100px 以上。
var addBreach=function(hit,width){
    var segmentCount=#game.wardPoints-1;
    var first=math.clamp(hit.segment,1,segmentCount);var last=first;
    var removed=segmentLength(first);var expandLeft=true;
    while(removed<width && (first>1 || last<segmentCount)){
        if(first>1 && last<segmentCount){
            if(expandLeft){first--;removed+=segmentLength(first);}
            else{last++;removed+=segmentLength(last);}
            expandLeft=!expandLeft;
        }
        elseif(first>1){first--;removed+=segmentLength(first);}
        else{last++;removed+=segmentLength(last);}
    }

    //相邻缺口合并；最多保留 4 个，超限时并入索引最近的缺口。
    for(i=#game.breaches;1;-1){
        var old=game.breaches[i];
        if(first<=old.last+1 && last>=old.first-1){
            first=math.min(first,old.first);last=math.max(last,old.last);
            table.remove(game.breaches,i);
        }
    }
    if(#game.breaches>=4){
        var nearest=1;var nearestDistance=1e30;
        for(i,old in game.breaches){
            var distance=math.abs((old.first+old.last)/2-hit.segment);
            if(distance<nearestDistance){nearestDistance=distance;nearest=i;}
        }
        var old=game.breaches[nearest];
        first=math.min(first,old.first);last=math.max(last,old.last);
        table.remove(game.breaches,nearest);
    }
    var breach={first=first;last=last;x=hit.qx;y=hit.qy;width=removed;repairing=false;repair=0;flash=1};
    table.push(game.breaches,breach);
    game.breachesMade++;
    return breach;
}

var nearestTarget=function(x,y){
    var best=1e30;var result;
    for(i,target in game.targets){
        var distance=dist2(x,y,target.x,target.y);
        if(distance<best){best=distance;result=target;}
    }
    return result;
}

var updateEnemyStrength=function(enemy){
    var ratio=math.clamp(enemy.power/enemy.maxPower,0,1);
    enemy.targetR=enemy.baseR*(0.46+0.54*ratio);
    enemy.damage=math.max(1,math.floor(enemy.baseDamage*difficulty.damage*(0.55+0.45*ratio)+0.35));
    enemy.currentBreach=enemy.baseBreach*difficulty.breach*(0.48+0.52*ratio);
    enemy.speed=enemy.baseSpeed*(0.74+0.26*ratio);
}

var spawnEnemy=function(forcedType){
    var data=levelData();
    var typ=forcedType;
    if(!typ){
        game.spawned++;
        typ=data.pattern[(game.spawned-1)%#data.pattern+1];
    }
    var def=enemyDefs[typ];
    game.portalCursor=game.portalCursor%#game.portals+1;
    var portal=game.portals[game.portalCursor];
    var target=nearestTarget(portal.x,portal.y);
    var dx=target.x-portal.x;var dy=target.y-portal.y;
    var len=math.sqrt(dx*dx+dy*dy);dx/=len;dy/=len;
    var power=math.max(1,def.power+difficulty.power);
    var speed=def.speed*difficulty.speed;
    var enemy={
        typ=typ;x=portal.x+dx*22;y=portal.y+dy*22;
        vx=dx*speed;vy=dy*speed;r=def.r;targetR=def.r;baseR=def.r;
        power=power;maxPower=power;baseDamage=def.damage;baseBreach=def.breach;
        baseSpeed=speed;speed=speed;damage=def.damage;currentBreach=def.breach;
        turn=def.turn;color=def.color;hits=0;
        age=0;phase=math.random()*math.pi*2;bounceCooldown=0;flash=0;
        portal=game.portalCursor;alpha=1;dashClock=0.7+math.random()*1.4
    };
    updateEnemyStrength(enemy);
    table.push(game.enemies,enemy);
    burst(enemy.x,enemy.y,5,enemy.color,55,"mote");
    if(typ=="boss"){
        setStatus("警告：暗影领主正在撕裂最后一道星门",1900);
        playNote(47,"C2",110);
    }
}

var removeEnemy=function(index,enemy,banished){
    if(banished){
        game.banished++;
        burst(enemy.x,enemy.y,enemy.typ=="boss" ? 22 : 10,enemy.color,enemy.typ=="boss" ? 190 : 135,"spark");
        addShock(enemy.x,enemy.y,enemy.color,enemy.r*2.4);
        if(game.soundCooldown<=0){
            playNote(enemy.typ=="boss" ? 104 : 11,enemy.typ=="boss" ? "C4 G4 C5" : "G6",34);
            game.soundCooldown=90;
        }
    }
    table.remove(game.enemies,index);
}

hitEnemyAt=function(x,y){
    for(i=#game.enemies;1;-1){
        var enemy=game.enemies[i];
        if(math.dist(x,y,enemy.x,enemy.y)<=math.max(18,enemy.r+8)){
            game.enemyClicks++;enemy.power--;enemy.flash=1;
            if(enemy.power<=0){
                removeEnemy(i,enemy,true);
                setStatus("暗影核心已净化",420);
                return true;
            }
            updateEnemyStrength(enemy);
            var target=nearestTarget(enemy.x,enemy.y);
            var dx=enemy.x-target.x;var dy=enemy.y-target.y;var len=math.sqrt(dx*dx+dy*dy);
            if(len<1){dx=1;dy=0;len=1;}
            enemy.vx=dx/len*enemy.speed*0.95;enemy.vy=dy/len*enemy.speed*0.95;
            enemy.bounceCooldown=0.24;
            burst(enemy.x,enemy.y,7,0xFFFFFFFF,115,"spark");
            addShock(enemy.x,enemy.y,enemy.color,enemy.r+7);
            setStatus("暗影被削弱：体型与缺口破坏力同步下降",470);
            if(game.soundCooldown<=0){
                var notes=["E6","G6","A6","C7"];
                playNote(11,notes[(enemy.power-1)%#notes+1],24);
                game.soundCooldown=58;
            }
            return true;
        }
    }
    return false;
}

var playBreachSound=function(enemy){
    if(game.soundCooldown>0) return;
    var heavy=enemy.typ=="brute" || enemy.typ=="boss";
    playNote(heavy ? 47 : 98,heavy ? "C3" : "D5",heavy ? 48 : 28);
    game.soundCooldown=heavy ? 300 : 220;
}

var impactWard=function(enemy,hit,nx,ny){
    game.impacts++;
    var breach=addBreach(hit,enemy.currentBreach);
    game.repairCharges=math.min(game.repairMax,game.repairCharges+1);
    game.wardEnergy=game.wardMax*wardIntegrity();

    var effectColor=enemy.typ=="boss" ? 0xFFFFC45E : 0xFFFF668C;
    table.push(game.hits,{x=hit.qx;y=hit.qy;life=0.70;maxLife=0.70;color=effectColor});
    burst(hit.qx,hit.qy,enemy.typ=="boss" ? 18 : (enemy.typ=="brute" ? 13 : 9),effectColor,enemy.typ=="boss" ? 215 : 165,"spark");
    addShock(hit.qx,hit.qy,effectColor,enemy.r+24);

    enemy.hits++;enemy.flash=1;
    enemy.vx=enemy.vx*0.86+nx*enemy.speed*0.82;
    enemy.vy=enemy.vy*0.86+ny*enemy.speed*0.82;
    enemy.bounceCooldown=enemy.typ=="boss" ? 0.62 : 0.48;
    breach.flash=1;
    setStatus((enemy.typ=="boss" ? "首领撕开巨大缺口！" : "结界被炸开！")++" 已获得 1 份修复星尘",760);
    playBreachSound(enemy);
}

var hitGuardian=function(index,enemy,target){
    game.guardianHp=math.max(0,game.guardianHp-enemy.damage);
    game.targetFlash=1;
    burst(target.x,target.y,12,0xFFFF668F,145,"spark");
    addShock(target.x,target.y,0xFFFF668F,target.r+18);
    playNote(116,"C3",85);
    table.remove(game.enemies,index);
    if(game.guardianHp<=0) failLevel();
    else setStatus("暗影击中了星灵！剩余光核 "++game.guardianHp,1200);
}

var moveEnemy=function(index,enemy,dt){
    var target=nearestTarget(enemy.x,enemy.y);
    enemy.age+=dt;
    enemy.r+=(enemy.targetR-enemy.r)*math.min(1,dt*11);
    enemy.flash=math.max(0,enemy.flash-dt*4.5);
    enemy.bounceCooldown=math.max(0,enemy.bounceCooldown-dt);

    if(enemy.bounceCooldown<=0){
        var dx=target.x-enemy.x;var dy=target.y-enemy.y;
        var len=math.sqrt(dx*dx+dy*dy);
        if(len>0){
            var speed=enemy.speed;
            enemy.dashClock-=dt;
            if(enemy.typ=="fast" && enemy.dashClock<0.30) speed*=1.75;
            if(enemy.dashClock<=0) enemy.dashClock=1.55+math.random()*0.65;
            var factor=math.min(1,dt*enemy.turn);
            enemy.vx+=(dx/len*speed-enemy.vx)*factor;
            enemy.vy+=(dy/len*speed-enemy.vy)*factor;
        }
    }

    //只在敌人接近且确实朝防线运动时记录预兆，绘制层无需重复执行几何查询。
    enemy.warning=0;
    var forecast=nearestBarrier(enemy.x,enemy.y);
    if(forecast && enemy.bounceCooldown<=0){
        var radius=enemy.r+WARD_WIDTH/2;
        var gap=forecast.distance-radius;
        var toward=enemy.vx*(forecast.qx-enemy.x)+enemy.vy*(forecast.qy-enemy.y);
        if(gap>=0 && gap<=68 && toward>0){
            enemy.warning=1-gap/68;enemy.warningX=forecast.qx;enemy.warningY=forecast.qy;
        }
    }

    var moveX=enemy.vx*dt;var moveY=enemy.vy*dt;
    var distance=math.sqrt(moveX*moveX+moveY*moveY);
    var steps=math.max(1,math.ceil(distance/4));
    for(step=1;steps){
        var nextX=enemy.x+moveX/steps;var nextY=enemy.y+moveY/steps;
        if(enemy.bounceCooldown<=0 && #game.wardPoints>1){
            var hit=nearestBarrier(nextX,nextY);
            var radius=enemy.r+WARD_WIDTH/2;
            if(hit && hit.distance<=radius){
                var nx=nextX-hit.qx;var ny=nextY-hit.qy;
                var nlen=math.sqrt(nx*nx+ny*ny);
                if(nlen<0.01){
                    var a=game.wardPoints[hit.segment];var b=game.wardPoints[hit.segment+1];
                    nx=-(b.y-a.y);ny=b.x-a.x;nlen=math.sqrt(nx*nx+ny*ny);
                }
                nx/=nlen;ny/=nlen;
                if(enemy.vx*nx+enemy.vy*ny>0){nx=-nx;ny=-ny;}
                enemy.x=hit.qx+nx*(radius+1.2);
                enemy.y=hit.qy+ny*(radius+1.2);
                var dot=enemy.vx*nx+enemy.vy*ny;
                enemy.vx=(enemy.vx-2*dot*nx)*0.82+nx*42;
                enemy.vy=(enemy.vy-2*dot*ny)*0.82+ny*42;
                enemy.bounceCooldown=enemy.typ=="boss" ? 0.16 : 0.22;
                impactWard(enemy,hit,nx,ny);
                break;
            }
        }
        enemy.x=nextX;enemy.y=nextY;
    }

    target=nearestTarget(enemy.x,enemy.y);
    if(math.dist(enemy.x,enemy.y,target.x,target.y)<=enemy.r+target.r){
        hitGuardian(index,enemy,target);
        return true;
    }
    return false;
}

beginPurify=function(){
    if(game.state!=STATE_DEFEND) return;
    game.state=STATE_SOLVED;game.resultTime=0;game.purifyRadius=0;game.purifyClock=0;game.purifyReady=false;
    game.breaches=[];game.hoverBreach=0;win.releaseCapture();
    game.statusText=defaultStatus();game.statusTime=0;
    for(i,target in game.targets){
        burst(target.x,target.y,24,levelData().accent,190,"spark");
        addShock(target.x,target.y,levelData().accent,42);
    }
    playNote(89,"C5 E5 G5 C6",170);
}

failLevel=function(){
    if(game.state==STATE_FAILED) return;
    game.state=STATE_FAILED;game.resultTime=0;game.drawing=false;
    win.releaseCapture();
    game.statusText=defaultStatus();game.statusTime=0;
    playNote(116,"C3 A2 F2",170);
}

var updateParticles=function(dt){
    for(i=#game.particles;1;-1){
        var p=game.particles[i];
        p.life-=dt;
        p.x+=p.vx*dt;p.y+=p.vy*dt;
        var damping=0.965 ** (dt*60);
        p.vx*=damping;p.vy*=damping;
        if(p.kind=="mote") p.vy-=6*dt;
        if(p.life<=0) table.remove(game.particles,i);
    }
    for(i=#game.shocks;1;-1){
        var s=game.shocks[i];s.life-=dt;
        if(s.life<=0) table.remove(game.shocks,i);
    }
    for(i=#game.hits;1;-1){
        var h=game.hits[i];h.life-=dt;
        if(h.life<=0) table.remove(game.hits,i);
    }
}

// -------------------- 绘图 --------------------
var starPoints=function(cx,cy,outerRadius,innerRadius,count,rotation){
    var points=[];
    for(i=0;count*2-1){
        var angle=rotation+i*math.pi/count;
        var radius=i%2==0 ? outerRadius : innerRadius;
        table.push(points,{x=cx+math.cos(angle)*radius;y=cy+math.sin(angle)*radius});
    }
    return points;
}

var drawAnchorBase=function(graphics,anchor){
    var pen1=gdip.pen(0x654B6B98,1.4);
    var pen2=gdip.pen(0xA06491BC,1.15);
    graphics.drawCircle(pen1,anchor.x,anchor.y,18);
    graphics.drawCircle(pen2,anchor.x,anchor.y,9);
    graphics.drawPolygon(pen2,starPoints(anchor.x,anchor.y,7,3.5,4,-math.pi/2));
    pen2.delete();pen1.delete();
}

var buildSceneCache=function(){
    if(sceneCache) sceneCache.delete();
    var width=math.floor(CANVAS_W*CACHE_SCALE);
    var height=math.floor(CANVAS_H*CACHE_SCALE);
    sceneCache=gdip.bitmap(width,height);
    var graphics=gdip.graphics(sceneCache);
    graphics.scale(CACHE_SCALE,CACHE_SCALE);
    graphics.smoothingMode=4/*_SmoothingModeAntiAlias*/;
    graphics.textRenderingHint=4/*_TextRenderingHintAntiAlias*/;

    var bg=gdip.lineBrush(::POINTF(0,0),::POINTF(CANVAS_W,CANVAS_H),0xFF24143F,0xFF070B1A);
    graphics.fillRect(bg,0,0,CANVAS_W,CANVAS_H);bg.delete();
    for(i,s in stars){
        var brush=gdip.solidBrush(alphaColor(0xFFDCE8FF,s.alpha));
        graphics.fillCircle(brush,s.x,s.y,s.size);brush.delete();
    }

    var data=levelData();
    drawText(graphics,"星 灵 结 界：守 护 最 后 的 光",27,24,12,748,40,0xFFF7F3FF,true,1);
    drawText(graphics,"STELLAR WARD · LAST LIGHT",10,24,48,748,20,0x929FD7EC,false,1);
    drawText(graphics,"画下结界 · 点击缺口修复；连点暗影净化目标",13,24,73,748,24,0xC8D9D4E9,false,1);

    var arenaShadow=gdip.solidBrush(0x55000000);
    graphics.fillRoundRect(arenaShadow,ARENA_X+8,ARENA_Y+10,ARENA_W,ARENA_H,24);arenaShadow.delete();
    var arena=gdip.lineBrush(::POINTF(ARENA_X,ARENA_Y),::POINTF(ARENA_X+ARENA_W,ARENA_Y+ARENA_H),0xE0191735,0xF0091426);
    graphics.fillRoundRect(arena,ARENA_X,ARENA_Y,ARENA_W,ARENA_H,24);arena.delete();
    var border=gdip.pen(alphaColor(data.accent,90),1.4);
    graphics.drawRoundRect(border,ARENA_X+1,ARENA_Y+1,ARENA_W-2,ARENA_H-2,23);border.delete();

    for(i,s in arenaDust){
        var dust=gdip.solidBrush(alphaColor(0xFFB9CEFF,s.alpha));
        graphics.fillCircle(dust,s.x,s.y,s.size);dust.delete();
    }

    var cx=ARENA_X+ARENA_W/2;var cy=ARENA_Y+ARENA_H/2;
    for(r=90;300;70){
        var ring=gdip.pen(0x2A486994,1);
        graphics.drawCircle(ring,cx,cy,r);ring.delete();
    }
    var radial=gdip.pen(0x203F5E8C,1);
    for(i=0;11){
        var angle=i*math.pi/6;
        graphics.drawLine(radial,cx+math.cos(angle)*72,cy+math.sin(angle)*72,cx+math.cos(angle)*332,cy+math.sin(angle)*332);
    }
    radial.delete();

    for(i,anchor in game.anchors) drawAnchorBase(graphics,anchor);

    var panelShadow=gdip.solidBrush(0x55000000);
    graphics.fillRoundRect(panelShadow,PANEL_X+6,PANEL_Y+9,PANEL_W,PANEL_H,23);panelShadow.delete();
    var panel=gdip.solidBrush(0xD817142D);
    graphics.fillRoundRect(panel,PANEL_X,PANEL_Y,PANEL_W,PANEL_H,23);panel.delete();
    var panelBorder=gdip.pen(0x4D7881A9,1);
    graphics.drawRoundRect(panelBorder,PANEL_X+0.5,PANEL_Y+0.5,PANEL_W-1,PANEL_H-1,23);panelBorder.delete();

    drawText(graphics,"第 "++game.level++" / "++#levels++" 章  ·  "++difficulty.name,11,PANEL_X+18,PANEL_Y+17,PANEL_W-36,22,0xAFC8C2DA,false,0);
    drawText(graphics,data.name,22,PANEL_X+18,PANEL_Y+43,PANEL_W-36,34,data.accent,true,0);
    drawText(graphics,data.subtitle,11,PANEL_X+18,PANEL_Y+79,PANEL_W-36,44,0xBFD8D2E5,false,0);
    drawText(graphics,"法阵充能",11,PANEL_X+18,PANEL_Y+142,90,20,0xAFC3BDD4,false,0);
    drawText(graphics,"守护光值",11,PANEL_X+18,PANEL_Y+208,90,20,0xAFC3BDD4,false,0);
    drawText(graphics,"结界完整度",11,PANEL_X+18,PANEL_Y+274,90,20,0xAFC3BDD4,false,0);

    drawText(graphics,"增幅符文",11,PANEL_X+18,PANEL_Y+406,90,20,0xAFC3BDD4,false,0);
    drawText(graphics,"H：提示 · M：音效",10,PANEL_X+18,PANEL_Y+523,PANEL_W-36,19,0xB3CBC5D7,false,0);
    drawText(graphics,"R / 右键：重置",10,PANEL_X+18,PANEL_Y+546,PANEL_W-36,19,0xB3CBC5D7,false,0);

    graphics.delete();sceneDirty=false;
}

var drawPortals=function(graphics){
    for(i,p in game.portals){
        var pulse=2.5*math.sin(game.pulse/260+p.angle);
        var glow=gdip.pen(alphaColor(0xFFA76CFF,45),10+pulse);
        var ring1=gdip.pen(alphaColor(0xFFB77AFF,150),2.1);
        var ring2=gdip.pen(alphaColor(levelData().accent,95),1.2);
        graphics.drawCircle(glow,p.x,p.y,22+pulse);
        graphics.drawArc(ring1,p.x-24,p.y-24,48,48,game.pulse/12+i*47,238);
        graphics.drawArc(ring2,p.x-17,p.y-17,34,34,-game.pulse/9-i*31,220);
        ring2.delete();ring1.delete();glow.delete();
        var core=gdip.solidBrush(0xD6060714);
        graphics.fillCircle(core,p.x,p.y,13);core.delete();
        var eye=gdip.solidBrush(alphaColor(0xFFEACBFF,90+45*math.sin(game.pulse/190+i)));
        graphics.fillCircle(eye,p.x,p.y,3.5);eye.delete();
    }
}

var drawHint=function(graphics){
    if(game.hintTime<=0 || game.state!=STATE_READY) return;
    var minX=10000;var maxX=-10000;var minY=10000;var maxY=-10000;
    for(i,t in game.targets){
        minX=math.min(minX,t.x-t.r);maxX=math.max(maxX,t.x+t.r);
        minY=math.min(minY,t.y-t.r);maxY=math.max(maxY,t.y+t.r);
    }
    var cx=(minX+maxX)/2;var cy=(minY+maxY)/2;
    var rx=(maxX-minX)/2+52;var ry=(maxY-minY)/2+52;
    var pen=gdip.pen(alphaColor(levelData().accent,90+45*math.sin(game.pulse/150)),2.2);
    for(i=0;11){
        var start=i*30+game.pulse/28;
        graphics.drawArc(pen,cx-rx,cy-ry,rx*2,ry*2,start,17);
    }
    pen.delete();
}

var wardColor=function(){
    var ratio=wardIntegrity();
    if(game.state==STATE_READY) return levelData().accent;
    if(ratio>0.78) return 0xFF61ECFF;
    if(ratio>0.52) return 0xFFB67CFF;
    return 0xFFFF796E;
}

var drawWard=function(graphics){
    if(#game.wardPoints<2) return;
    var color=wardColor();var alpha=255;
    var glow=gdip.pen(alphaColor(color,alpha*0.17),WARD_WIDTH+3);
    var outer=gdip.pen(alphaColor(shadeColor(color,-70),alpha*0.80),WARD_WIDTH+3);
    var core=gdip.pen(alphaColor(color,alpha),6.2);
    var white=gdip.pen(alphaColor(0xFFFFFFFF,alpha*0.72),1.6);
    var runs=getWardRuns();
    for(i,run in runs){
        graphics.drawLines(glow,run);graphics.drawLines(outer,run);
        graphics.drawLines(core,run);graphics.drawLines(white,run);
    }
    white.delete();core.delete();outer.delete();glow.delete();

    var total=polylineLength(game.wardPoints);
    var flow=gdip.solidBrush(alphaColor(0xFFFFFFFF,195));
    var offset=(game.pulse/7)%38;
    for(distance=offset;distance<total;distance+=38){
        var p=pointOnWard(distance);
        if(p && !isSegmentBroken(p.segment)) graphics.fillCircle(flow,p.x,p.y,2.2);
    }
    flow.delete();
    var endBrush=gdip.solidBrush(color);
    if(!isSegmentBroken(1)) graphics.fillCircle(endBrush,game.wardPoints[1].x,game.wardPoints[1].y,4.2);
    var lastIndex=#game.wardPoints-1;
    if(!isSegmentBroken(lastIndex)){
        var last=game.wardPoints[#game.wardPoints];graphics.fillCircle(endBrush,last.x,last.y,4.2);
    }
    endBrush.delete();
}

var drawCombatGuides=function(graphics){
    //真实缺口完全留白，仅在两端绘制红热断口；修复时缺失段会由两端向中心逐步回长。
    for(i,breach in game.breaches){
        var first,last=effectiveBreachRange(breach);
        var a=game.wardPoints[first];var b=game.wardPoints[last+1];
        if(a && b){
            var repairing=breach.repairing;
            var hovered=game.hoverBreach==i;
            var color=repairing ? 0xFF8DFFF0 : 0xFFFF4E72;
            var pulse=1+0.18*math.sin(game.pulse/80+i);
            var flash=breach.flash : 0;
            var glow=gdip.pen(alphaColor(color,hovered ? 145 : 85+flash*80),hovered ? 19 : 14+flash*5);
            var hot=gdip.pen(alphaColor(0xFFFFFFFF,repairing ? 250 : 225+flash*30),2.8+flash*1.8);
            graphics.drawCircle(glow,a.x,a.y,8*pulse);graphics.drawCircle(glow,b.x,b.y,8*pulse);
            var ember=gdip.solidBrush(alphaColor(color,repairing ? 235 : 225+flash*30));
            var emberCore=gdip.solidBrush(0xFFFFFFFF);
            graphics.fillCircle(ember,a.x,a.y,5.2+flash*1.8);graphics.fillCircle(ember,b.x,b.y,5.2+flash*1.8);
            graphics.fillCircle(emberCore,a.x,a.y,2.2);graphics.fillCircle(emberCore,b.x,b.y,2.2);
            emberCore.delete();ember.delete();
            graphics.drawCircle(hot,a.x,a.y,5.8);graphics.drawCircle(hot,b.x,b.y,5.8);
            var mx=(a.x+b.x)/2;var my=(a.y+b.y)/2;
            var dx=mx-a.x;var dy=my-a.y;var len=math.sqrt(dx*dx+dy*dy);
            if(len<1) len=1;dx/=len;dy/=len;
            var nx=-dy;var ny=dx;
            var bracket=gdip.pen(alphaColor(color,hovered ? 245 : 195+flash*50),hovered ? 3 : 2.2+flash);
            for(shard=-1;1){
                var side=shard*4.5;
                graphics.drawLine(bracket,a.x+nx*side,a.y+ny*side,a.x+dx*(10+math.abs(shard)*3)+nx*side*0.35,a.y+dy*(10+math.abs(shard)*3)+ny*side*0.35);
                graphics.drawLine(bracket,b.x+nx*side,b.y+ny*side,b.x-dx*(10+math.abs(shard)*3)+nx*side*0.35,b.y-dy*(10+math.abs(shard)*3)+ny*side*0.35);
            }
            if(repairing){
                graphics.drawCircle(bracket,mx,my,12+(1-breach.repair)*26);
            }
            elseif(hovered){
                drawText(graphics,"点击修复",10,mx-39,my-31,78,20,0xFFFFD6DE,true,1);
            }
            bracket.delete();hot.delete();glow.delete();
        }
    }
}

var drawAnchors=function(graphics){
    for(i,a in game.anchors){
        if(a.charged){
            var glow=gdip.pen(alphaColor(levelData().accent,45+35*math.sin(game.pulse/150+i)),7);
            var ring=gdip.pen(alphaColor(0xFFFFFFFF,195),1.6);
            graphics.drawCircle(glow,a.x,a.y,15);
            graphics.drawCircle(ring,a.x,a.y,12+2*math.sin(game.pulse/210+i));
            ring.delete();glow.delete();
            var core=gdip.solidBrush(levelData().accent);graphics.fillCircle(core,a.x,a.y,3.5);core.delete();
        }
    }
}

var drawTarget=function(graphics,target,index){
    var data=levelData();
    var pulse=1+0.06*math.sin(game.pulse/180+index);
    var flash=game.targetFlash;
    var haloColor=flash>0 ? 0xFFFF5E83 : data.accent;
    var halo=gdip.solidBrush(alphaColor(haloColor,22+28*pulse+flash*60));
    graphics.fillCircle(halo,target.x,target.y,(target.r+21)*pulse);halo.delete();
    var orbit=gdip.pen(alphaColor(data.accent,120),1.6);
    graphics.drawArc(orbit,target.x-target.r-13,target.y-target.r-13,(target.r+13)*2,(target.r+13)*2,game.pulse/10+index*90,238);
    graphics.drawArc(orbit,target.x-target.r-8,target.y-target.r-8,(target.r+8)*2,(target.r+8)*2,-game.pulse/13-index*55,152);
    orbit.delete();
    var body=gdip.lineBrush(::POINTF(target.x-target.r,target.y-target.r),::POINTF(target.x+target.r,target.y+target.r),shadeColor(data.accent,65),shadeColor(data.accent,-65));
    graphics.fillPolygon(body,starPoints(target.x,target.y,target.r,target.r*0.43,6,-math.pi/2+game.pulse/5000));
    body.delete();
    var core=gdip.solidBrush(flash>0 ? 0xFFFFFFFF : 0xFFF9F1FF);
    graphics.fillCircle(core,target.x,target.y,target.r*0.27);core.delete();
    var eye=gdip.solidBrush(0xFF271746);
    graphics.fillCircle(eye,target.x-5,target.y-1,1.8);graphics.fillCircle(eye,target.x+5,target.y-1,1.8);eye.delete();
    var smile=gdip.pen(0xB1271746,1.4);graphics.drawArc(smile,target.x-6,target.y,target.r*0.45,target.r*0.30,10,160);smile.delete();
    if(game.purifyReady){
        var readyGlow=gdip.pen(alphaColor(0xFFFFEFA1,65+45*math.sin(game.pulse/110)),12);
        var readyRing=gdip.pen(0xEEFFF3B0,2.2);
        graphics.drawCircle(readyGlow,target.x,target.y,target.r+28);
        graphics.drawCircle(readyRing,target.x,target.y,target.r+24+3*math.sin(game.pulse/120));
        readyRing.delete();readyGlow.delete();
        drawText(graphics,"点击释放",11,target.x-48,target.y+target.r+30,96,24,0xFFFFF0A8,true,1);
    }
}

var drawEnemy=function(graphics,enemy){
    var alpha=math.clamp(enemy.alpha*255,0,255);
    var r=enemy.r;
    var speed=math.sqrt(enemy.vx*enemy.vx+enemy.vy*enemy.vy);
    var dx=speed>0.1 ? enemy.vx/speed : 1;
    var dy=speed>0.1 ? enemy.vy/speed : 0;
    var pulse=1+0.05*math.sin(game.pulse/100+enemy.phase);
    var glow=gdip.solidBrush(alphaColor(enemy.color,alpha*0.18));
    graphics.fillCircle(glow,enemy.x,enemy.y,r*1.75*pulse);glow.delete();

    var tentacle=gdip.pen(alphaColor(shadeColor(enemy.color,-100),alpha*0.72),math.max(2,r*0.22));
    for(k=-1;1){
        var sideX=-dy*k*r*0.42;var sideY=dx*k*r*0.42;
        graphics.drawLine(tentacle,enemy.x-dx*r*0.45+sideX,enemy.y-dy*r*0.45+sideY,
            enemy.x-dx*r*(1.15+0.18*math.sin(game.pulse/120+k))+sideX*1.4,
            enemy.y-dy*r*(1.15+0.18*math.sin(game.pulse/120+k))+sideY*1.4);
    }
    tentacle.delete();

    var body=gdip.lineBrush(::POINTF(enemy.x-r,enemy.y-r),::POINTF(enemy.x+r,enemy.y+r),shadeColor(enemy.color,18),shadeColor(enemy.color,-105));
    graphics.fillCircle(body,enemy.x,enemy.y,r*pulse);body.delete();
    var edge=gdip.pen(alphaColor(enemy.color,alpha*0.9),enemy.typ=="boss" ? 2.4 : 1.3);
    graphics.drawCircle(edge,enemy.x,enemy.y,r*pulse);edge.delete();

    if(enemy.typ=="brute" || enemy.typ=="boss"){
        var horn=gdip.solidBrush(alphaColor(0xFFFFD6A5,alpha*0.85));
        graphics.fillPolygon(horn,[
            {x=enemy.x-dy*r*0.55-dx*r*0.5;y=enemy.y+dx*r*0.55-dy*r*0.5},
            {x=enemy.x-dy*r*1.05-dx*r*0.9;y=enemy.y+dx*r*1.05-dy*r*0.9},
            {x=enemy.x-dy*r*0.15-dx*r*0.7;y=enemy.y+dx*r*0.15-dy*r*0.7}
        ]);
        graphics.fillPolygon(horn,[
            {x=enemy.x+dy*r*0.55-dx*r*0.5;y=enemy.y-dx*r*0.55-dy*r*0.5},
            {x=enemy.x+dy*r*1.05-dx*r*0.9;y=enemy.y-dx*r*1.05-dy*r*0.9},
            {x=enemy.x+dy*r*0.15-dx*r*0.7;y=enemy.y-dx*r*0.15-dy*r*0.7}
        ]);
        horn.delete();
    }

    var eye=gdip.solidBrush(alphaColor(enemy.flash>0 ? 0xFFFFFFFF : 0xFFFFE9F7,alpha));
    graphics.fillCircle(eye,enemy.x+dx*r*0.34-dy*r*0.28,enemy.y+dy*r*0.34+dx*r*0.28,math.max(1.5,r*0.13));
    graphics.fillCircle(eye,enemy.x+dx*r*0.34+dy*r*0.28,enemy.y+dy*r*0.34-dx*r*0.28,math.max(1.5,r*0.13));
    eye.delete();

    var powerRatio=enemy.maxPower>0 ? enemy.power/enemy.maxPower : 0;
    var powerRing=gdip.pen(alphaColor(0xFFFFFFFF,alpha*0.78),enemy.typ=="boss" ? 2.6 : 1.8);
    graphics.drawArc(powerRing,enemy.x-r-5,enemy.y-r-5,(r+5)*2,(r+5)*2,-90,360*powerRatio);
    powerRing.delete();
    if(enemy.typ=="boss"){
        var crown=gdip.pen(alphaColor(0xFFFFE08B,alpha*(0.65+0.25*math.sin(game.pulse/120))),2.2);
        graphics.drawCircle(crown,enemy.x,enemy.y,r+9+2*math.sin(game.pulse/160));crown.delete();
    }
    if(enemy.flash>0){
        var flash=gdip.solidBrush(alphaColor(0xFFFFFFFF,enemy.flash*115));graphics.fillCircle(flash,enemy.x,enemy.y,r);flash.delete();
    }
}

var drawEffects=function(graphics){
    for(i,s in game.shocks){
        var ratio=math.clamp(s.life/s.maxLife,0,1);
        var radius=s.size+(1-ratio)*42;
        var pen=gdip.pen(alphaColor(s.color,ratio*170),1.2+ratio*2.2);
        graphics.drawCircle(pen,s.x,s.y,radius);pen.delete();
    }
    for(i,h in game.hits){
        var ratio=math.clamp(h.life/h.maxLife,0,1);
        var pen=gdip.pen(alphaColor(0xFFFFFFFF,ratio*220),2+ratio*4);
        graphics.drawCircle(pen,h.x,h.y,7+(1-ratio)*18);
        for(k=0;3){
            var angle=k*math.pi/2+game.pulse/180;
            graphics.drawLine(pen,h.x+math.cos(angle)*4,h.y+math.sin(angle)*4,h.x+math.cos(angle)*(13+12*(1-ratio)),h.y+math.sin(angle)*(13+12*(1-ratio)));
        }
        pen.delete();
    }
    for(i,p in game.particles){
        var ratio=math.clamp(p.life/p.maxLife,0,1);
        if(p.kind=="spark"){
            var pen=gdip.pen(alphaColor(p.color,ratio*230),p.size*ratio+0.6);
            graphics.drawLine(pen,p.x,p.y,p.x-p.vx*0.035,p.y-p.vy*0.035);pen.delete();
        }
        else{
            var brush=gdip.solidBrush(alphaColor(p.color,ratio*180));graphics.fillCircle(brush,p.x,p.y,p.size*ratio+0.5);brush.delete();
        }
    }
}

var drawPanel=function(graphics){
    var data=levelData();
    var charge=game.state==STATE_READY ? 0 : math.clamp(game.defendTime/data.duration,0,1);
    var remaining=math.max(0,data.duration-game.defendTime);
    drawBar(graphics,PANEL_X+18,PANEL_Y+169,PANEL_W-36,12,charge>=0.025 ? charge : 0,data.accent);
    var chargeText;
    if(game.state==STATE_READY) chargeText="等待结界";
    elseif(game.state==STATE_SOLVED) chargeText="充能完成";
    elseif(game.state==STATE_FAILED) chargeText="充能中断";
    elseif(game.purifyReady) chargeText="点击释放";
    else chargeText=string.format("%.1f 秒",remaining);
    drawText(graphics,chargeText,11,PANEL_X+118,PANEL_Y+136,100,25,0xFFE7E2F0,true,2);

    var hpRatio=game.guardianMax>0 ? game.guardianHp/game.guardianMax : 0;
    drawBar(graphics,PANEL_X+18,PANEL_Y+235,PANEL_W-36,12,hpRatio,game.targetFlash>0 ? 0xFFFF5F7F : 0xFFFF7FC8);
    drawText(graphics,game.guardianHp++" / "++game.guardianMax,11,PANEL_X+130,PANEL_Y+202,88,25,0xFFFFD8EC,true,2);

    var wardRatio=wardIntegrity();
    drawBar(graphics,PANEL_X+18,PANEL_Y+301,PANEL_W-36,12,wardRatio,wardColor());
    drawText(graphics,math.floor(wardRatio*100)++"% · 缺口 "++#game.breaches,11,PANEL_X+106,PANEL_Y+268,112,25,#game.breaches>2 ? 0xFFFF9BAF : 0xFFE5E1EF,true,2);

    drawText(graphics,game.state==STATE_READY ? "绘制墨水" : "修复星尘",11,PANEL_X+18,PANEL_Y+340,120,20,0xAFC3BDD4,false,0);
    var resourceRatio=game.state==STATE_READY ? game.inkUsed/levelData().ink : game.repairCharges/game.repairMax;
    var resourceColor=game.state==STATE_READY ? 0xFF8BE6B7 : 0xFF83F0B8;
    var resourceText=game.state==STATE_READY ? math.floor(game.inkUsed)++" / "++levelData().ink : game.repairCharges++" / "++game.repairMax;
    drawBar(graphics,PANEL_X+18,PANEL_Y+367,PANEL_W-36,12,resourceRatio,resourceColor);
    drawText(graphics,resourceText,11,PANEL_X+118,PANEL_Y+334,100,25,0xFFD7F6E5,true,2);

    var charged=0;for(i,a in game.anchors) if(a.charged) charged++;
    for(i=1;#game.anchors){
        var x=PANEL_X+27+(i-1)*37;
        var ring=gdip.pen(i<=charged ? data.accent : 0x806F7794,1.6);
        graphics.drawCircle(ring,x,PANEL_Y+443,10);ring.delete();
        if(i<=charged){var core=gdip.solidBrush(data.accent);graphics.fillCircle(core,x,PANEL_Y+443,4);core.delete();}
    }
    drawText(graphics,charged++" / "++#game.anchors,11,PANEL_X+160,PANEL_Y+430,58,25,0xFFE4DFEC,true,2);

    var statusColor=game.state==STATE_FAILED ? 0xFFFF7894 : (game.state==STATE_SOLVED ? data.accent : 0xFFD9D3E5);
    drawText(graphics,game.statusText,11,PANEL_X+18,PANEL_Y+465,PANEL_W-36,52,statusColor,game.state!=STATE_READY,0);
    if(game.state==STATE_SOLVED || game.state==STATE_FAILED){
        var footer=gdip.solidBrush(0xFF17142D);
        graphics.fillRoundRect(footer,PANEL_X+12,PANEL_Y+518,PANEL_W-24,54,9);footer.delete();
        var footerText;
        if(game.state==STATE_FAILED) footerText="单击画面 / 空格：重试";
        elseif(game.level==#levels) footerText="单击画面 / 空格：再次守护";
        else footerText="单击画面 / 空格：进入下一章";
        drawText(graphics,footerText,10,PANEL_X+20,PANEL_Y+529,PANEL_W-40,28,game.state==STATE_SOLVED ? levelData().accent : 0xFFFFA2B7,true,1);
    }
}

var drawResult=function(graphics){
    if(game.state!=STATE_SOLVED && game.state!=STATE_FAILED) return;
    if(game.resultTime<0.75) return;
    var t=math.clamp((game.resultTime-0.75)/0.45,0,1);
    var x=ARENA_X+170;var y=ARENA_Y+190;var w=408;var h=196;
    var shadow=gdip.solidBrush(alphaColor(0xFF000000,t*110));
    graphics.fillRoundRect(shadow,x+7,y+9,w,h,25);shadow.delete();
    var panel=gdip.solidBrush(alphaColor(game.state==STATE_SOLVED ? 0xFF19143B : 0xFF321327,t*250));
    graphics.fillRoundRect(panel,x,y,w,h,25);panel.delete();
    var color=game.state==STATE_SOLVED ? levelData().accent : 0xFFFF6A8A;
    var border=gdip.pen(alphaColor(color,t*210),2);graphics.drawRoundRect(border,x,y,w,h,25);border.delete();
    if(game.state==STATE_SOLVED){
        var starsCount=game.guardianHp==game.guardianMax && #game.breaches==0 ? 3 : (game.guardianHp>1 ? 2 : 1);
        var starText="";for(i=1;3) starText++=i<=starsCount ? "★ " : "☆ ";
        drawText(graphics,"守 护 完 成",25,x,y+23,w,38,alphaColor(color,t*255),true,1);
        drawText(graphics,"LAST LIGHT RESTORED",10,x,y+61,w,22,alphaColor(0xFFE5DDF2,t*175),false,1);
        drawText(graphics,starText,25,x,y+89,w,36,alphaColor(0xFFFFD86A,t*255),true,1);
        drawText(graphics,"净化暗影 "++game.banished++" 只 · 炸开缺口 "++game.breachesMade++" 次 · 修复 "++game.repairs++" 次",11,x,y+128,w,25,alphaColor(0xFFF0EAF7,t*220),false,1);
        drawText(graphics,game.level==#levels ? "单击 / 空格：再次守护星海" : "单击 / 空格：进入下一章",11,x,y+158,w,23,alphaColor(0xFFD4CCDF,t*205),false,1);
    }
    else{
        drawText(graphics,"最 后 的 光 熄 灭",24,x,y+29,w,38,alphaColor(color,t*255),true,1);
        drawText(graphics,"结界的缺口让暗影触碰了星核",12,x,y+80,w,28,alphaColor(0xFFF0DCE4,t*215),false,1);
        drawText(graphics,"点击暗影削弱威力，优先修补最大的真实缺口",11,x+30,y+116,w-60,34,alphaColor(0xFFD9C9D2,t*190),false,1);
        drawText(graphics,"单击 / 空格：重新守护",11,x,y+160,w,22,alphaColor(0xFFFFB5C7,t*220),true,1);
    }
}

winform.wardCanvas.onDrawBackground=function(graphics,rc,backgroundColor,color){return true;}

winform.wardCanvas.onDrawForeground=function(graphics,rc,foregroundRect,foregroundColor,color,font){
    var dpiX,dpiY=winform.dpiScale(1,1);
    if(math.abs(CACHE_SCALE-dpiX)>0.001){CACHE_SCALE=dpiX;sceneDirty=true;}
    if(sceneDirty || !sceneCache) buildSceneCache();
    graphics.interpolationMode=3/*_InterpolationModeBilinear*/;
    graphics.drawImageRect(sceneCache,0,0,rc.width,rc.height);

    var saved=graphics.save();
    graphics.scale(dpiX,dpiY);
    graphics.smoothingMode=4/*_SmoothingModeAntiAlias*/;
    graphics.textRenderingHint=4/*_TextRenderingHintAntiAlias*/;

    var arenaState=graphics.save();
    graphics.setClipRect(ARENA_X,ARENA_Y,ARENA_W,ARENA_H);
    drawPortals(graphics);
    drawHint(graphics);
    drawAnchors(graphics);
    drawWard(graphics);
    drawCombatGuides(graphics);
    for(i,target in game.targets) drawTarget(graphics,target,i);
    for(i,enemy in game.enemies) drawEnemy(graphics,enemy);
    drawEffects(graphics);

    if(game.state==STATE_SOLVED){
        if(game.purifyRadius>0){
            var alpha=math.max(0,190-game.purifyRadius*0.13);
            var pen=gdip.pen(alphaColor(levelData().accent,alpha),4+6*math.max(0,1-game.resultTime/1.5));
            for(i,target in game.targets) graphics.drawCircle(pen,target.x,target.y,game.purifyRadius);
            pen.delete();
        }
        var auraAlpha=math.min(105,35+game.resultTime*45);
        for(i,target in game.targets){
            var auraPen=gdip.pen(alphaColor(levelData().accent,auraAlpha),1.5);
            graphics.drawCircle(auraPen,target.x,target.y,122+5*math.sin(game.pulse/170));
            graphics.drawCircle(auraPen,target.x,target.y,174+7*math.sin(game.pulse/210));
            for(ray=0;15){
                var angle=ray*math.pi/8+game.pulse/3600;
                graphics.drawLine(auraPen,
                    target.x+math.cos(angle)*184,target.y+math.sin(angle)*184,
                    target.x+math.cos(angle)*(214+10*math.sin(game.pulse/140+ray)),target.y+math.sin(angle)*(214+10*math.sin(game.pulse/140+ray))
                );
            }
            auraPen.delete();
        }
    }
    graphics.restore(arenaState);

    if(game.state==STATE_FAILED){
        var failedMask=gdip.solidBrush(0x69060714);
        graphics.fillRoundRect(failedMask,ARENA_X,ARENA_Y,ARENA_W,ARENA_H,24);failedMask.delete();
    }

    drawPanel(graphics);
    drawResult(graphics);
    graphics.restore(saved);
}

// -------------------- 动画循环 --------------------
winform.wardCanvas.onAnimation=function(prevState,beginning,change,timestamp,duration,deltaMs){
    deltaMs=math.min(deltaMs,55);
    var dt=deltaMs/1000;
    game.pulse+=deltaMs;
    game.soundCooldown=math.max(0,game.soundCooldown-deltaMs);
    game.repairCooldown=math.max(0,game.repairCooldown-dt);
    game.targetFlash=math.max(0,game.targetFlash-dt*3.2);
    game.hintTime=math.max(0,game.hintTime-deltaMs);
    for(i=#game.breaches;1;-1){
        var breach=game.breaches[i];
        breach.flash=math.max(0,(breach.flash : 0)-dt*3.5);
        if(breach.repairing){
            breach.repair+=dt/0.32;
            if(breach.repair>=1){
                table.remove(game.breaches,i);
                game.wardEnergy=game.wardMax*wardIntegrity();
            }
        }
    }

    if(game.statusTime>0){
        game.statusTime=math.max(0,game.statusTime-deltaMs);
        if(game.statusTime==0) game.statusText=defaultStatus();
    }

    if(game.state==STATE_DEFEND){
        game.defendTime+=dt;
        var data=levelData();
        if(!game.purifyReady){
            if(data.bossAt && !game.bossSpawned && game.defendTime>=data.bossAt){
                game.bossSpawned=true;spawnEnemy("boss");
            }
            game.spawnClock-=dt;
            if(game.spawnClock<=0 && game.defendTime<data.duration-0.35 && #game.enemies<11){
                spawnEnemy();
                game.spawnClock=data.spawnInterval*difficulty.spawn*(0.84+math.random()*0.32);
            }
            if(game.defendTime>=data.duration){
                game.purifyReady=true;
                setStatus("终焉法阵已就绪：单击星灵或按空格释放！",4000);
                for(i,target in game.targets) addShock(target.x,target.y,data.accent,46);
                playNote(89,"C5 E5 G5 B5",82);
            }
        }
        for(i=#game.enemies;1;-1) moveEnemy(i,game.enemies[i],dt);
    }
    elseif(game.state==STATE_SOLVED){
        game.resultTime+=dt;
        game.purifyRadius=math.min(820,game.purifyRadius+dt*(260+game.purifyRadius*0.85));
        game.purifyClock-=dt;
        if(game.purifyClock<=0 && game.resultTime<1.45){
            game.purifyClock=0.075;
            for(i,target in game.targets) burst(target.x,target.y,2,levelData().accent,230,"spark");
        }
        var cx=ARENA_X+ARENA_W/2;var cy=ARENA_Y+ARENA_H/2;
        for(i=#game.enemies;1;-1){
            var e=game.enemies[i];
            var dx=e.x-cx;var dy=e.y-cy;var len=math.sqrt(dx*dx+dy*dy);
            if(len<1){dx=1;dy=0;len=1;}
            e.x+=dx/len*190*dt;e.y+=dy/len*190*dt;e.alpha-=dt*0.72;e.flash=1;
            if(e.alpha<=0) removeEnemy(i,e,true);
        }
    }
    elseif(game.state==STATE_FAILED){
        game.resultTime+=dt;
        for(i,enemy in game.enemies){
            enemy.vx*=0.985 ** (deltaMs/16);enemy.vy*=0.985 ** (deltaMs/16);
            enemy.x+=enemy.vx*dt*0.2;enemy.y+=enemy.vy*dt*0.2;
        }
    }

    updateParticles(dt);
    return true;
}

winform.wardCanvas.cursor="cross";
winform.wardCanvas.startAnimation(24);
winform.wardCanvas.setFocus();

winform.show();
win.loopMessage();

```

