aardio 文档

aardio 范例: 鹈鹕之门 - 当场惊跌坐,大的要来了!

import win.ui;
/*DSG{{*/
var winform = win.form(text="鹈鹕之门 - 当场惊跌坐,大的要来了!";right=1060;bottom=720;bgcolor=0x081523;border="dialog frame";max=false)
winform.add(
pelicanCanvas={cls="plus";left=0;top=0;right=1060;bottom=720;clipBk=false;db=1;dl=1;dr=1;dt=1;notify=1;z=1}
)
/*}}*/

/*
鹈鹕之门
- 模型鱼被越来越大的版本泡泡托向 AGI? 漩涡。
- 等泡泡经过淡蓝色 TEST LINE,点击任意位置;鹈鹕会把线路上从近到远的第一个物体戳中。
- 版本越高,泡泡越大、越快、越靠近 AGI,也越容易彼此接触并触发连锁爆破。
- 模型鱼全部使用同等可爱的普通鱼造型;HYPE 河豚、幻觉水母、技术债旧靴子、基准泄漏仅代表行业现象。
- 默认启用舒缓规则:漏掉模型鱼会依次点亮 A → G → I,三个字母全亮才失败。
- 将 RELAXED_AGI_MODE 改为 false,即可切换为“一条模型鱼进入 AGI 立即失败”的硬核规则。
- M 切换事件音效,R 重开当前关。玩家的实际游戏动作始终只有“点击任意位置”。
*/

var CANVAS_W=1060;
var CANVAS_H=720;
var CX=620;
var CY=280;
var ORBIT_Y=0.72;
var CAPTURE_RADIUS=64;
var LINE_X=240;
var LINE_Y=520;
var LINE_LEN=790;
var RELAXED_AGI_MODE=true;

var lineVx=CX-LINE_X;
var lineVy=CY-LINE_Y;
var lineDistance=math.sqrt(lineVx*lineVx+lineVy*lineVy);
var LINE_DX=lineVx/lineDistance;
var LINE_DY=lineVy/lineDistance;
var LINE_NX=-LINE_DY;
var LINE_NY=LINE_DX;
var LINE_END_X=LINE_X+LINE_DX*LINE_LEN;
var LINE_END_Y=LINE_Y+LINE_DY*LINE_LEN;

var STATE_PLAY=1;
var STATE_WIN=2;
var STATE_FAILED=3;

var VERSION_LABELS=["1.2","2.5","3.0","AGI?"];
var VERSION_SIZES=[31,38,46,55];
var VERSION_COLORS=[0xFF63D9FF,0xFF8D8CFF,0xFFFF73C9,0xFFFFC85B];
var PHASES=[1.72,2.05,3.54,4.66,5.78,0.50,0.83,3.92];

var levels=[
    {name="第一次基准测试";subtitle="友情提醒:等泡泡穿过 TEST LINE";fish=4;inward=14.4;spin=0.61;accent=0xFF5BE9FF;specials=[]},
    {name="版本开始膨胀";subtitle="近点更好戳,更易连爆";fish=5;inward=15.2;spin=0.65;accent=0xFF8EA1FF;specials=[{kind="hype";at=0.45}]},
    {name="透明的干扰项";subtitle="幻觉水母只会挡你一次";fish=5;inward=16.0;spin=0.69;accent=0xFFC387FF;specials=[{kind="jelly";at=0.75}]},
    {name="历史包袱";subtitle="技术债会反弹,但也能一嘴撞飞";fish=6;inward=16.8;spin=0.73;accent=0xFFFFA56D;specials=[{kind="hype";at=0.45},{kind="boot";at=1.65}]},
    {name="基准开始泄漏";subtitle="LEAK 会让附近模型突然加速升级";fish=6;inward=17.7;spin=0.77;accent=0xFF52F2C4;specials=[{kind="jelly";at=0.55},{kind="leak";at=1.45}]},
    {name="发布会前夜";subtitle="大的要来了,小的要去了!";fish=7;inward=18.6;spin=0.81;accent=0xFFFFD35E;specials=[{kind="hype";at=0.35},{kind="boot";at=1.75},{kind="leak";at=2.65}]}
];

//不借用任何真实模型名字;差异只来自颜色、鳍纹与通用能力暗示。
var modelDefs=[
    {name="推理鳍";color=0xFF62D9FF;mark=1},
    {name="视觉鳞";color=0xFFFF78B8;mark=2},
    {name="代码尾";color=0xFF8D8CFF;mark=3},
    {name="长文鱼";color=0xFF62E2A5;mark=4},
    {name="工具鳍";color=0xFFFFB85C;mark=5},
    {name="多模眼";color=0xFFEF7BFF;mark=6},
    {name="规划尾";color=0xFF76A9FF;mark=7}
];

var game={
    level=1;state=STATE_PLAY;pulse=0;elapsed=0;intro=0;
    entities=[];particles=[];shards=[];shocks=[];splashes=[];feathers=[];
    score=0;leaks=0;saved=0;shotId=0;chainCount=0;chainTime=0;
    cooldown=0;stun=0;strike=null;neckWobble=0;screenShake=0;
    status="";statusTime=0;winDelay=0;resultTime=0;
    gagText="";gagTime=0;gagMax=0;gagColor=0xFFFFFFFF;hypeSeen=0;finalCue=0;
    mega=null;megaShotId=-1;hypeShotId=-1;pelicanBlast=0;flash=0;
    muted=false;nextEntityId=0;lineReady=false
};

var CACHE_SCALE=winform.dpiScale(1);

var sceneCache;
var sceneDirty=true;
var stars=[];
for(i=1;92){
    table.push(stars,{x=math.random()*CANVAS_W;y=math.random()*CANVAS_H;size=0.5+math.random()*1.4;alpha=24+math.random(0,70)});
}

//复用字体,避免逐帧反复创建字体对象。Tahoma 会自动转换为 Segoe UI
var FONT_10=gdip.font("Tahoma",10);
var FONT_11=gdip.font("Tahoma",11);
var FONT_12=gdip.font("Tahoma",12);
var FONT_13=gdip.font("Tahoma",13);
var FONT_12B=gdip.font("Tahoma",12,1/*_GdipFontStyleBold*/);
var FONT_14B=gdip.font("Tahoma",14,1);
var FONT_18B=gdip.font("Tahoma",18,1);
var FONT_24B=gdip.font("Tahoma",24,1);
var FONT_32B=gdip.font("Tahoma",32,1);
var FONT_44B=gdip.font("Tahoma",44,1);

//把昂贵的大面积半透明爆发预绘为小型透明精灵;高 DPI 下每帧只缩放一次位图。
import gdip.imageAttributes;
var megaSprite=gdip.bitmap(640,640);
var megaSpriteGraphics=gdip.graphics(megaSprite);
megaSpriteGraphics.clear(0x00000000);megaSpriteGraphics.smoothingMode=4;
var megaAura=gdip.solidBrush(0x2AFFB52E);megaSpriteGraphics.fillCircle(megaAura,320,320,220);megaAura.delete();
var megaCore=gdip.solidBrush(0x76FFFFB4);megaSpriteGraphics.fillCircle(megaCore,320,320,108);megaCore.delete();
for(i=0;7){
    var a=i*math.pi/4;var ray=gdip.pen(0x52FFE887,4);
    megaSpriteGraphics.drawLine(ray,320+math.cos(a)*48,320+math.sin(a)*48,320+math.cos(a)*310,320+math.sin(a)*310);ray.delete();
}
var megaRings=[90,175,270];
for(i,r in megaRings){
    var ring=gdip.pen(i==1 ? 0xC3FFFFFF : 0xC3FFD866,7-i*1.3);
    megaSpriteGraphics.drawCircle(ring,320,320,r);ring.delete();
}
megaSpriteGraphics.delete();megaSpriteGraphics=null;
var megaImageAttributes=gdip.imageAttributes();

import sys.midiOut;
var midiOut=sys.midiOut();
if(midiOut) midiOut.setVelocity(82);

// -------------------- 通用辅助 --------------------
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 levelData=function(){ return levels[game.level]; }

var setStatus=function(text,duration){
    game.status=text;game.statusTime=duration;
}

var setGag=function(text,duration,color){
    game.gagText=text;game.gagTime=duration;game.gagMax=duration;
    game.gagColor=color : 0xFFFFFFFF;
}

var drawText=function(graphics,text,font,x,y,w,h,color,align){
    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();
}

//简谱中的下划线表示短音时值;sys.midiOut 会在时值结束时自动释放音符。
var playNote=function(instrument,note,duration){
    if(midiOut && !game.muted) midiOut.play("changeInstrument("++instrument++"),1_",note,duration);
}

var stageFromRadius=function(radius){
    if(radius>235) return 1;
    if(radius>175) return 2;
    if(radius>112) return 3;
    return 4;
}

var bubbleRadius=function(stage,kind){
    var radius=VERSION_SIZES[math.clamp(stage,1,4)];
    if(kind=="hype") radius+=20;
    elseif(kind=="leak") radius=29;
    return radius;
}

var orbitPosition=function(radius,angle){
    return CX+math.cos(angle)*radius,CY+math.sin(angle)*radius*ORBIT_Y;
}

var lineProjection=function(x,y){
    var ox=x-LINE_X;var oy=y-LINE_Y;
    var along=ox*LINE_DX+oy*LINE_DY;
    var px=LINE_X+LINE_DX*along;var py=LINE_Y+LINE_DY*along;
    return along,math.dist(x,y,px,py);
}

var kindTitle=function(kind){
    select(kind){
        case "hype" { return "HYPE 河豚"; }
        case "jelly" { return "幻觉水母"; }
        case "boot" { return "技术债旧靴子"; }
        case "leak" { return "基准泄漏气泡"; }
    }
    return "模型鱼";
}

var pushLimited=function(array,item,limit){
    table.push(array,item);
    while(#array>limit) table.remove(array,1);
}

// -------------------- 实体与关卡 --------------------
var makeModel=function(index){
    var data=levelData();
    var def=modelDefs[(index-1)%#modelDefs+1];
    var radius=278+((index-1)%3)*12+math.floor((index-1)/3)*5;
    var angle=PHASES[(index-1)%#PHASES+1]+game.level*0.07;
    var urgent=game.level==#levels && index==1;
    //最终关预先埋下一个可救的小模型:HYPE 过线时二者会在同一射线上形成稳定连锁。
    if(urgent){radius=142;angle=0.62;}
    elseif(game.level==#levels && index==7) angle+=0.50; //让 HYPE 保持完整轮廓,不把外圈模型吞进肚子里。
    game.nextEntityId++;
    var x,y=orbitPosition(radius,angle);
    return {
        id=game.nextEntityId;kind="model";state="orbit";name=def.name;color=def.color;mark=def.mark;
        radius=radius;angle=angle;spin=data.spin*(0.94+((index-1)%4)*0.035);direction=1;
        stage=stageFromRadius(radius);r=bubbleRadius(stageFromRadius(radius),"model");x=x;y=y;
        boosted=false;fallAngle=0;savedSlot=0;urgent=urgent
    };
}

var makeSpecial=function(spec,index){
    game.nextEntityId++;
    var kind=spec.kind;
    var radius=235;var angle=0.7+index*1.23;var spin=0.58;var hitRadius=34;
    if(kind=="hype"){radius=262;angle=0.92+game.level*0.13;spin=0.55;}
    elseif(kind=="jelly"){radius=205;angle=1.18+game.level*0.08;spin=-0.62;hitRadius=38;}
    elseif(kind=="boot"){radius=158;angle=4.22+game.level*0.1;spin=0.53;hitRadius=36;}
    elseif(kind=="leak"){radius=198;angle=5.05+game.level*0.07;spin=-0.50;hitRadius=31;}
    var x,y=orbitPosition(radius,angle);
    return {
        id=game.nextEntityId;kind=kind;state="waiting";appearAt=spec.at;
        radius=radius;angle=angle;spin=spin;stage=stageFromRadius(radius);
        r=kind=="hype" ? bubbleRadius(stageFromRadius(radius),kind) : hitRadius;
        x=x;y=y;wobble=0
    };
}

var resetLevel=function(levelIndex){
    //重开/切关时立即清掉尚未结束的事件音,绝不保留背景声。
    if(midiOut) midiOut.reset();
    game.level=math.clamp(levelIndex,1,#levels);
    game.state=STATE_PLAY;game.elapsed=0;game.intro=game.level==1 ? 1.6 : 0;game.resultTime=0;
    game.entities=[];game.particles=[];game.shards=[];game.shocks=[];game.splashes=[];game.feathers=[];
    game.score=0;game.leaks=0;game.saved=0;game.shotId=0;game.chainCount=0;game.chainTime=0;
    game.cooldown=0;game.stun=0;game.strike=null;game.neckWobble=0;game.screenShake=0;
    game.status="";game.statusTime=0;game.winDelay=0;game.nextEntityId=0;game.lineReady=false;
    game.gagText="";game.gagTime=0;game.gagMax=0;game.finalCue=0;
    game.mega=null;game.megaShotId=-1;game.hypeShotId=-1;game.pelicanBlast=0;game.flash=0;
    var data=levelData();
    for(i=1;data.fish) table.push(game.entities,makeModel(i));
    for(i,spec in data.specials) table.push(game.entities,makeSpecial(spec,i));
    sceneDirty=true;
}

var activeModelCount=function(){
    var count=0;
    for(i,e in game.entities) if(e.kind=="model" && e.state=="orbit") count++;
    return count;
}

var checkClear=function(){
    if(game.state==STATE_PLAY && activeModelCount()==0 && game.winDelay<=0) game.winDelay=0.82;
}

// -------------------- 短寿命反馈 --------------------
var spawnDots=function(x,y,color,count,speed,size){
    for(i=1;count){
        var angle=math.random()*math.pi*2;
        var velocity=speed*(0.35+math.random()*0.65);
        pushLimited(game.particles,{
            x=x;y=y;vx=math.cos(angle)*velocity;vy=math.sin(angle)*velocity;
            life=0.38+math.random()*0.38;maxLife=0.76;size=size*(0.55+math.random()*0.75);
            color=color;gravity=80+math.random()*140
        },260);
    }
}

var spawnVersionShards=function(text,x,y,color){
    var chars=string.split(text);
    for(i,ch in chars){
        pushLimited(game.shards,{
            text=ch;x=x+(i-(#chars+1)/2)*7;y=y;vx=(i-(#chars+1)/2)*32+math.random(-18,18);
            vy=-95-math.random()*50;life=0.72;maxLife=0.72;color=color;spin=math.random(-160,160);angle=0
        },60);
    }
}

var spawnSplash=function(x,color){
    pushLimited(game.splashes,{x=x;y=654;life=0.55;maxLife=0.55;color=color},24);
    for(i=1;7){
        pushLimited(game.particles,{
            x=x;y=654;vx=math.random(-70,70);vy=math.random(-180,-65);
            life=0.48+math.random()*0.25;maxLife=0.72;size=2+math.random()*2.3;
            color=0xFF66DDFC;gravity=360
        },260);
    }
}

var spawnFeathers=function(x,y,count){
    for(i=1;count){
        pushLimited(game.feathers,{
            x=x+math.random(-28,28);y=y+math.random(-34,10);
            vx=math.random(-150,125);vy=math.random(-245,-105);
            angle=math.random()*360;spin=math.random(-260,260);
            life=0.85+math.random()*0.55;maxLife=1.4;size=8+math.random()*6
        },22);
    }
}

var triggerMegaChain=function(x,y){
    if(game.megaShotId==game.shotId) return;
    game.megaShotId=game.shotId;
    game.mega={x=x;y=y;life=1.05;maxLife=1.05};
    game.pelicanBlast=1.18;game.flash=1.15;
    game.screenShake=math.max(game.screenShake,3.2);
    game.cooldown=math.max(game.cooldown,0.86);
    spawnFeathers(135,610,10);
    setGag(game.level==#levels ? "大的真来了!" : "这动静不对!",1.08,0xFFFFE98A);
    setStatus("一嘴核爆级连锁——鹈鹕当场惊跌坐!",1.4);
    if(game.level==#levels) game.finalCue=3;
    playNote(47,43,145);
}

var burstEntity=function(entity,depth,shotId){
    if(!entity || entity.state!="orbit") return false;
    var x=entity.x;var y=entity.y;var stage=entity.stage : 1;
    var color=entity.kind=="leak" ? 0xFF62F4D4 : (entity.color : VERSION_COLORS[stage]);
    entity.state=entity.kind=="model" ? "falling" : "gone";

    var shockMax=70+stage*5;
    if(entity.kind=="hype") shockMax=168;
    elseif(entity.kind=="leak") shockMax=52;
    table.push(game.shocks,{x=x;y=y;r=4;max=shockMax;age=0;delay=depth*0.045;duration=0.34;depth=depth;source=entity.id;shotId=shotId;hit={}});

    spawnDots(x,y,color,entity.kind=="hype" ? 30 : 15,entity.kind=="hype" ? 245 : 165,entity.kind=="hype" ? 5 : 3.6);
    if(entity.kind=="model"){
        spawnVersionShards(VERSION_LABELS[stage],x,y-8,VERSION_COLORS[stage]);
        entity.vx=(x-CX)*0.18+math.random(-65,65);
        entity.vy=-105-math.random()*65;
        entity.fallAngle=math.random(-25,25);
        game.saved++;
        game.score+=100*stage+depth*45;
    }
    elseif(entity.kind=="hype"){
        spawnVersionShards("HYPE",x,y-6,0xFFFFD65B);
        game.score+=500;game.hypeShotId=shotId;
        setStatus("HYPE 爆开:超大范围连锁冲击!",1.15);
    }
    elseif(entity.kind=="leak"){
        spawnVersionShards("LEAK",x,y-5,0xFF62F4D4);
        game.score+=280;
        setStatus("基准泄漏已封堵,附近版本恢复常速",1.15);
    }

    if(shotId==game.shotId){
        game.chainCount++;
        game.chainTime=1.0;
        //普通四连或 HYPE 带出的任意连锁,都会进入一次短促的招牌爆发镜头。
        if(game.chainCount>=4 || (game.chainCount>=2 && game.hypeShotId==shotId)) triggerMegaChain(x,y);
    }
    game.screenShake=math.max(game.screenShake,entity.kind=="hype" ? 1 : 0.42);
    playNote(depth>=2 ? 115 : 114,60+math.min(depth,5)*4+(stage-1)*2,70+depth*12);
    checkClear();
    return true;
}

var captureModel=function(entity){
    if(entity.state!="orbit") return;
    entity.state="captured";entity.captureAge=0;
    game.leaks++;
    game.screenShake=1;
    spawnDots(entity.x,entity.y,0xFFFF6FAD,22,175,4);
    playNote(55,42,210);
    if(!RELAXED_AGI_MODE || game.leaks>=3){
        game.state=STATE_FAILED;game.resultTime=0;game.strike=null;
        setStatus("发布搞错了:把炼废版上传了!",2);
        setGag("上传错了!",1.15,0xFFFF7FAB);
        playNote(13,48,150);
    }
    else{
        var letter=(['A','G','I'])[game.leaks];
        setStatus((game.leaks==1 ? "炼废了!" : "又炼废了!")++" "++letter++" 已点亮",1.35);
        checkClear();
    }
}

// -------------------- 测试线命中 --------------------
var findFirstLineHit=function(){
    var found;var best=100000;var bestPerp;
    for(i,e in game.entities){
        if(e.state=="orbit"){
            var hitRadius=e.r;
            var along,perp=lineProjection(e.x,e.y);
            if(along>=0 && along<=LINE_LEN && perp<=hitRadius+2 && along<best){
                found=e;best=along;bestPerp=perp;
            }
        }
    }
    return found,best,bestPerp;
}

var beginStrike=function(){
    if(game.state==STATE_WIN){
        resetLevel(game.level==#levels ? 1 : game.level+1);
        return;
    }
    if(game.state==STATE_FAILED){
        resetLevel(game.level);
        return;
    }
    if(game.state!=STATE_PLAY || game.cooldown>0 || game.stun>0) return;

    game.shotId++;
    game.chainCount=0;game.chainTime=0;
    var hit,along=findFirstLineHit();
    var strikeLength=hit ? math.clamp(along,115,LINE_LEN) : LINE_LEN;
    game.strike={age=0;target=strikeLength;length=72;hit=hit ? hit.kind : "miss"};
    game.cooldown=0.34;game.neckWobble=1;

    if(!hit){
        setStatus("落空——别追泡泡,等它自己穿过 TEST LINE",0.9);
        spawnDots(LINE_END_X,LINE_END_Y,0xFF7FCBE5,7,80,2.4);
        playNote(73,48,65);
        return;
    }

    if(hit.kind=="model" || hit.kind=="hype" || hit.kind=="leak"){
        burstEntity(hit,0,game.shotId);
    }
    elseif(hit.kind=="jelly"){
        hit.state="gone";
        setStatus("戳破幻觉:看起来很强的结果当场蒸发",1.05);
        setGag("过拟合到出现幻觉",0.82,0xFFDAB9FF);
        spawnDots(hit.x,hit.y,0xFFB98CFF,18,145,3.4);
        spawnVersionShards("幻觉",hit.x,hit.y,0xFFD5C3FF);
        playNote(10,69,90);
    }
    elseif(hit.kind=="boot"){
        hit.state="gone";game.stun=0.82;
        setStatus("技术债被一嘴撞飞——长嘴仍麻 0.8 秒",1.15);
        setGag("技术债反弹!",0.76,0xFFFFC778);
        spawnDots(hit.x,hit.y,0xFFFFB55E,22,175,3.7);
        spawnVersionShards("债",hit.x,hit.y,0xFFFFC46E);
        playNote(115,48,95);
    }
}

// -------------------- 每帧更新 --------------------
var updateParticles=function(dt){
    for(i=#game.particles;1;-1){
        var p=game.particles[i];
        p.life-=dt;
        if(p.life<=0) table.remove(game.particles,i);
        else{
            p.vy+=p.gravity*dt;p.x+=p.vx*dt;p.y+=p.vy*dt;
            p.vx*=math.max(0,1-dt*1.7);
        }
    }
    for(i=#game.shards;1;-1){
        var s=game.shards[i];s.life-=dt;
        if(s.life<=0) table.remove(game.shards,i);
        else{s.vy+=240*dt;s.x+=s.vx*dt;s.y+=s.vy*dt;s.angle+=s.spin*dt;}
    }
    for(i=#game.splashes;1;-1){
        game.splashes[i].life-=dt;
        if(game.splashes[i].life<=0) table.remove(game.splashes,i);
    }
    for(i=#game.feathers;1;-1){
        var f=game.feathers[i];f.life-=dt;
        if(f.life<=0) table.remove(game.feathers,i);
        else{
            f.vy+=255*dt;f.x+=f.vx*dt;f.y+=f.vy*dt;f.angle+=f.spin*dt;
            f.vx*=math.max(0,1-dt*0.9);
        }
    }
}

var updateFalling=function(dt){
    for(i,e in game.entities){
        if(e.state=="falling"){
            e.vy+=520*dt;e.x+=e.vx*dt;e.y+=e.vy*dt;e.fallAngle+=170*dt;
            e.vx*=math.max(0,1-dt*0.65);
            if(e.y>=657){
                e.state="saved";e.savedSlot=game.saved;e.y=672;e.x=math.clamp(e.x,335,1010);
                spawnSplash(e.x,e.color);
                playNote(74,52+(e.savedSlot%5)*2,70);
            }
        }
        elseif(e.state=="captured"){
            e.captureAge+=dt;
            var t=math.clamp(e.captureAge/0.42,0,1);
            e.x=math.lerp(e.x,CX,t*0.18);e.y=math.lerp(e.y,CY,t*0.18);
            if(t>=1) e.state="gone";
        }
    }
}

var updateStrike=function(dt){
    if(!game.strike) return;
    var strike=game.strike;
    strike.age+=dt;
    if(strike.age<0.065){
        var t=strike.age/0.065;
        strike.length=72+(strike.target-72)*(1-(1-t)*(1-t)*(1-t));
    }
    elseif(strike.age<0.125) strike.length=strike.target;
    elseif(strike.age<0.34){
        var t=(strike.age-0.125)/0.215;
        strike.length=72+(strike.target-72)*(1-t)*(1-t);
    }
    else game.strike=null;
}

var updateShocks=function(dt){
    for(i=#game.shocks;1;-1){
        var shock=game.shocks[i];shock.age+=dt;
        var activeAge=shock.age-shock.delay;
        if(activeAge>=0){
            var t=math.clamp(activeAge/shock.duration,0,1);
            shock.r=shock.max*(1-(1-t)*(1-t));
            //depth < 0 仅用于版本升级的装饰环,绝不能误触发连锁爆破。
            if(shock.depth>=0){
                for(j,e in game.entities){
                    if(e.state=="orbit" && e.id!=shock.source && !shock.hit[e.id]
                        && (e.kind=="model" || e.kind=="hype" || e.kind=="leak") ){
                        if(math.dist(shock.x,shock.y,e.x,e.y)<=shock.r+e.r){
                            shock.hit[e.id]=true;
                            burstEntity(e,shock.depth+1,shock.shotId);
                        }
                    }
                }
            }
        }
        if(activeAge>shock.duration) table.remove(game.shocks,i);
    }
}

var activateSpecials=function(){
    for(i,e in game.entities){
        if(e.state=="waiting" && game.elapsed>=e.appearAt){
            e.state="orbit";
            setStatus(kindTitle(e.kind)++" 进入本轮测试",1.05);
            spawnDots(e.x,e.y,e.kind=="boot" ? 0xFFFFB05C : 0xFF90E8FF,12,105,3);
            if(e.kind=="hype"){
                game.hypeSeen++;
                if(game.level==#levels){
                    setGag("大的要来了……",1.02,0xFFFFE589);game.finalCue=1;
                }
                else setGag(game.hypeSeen==1 ? "大的要来了……" : "大的又要来了!",0.92,0xFFFFE589);
            }
            //自动入场保持安静,音效只服务玩家操作和明确结果。
        }
    }
}

var updateOrbiting=function(dt){
    //先更新行业现象,使 LEAK 的位置用于同一帧加速检测。
    for(i,e in game.entities){
        if(e.state=="orbit" && e.kind!="model" && e.kind!="hype"){
            e.angle+=e.spin*dt;
            e.x,e.y=orbitPosition(e.radius,e.angle);
            e.wobble=math.max(0,e.wobble-dt*3.4);
        }
    }

    var leakEntity;
    for(i,e in game.entities) if(e.kind=="leak" && e.state=="orbit") leakEntity=e;
    var data=levelData();
    for(i,e in game.entities){
        if(e.state=="orbit" && (e.kind=="model" || e.kind=="hype")){
            e.boosted=false;
            var boost=1;
            if(e.kind=="model" && leakEntity && math.dist(e.x,e.y,leakEntity.x,leakEntity.y)<175){
                boost=2.55;e.boosted=true;
            }
            var oldStage=e.stage;
            e.stage=stageFromRadius(e.radius);
            var versionSpeed=1+(e.stage-1)*0.17;
            var inward=data.inward*(e.kind=="hype" ? 0.72 : 1)*(1+(e.stage-1)*0.12)*boost;
            e.radius-=inward*dt;
            e.angle+=e.spin*versionSpeed*boost*dt;
            e.x,e.y=orbitPosition(e.radius,e.angle);
            e.r=bubbleRadius(e.stage,e.kind);
            var newStage=stageFromRadius(e.radius);
            if(newStage>oldStage){
                e.stage=newStage;e.r=bubbleRadius(newStage,e.kind);
                spawnDots(e.x,e.y,VERSION_COLORS[newStage],10,95,2.8);
                pushLimited(game.shocks,{x=e.x;y=e.y;r=3;max=e.r+17;age=0;delay=0;duration=0.28;depth=-99;source=e.id;shotId=-1;hit={}},40);
                if(e.kind=="model") setStatus(e.name++" 升级至 "++VERSION_LABELS[newStage],0.78);
            }
            if(e.radius<=CAPTURE_RADIUS){
                if(e.kind=="model") captureModel(e);
                else e.state="gone";
            }
        }
    }
}

var updateStoryCues=function(){
    if(game.level==#levels && game.finalCue==1){
        for(i,e in game.entities){
            if(e.kind=="model" && e.state=="orbit" && e.urgent && e.radius<120){
                setGag("小的要去了!",0.92,0xFFFF91BE);game.finalCue=2;
                break;
            }
        }
    }
}

var updateGame=function(dt){
    game.elapsed+=dt;
    activateSpecials();
    updateOrbiting(dt);
    updateStoryCues();
    updateShocks(dt);
    checkClear();
    if(game.winDelay>0){
        game.winDelay-=dt;
        if(game.winDelay<=0 && game.state==STATE_PLAY){
            game.state=STATE_WIN;game.resultTime=0;game.strike=null;
            setStatus("全部返回训练池——今天仍未 AGI 。",2);
            playNote(14,76,180);
        }
    }
}

// -------------------- 静态场景缓存 --------------------
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),0xFF102D42,0xFF070A18);
    graphics.fillRect(bg,0,0,CANVAS_W,CANVAS_H);bg.delete();
    var haze=gdip.solidBrush(0x261FA3B8);
    graphics.fillEllipse(haze,270,-160,760,650);haze.delete();
    for(i,s in stars){
        var brush=gdip.solidBrush(alphaColor(0xFFD8F5FF,s.alpha));
        graphics.fillCircle(brush,s.x,s.y,s.size);brush.delete();
    }

    var top=gdip.solidBrush(0x7A06111F);
    graphics.fillRect(top,0,0,CANVAS_W,92);top.delete();
    drawText(graphics,"鹈 鹕 之 门",FONT_24B,26,8,280,38,0xFFF3FBFF,0);
    drawText(graphics,"PELICAN GATE · AGI BENCHMARK CONTROL",FONT_10,28,45,360,18,0x9BC3E8F4,0);
    drawText(graphics,"目标经过 TEST LINE 时点击任意位置 · 越晚越容易连爆,也越接近 AGI",FONT_13,28,68,720,20,0xD7D6F4FA,0);

    var data=levelData();
    drawText(graphics,"第 "++game.level++" / "++#levels++" 关",FONT_11,790,15,120,22,0xAFC9DDE8,0);
    drawText(graphics,data.name,FONT_18B,790,36,270,30,data.accent,0);
    drawText(graphics,data.subtitle,FONT_13,790,68,300,20,0xBFD8E7EE,0);

    //升级环:与模型鱼实际使用的椭圆轨道完全一致。
    var ringRadii=[300,235,175,112,64];
    for(i,r in ringRadii){
        var alpha=i==#ringRadii ? 92 : 42+i*7;
        var pen=gdip.pen(alphaColor(data.accent,alpha),i==#ringRadii ? 2 : 1);
        graphics.drawEllipse(pen,CX-r,CY-r*ORBIT_Y,r*2,r*2*ORBIT_Y);pen.delete();
    }
    var radial=gdip.pen(0x173AD0E7,1);
    for(i=0;11){
        var a=i*math.pi/6;
        graphics.drawLine(radial,CX+math.cos(a)*66,CY+math.sin(a)*66*ORBIT_Y,CX+math.cos(a)*302,CY+math.sin(a)*302*ORBIT_Y);
    }
    radial.delete();

    //把版本说明移到左侧信息区,避免任何静态标签盖住动态泡泡。
    drawText(graphics,"VERSION PATH",FONT_10,26,188,100,17,0x8397C9D8,0);
    drawText(graphics,"1.2  ›  2.5  ›  3.0  ›  AGI?",FONT_12,26,205,248,22,0xCDE5F6FA,0);

    //淡淡但清楚的基准测试线由底层辉光和短线段组成;点击位置不参与瞄准。
    var lineGlow=gdip.pen(0x3576E8FF,8);
    graphics.drawLine(lineGlow,LINE_X,LINE_Y,LINE_END_X,LINE_END_Y);lineGlow.delete();
    for(distance=8;distance<LINE_LEN;distance+=24){
        var a=math.min(190,95+distance*0.12);
        var pen=gdip.pen(alphaColor(0xFF76E8FF,a),2);
        graphics.drawLine(pen,LINE_X+LINE_DX*distance,LINE_Y+LINE_DY*distance,
            LINE_X+LINE_DX*(distance+13),LINE_Y+LINE_DY*(distance+13));
        pen.delete();
    }
    drawText(graphics,"BENCHMARK  TEST  LINE",FONT_10,246,524,210,18,0xA85ECFE7,0);

    //训练池位于前景底部,所有被释放的普通模型鱼都落回这里。
    var poolShadow=gdip.solidBrush(0x66000000);
    graphics.fillRoundRect(poolShadow,292,630,744,80,26);poolShadow.delete();
    var pool=gdip.lineBrush(::POINTF(300,636),::POINTF(1018,704),0xC515617A,0xEA082A48);
    graphics.fillRoundRect(pool,300,632,728,76,24);pool.delete();
    var poolEdge=gdip.pen(0xB45CE8FF,2);
    graphics.drawArc(poolEdge,312,622,704,38,184,172);poolEdge.delete();
    drawText(graphics,"T R A I N I N G",FONT_14B,685,674,250,24,0x836BE5F7,1);
    drawText(graphics,"模型鱼返回训练池",FONT_12,838,651,170,17,0x7CCBEAF2,1);

    graphics.delete();sceneDirty=false;
}

// -------------------- 动态绘制 --------------------
var drawVortex=function(graphics){
    var failed=game.state==STATE_FAILED;
    var baseColor=failed ? 0xFFFF4A8E : levelData().accent;
    var pulse=math.sin(game.pulse/125);
    if(failed){
        var spot=gdip.solidBrush(0x24FF5C9A);
        graphics.fillPolygon(spot,[[430,92],[520,92],[CX+34,CY+12],[CX-24,CY+10]]);
        graphics.fillPolygon(spot,[[760,92],[850,92],[CX+26,CY+14],[CX-20,CY+8]]);spot.delete();
    }
    for(i=5;1;-1){
        var radius=28+i*12+3*pulse;
        var glow=gdip.solidBrush(alphaColor(baseColor,8+i*5));
        graphics.fillCircle(glow,CX,CY,radius);glow.delete();
    }
    for(i=1;4){
        var r=24+i*11;
        var pen=gdip.pen(alphaColor(baseColor,105+i*24),1.2+i*0.65);
        var start=(i%2 ? 1 : -1)*game.pulse/(8+i*2)+i*41;
        graphics.drawArc(pen,CX-r,CY-r,r*2,r*2,start,205+i*18);pen.delete();
    }
    var core=gdip.solidBrush(failed ? 0xFF3B061C : 0xFF050613);
    graphics.fillCircle(core,CX,CY,24+2*pulse);core.delete();
    var dot=gdip.solidBrush(alphaColor(0xFFFFFFFF,170+60*pulse));
    graphics.fillCircle(dot,CX-5,CY-5,4);dot.delete();
    drawText(graphics,"AGI?",FONT_14B,CX-38,CY-13,76,26,failed ? 0xFFFFD4E4 : 0xFFEAFBFF,1);
}

var drawActiveLine=function(graphics){
    if(!game.lineReady || game.state!=STATE_PLAY) return;
    var hit,along=findFirstLineHit();
    if(!hit) return;
    var alpha=135+75*math.sin(game.pulse/52);
    var glow=gdip.pen(alphaColor(0xFF8CFAFF,alpha*0.28),10);
    var core=gdip.pen(alphaColor(0xFFDFFFFF,alpha),2.4);
    var endX=LINE_X+LINE_DX*along;var endY=LINE_Y+LINE_DY*along;
    graphics.drawLine(glow,LINE_X,LINE_Y,endX,endY);
    graphics.drawLine(core,LINE_X,LINE_Y,endX,endY);
    graphics.drawCircle(core,endX,endY,hit.r+5+2*math.sin(game.pulse/55));
    core.delete();glow.delete();
}

var drawPoolWater=function(graphics){
    var pen=gdip.pen(0x6969E9FF,1.5);
    if(game.mega){
        //爆发波纹留在训练池后方,避免圆环压过水面与 TRAINING 字样。
        var cover=gdip.solidBrush(0xF0082A48);graphics.fillRoundRect(cover,300,632,728,76,24);cover.delete();
    }
    var offset=(game.pulse/18)%42;
    for(x=310-offset;x<1020;x+=42){
        graphics.drawArc(pen,x,641,34,11,190,150);
    }
    pen.delete();
    var shine=gdip.pen(0x3267F4FF,5);
    graphics.drawArc(shine,315,625,697,36,188,164);shine.delete();
}

var drawFishShape=function(graphics,color,mark,scale,overfit){
    var body=gdip.solidBrush(color);
    var dark=gdip.solidBrush(shadeColor(color,-65));
    var light=gdip.solidBrush(shadeColor(color,40));
    graphics.fillPolygon(dark,[[-17*scale,0],[-29*scale,-10*scale],[-27*scale,10*scale]]);
    if(overfit){
        //同一条鱼长出过多装饰鳍:不增加新实体,也能一眼看出“训练集卷满了”。
        graphics.fillPolygon(light,[[-13*scale,-7*scale],[-11*scale,-20*scale],[-4*scale,-8*scale]]);
        graphics.fillPolygon(light,[[0,-8*scale],[4*scale,-22*scale],[9*scale,-7*scale]]);
        graphics.fillPolygon(light,[[7*scale,7*scale],[13*scale,18*scale],[14*scale,5*scale]]);
    }
    graphics.fillEllipse(body,-18*scale,-10*scale,36*scale,20*scale);
    graphics.fillPolygon(light,[[-4*scale,-7*scale],[5*scale,-17*scale],[10*scale,-6*scale]]);
    if(mark%3==1) graphics.fillCircle(light,-2*scale,1*scale,3.3*scale);
    elseif(mark%3==2) graphics.fillRect(light,-7*scale,-2*scale,12*scale,4*scale);
    else graphics.fillPolygon(light,[[-8*scale,6*scale],[0,0],[8*scale,7*scale]]);
    if(overfit){
        var medal=gdip.solidBrush(0xFFFFD45B);
        graphics.fillCircle(medal,-3*scale,2*scale,3.2*scale);medal.delete();
    }
    var eyeWhite=gdip.solidBrush(0xFFF7FFFF);var pupil=gdip.solidBrush(0xFF07131E);
    graphics.fillCircle(eyeWhite,10*scale,-3*scale,3.6*scale);
    graphics.fillCircle(pupil,11*scale,-3*scale,1.7*scale);
    pupil.delete();eyeWhite.delete();light.delete();dark.delete();body.delete();
}

var entityLabelClear=function(entity){
    for(i,other in game.entities){
        if(other.id!=entity.id && other.state=="orbit" && math.dist(entity.x,entity.y,other.x,other.y)<entity.r+other.r+42) return false;
    }
    return true;
}

var drawModelBubble=function(graphics,e){
    var stage=e.stage;var radius=e.r;var color=VERSION_COLORS[stage];
    var charred=stage==4 && e.radius<88;
    if(e.boosted){
        var boost=gdip.pen(alphaColor(0xFF5DFFD4,115+60*math.sin(game.pulse/80)),5);
        graphics.drawCircle(boost,e.x,e.y,radius+8);boost.delete();
    }
    if(e.urgent){
        var warning=gdip.pen(alphaColor(0xFFFF76A9,135+80*math.sin(game.pulse/55)),3);
        graphics.drawArc(warning,e.x-radius-10,e.y-radius-10,(radius+10)*2,(radius+10)*2,205,58);
        graphics.drawArc(warning,e.x-radius-10,e.y-radius-10,(radius+10)*2,(radius+10)*2,25,58);warning.delete();
    }
    var glow=gdip.pen(alphaColor(color,42+stage*11),8+stage*2);
    var outline=gdip.pen(alphaColor(color,205),1.7+stage*0.25);
    var fill=gdip.solidBrush(alphaColor(color,30+stage*7));
    graphics.fillCircle(fill,e.x,e.y,radius);
    graphics.drawCircle(glow,e.x,e.y,radius+1);
    graphics.drawCircle(outline,e.x,e.y,radius);
    fill.delete();outline.delete();glow.delete();
    var highlight=gdip.pen(0xB8FFFFFF,2.3);
    graphics.drawArc(highlight,e.x-radius+5,e.y-radius+5,(radius-5)*2,(radius-5)*2,205,72);highlight.delete();

    if(charred){
        for(i=0;2){
            var smoke=gdip.solidBrush(alphaColor(0xFF8CA0A8,90-i*20));
            graphics.fillCircle(smoke,e.x-9+i*9+math.sin(game.pulse/120+i)*4,e.y-radius-8-i*8,7-i);smoke.delete();
        }
    }
    var tangentX=-math.sin(e.angle);
    var tangentY=math.cos(e.angle)*ORBIT_Y;
    var angle=math.deg(math.atan2(tangentY,tangentX));
    var saved=graphics.save();
    graphics.translate(e.x,e.y+5);
    graphics.rotate(angle);
    drawFishShape(graphics,charred ? shadeColor(e.color,-95) : e.color,e.mark,0.72+stage*0.08,stage==4);
    graphics.restore(saved);

    drawText(graphics,VERSION_LABELS[stage],stage==4 ? FONT_12B : FONT_12,e.x-radius,e.y-radius+3,radius*2,20,0xF7FFFFFF,1);
    if(stage==4) drawText(graphics,charred ? "炼废中" : "训练集 100%",FONT_10,e.x-radius,e.y+radius-20,radius*2,16,charred ? 0xFFFFA2A2 : 0xFFFFE7A0,1);
    if(entityLabelClear(e)) drawText(graphics,e.name,FONT_10,e.x-radius-8,e.y+radius+9,radius*2+16,17,alphaColor(e.color,215),1);
}

var drawHype=function(graphics,e){
    var r=e.r;var pulse=2*math.sin(game.pulse/85);
    var fill=gdip.solidBrush(0x42FFD34F);var glow=gdip.pen(0x85FFD95F,11+pulse);var edge=gdip.pen(0xEFFFF1A3,2.3);
    graphics.fillCircle(fill,e.x,e.y,r);graphics.drawCircle(glow,e.x,e.y,r+2);graphics.drawCircle(edge,e.x,e.y,r);
    edge.delete();glow.delete();fill.delete();
    var puffer=gdip.solidBrush(0xFFFFC94C);var dark=gdip.solidBrush(0xFF9C5D23);
    for(i=0;11){
        var a=i*math.pi/6;
        var x1=e.x+math.cos(a)*20;var y1=e.y+math.sin(a)*20;
        graphics.fillPolygon(puffer,[[x1+math.cos(a-0.22)*8,y1+math.sin(a-0.22)*8],[e.x+math.cos(a)*29,e.y+math.sin(a)*29],[x1+math.cos(a+0.22)*8,y1+math.sin(a+0.22)*8]]);
    }
    graphics.fillCircle(puffer,e.x,e.y,22);
    graphics.fillCircle(dark,e.x+8,e.y-5,2.4);graphics.fillCircle(dark,e.x-7,e.y-5,2.4);
    dark.delete();puffer.delete();
    drawText(graphics,"HYPE",FONT_12B,e.x-r,e.y+7,r*2,22,0xFF5E3515,1);
    drawText(graphics,"连锁",FONT_11,e.x-r,e.y+26,r*2,17,0xFF321B08,1);
}

var drawJelly=function(graphics,e){
    var wobble=e.wobble*6+math.sin(game.pulse/120+e.id)*2;
    var glow=gdip.pen(0x527E9CFF,10);var edge=gdip.pen(0xC5C6B2FF,2);
    var fill=gdip.solidBrush(0x4F9A80FF);
    graphics.fillPie(fill,e.x-31,e.y-25,62,50,180,180);
    graphics.drawArc(glow,e.x-33,e.y-27,66,54,180,180);
    graphics.drawArc(edge,e.x-31,e.y-25,62,50,180,180);
    var tentacle=gdip.pen(0x9DB8A5FF,2.2);
    for(i=-2;2){
        graphics.drawBezier(tentacle,e.x+i*10,e.y,e.x+i*12+wobble,e.y+13,e.x+i*5-wobble,e.y+21,e.x+i*9,e.y+33);
    }
    tentacle.delete();fill.delete();edge.delete();glow.delete();
    drawText(graphics,"幻觉",FONT_11,e.x-31,e.y-20,62,20,0xE9F0E9FF,1);
}

var drawBoot=function(graphics,e){
    var shake=e.wobble*math.sin(game.pulse/18)*5;
    var saved=graphics.save();graphics.translate(e.x+shake,e.y);graphics.rotate(-12+e.wobble*9);
    var glow=gdip.pen(0x4BFF9E50,10);var edge=gdip.pen(0xE8D79A5B,2);
    var leather=gdip.solidBrush(0xFF70401F);var sole=gdip.solidBrush(0xFF281B16);
    var points=[[-18,-28],[9,-28],[7,4],[20,14],[34,17],[32,28],[-11,28],[-20,17]];
    graphics.drawPolygon(glow,points);graphics.fillPolygon(leather,points);graphics.drawPolygon(edge,points);
    graphics.fillRoundRect(sole,-15,22,49,9,4);
    var patch=gdip.pen(0xFFE6B06C,1.4);graphics.drawLine(patch,-9,-12,5,-4);graphics.drawLine(patch,-9,-5,5,3);
    patch.delete();sole.delete();leather.delete();edge.delete();glow.delete();graphics.restore(saved);
    drawText(graphics,"技术债",FONT_10,e.x-34,e.y+31,68,17,0xEFD9A15E,1);
}

var drawLeak=function(graphics,e){
    var r=e.r;var pulse=2*math.sin(game.pulse/75);
    var glow=gdip.pen(0x654CFFD2,9+pulse);var fill=gdip.solidBrush(0x3B3BFFD0);var edge=gdip.pen(0xDD91FFE5,2);
    graphics.fillCircle(fill,e.x,e.y,r);graphics.drawCircle(glow,e.x,e.y,r+1);graphics.drawCircle(edge,e.x,e.y,r);
    var crack=gdip.pen(0xF0E9FFFF,2);
    graphics.drawLines(crack,[[e.x-5,e.y-r+7],[e.x+2,e.y-8],[e.x-4,e.y+2],[e.x+7,e.y+11],[e.x+2,e.y+r-7]]);
    crack.delete();edge.delete();fill.delete();glow.delete();
    drawText(graphics,"LEAK",FONT_11,e.x-r,e.y-9,r*2,18,0xFFF0FFFF,1);
}

var drawEntities=function(graphics){
    for(i,e in game.entities){
        if(e.state=="orbit"){
            if(e.kind=="model") drawModelBubble(graphics,e);
            elseif(e.kind=="hype") drawHype(graphics,e);
            elseif(e.kind=="jelly") drawJelly(graphics,e);
            elseif(e.kind=="boot") drawBoot(graphics,e);
            elseif(e.kind=="leak") drawLeak(graphics,e);
        }
        elseif(e.state=="falling" || e.state=="captured"){
            var saved=graphics.save();graphics.translate(e.x,e.y);graphics.rotate(e.fallAngle : 0);
            drawFishShape(graphics,e.color,e.mark,0.92);graphics.restore(saved);
        }
        elseif(e.state=="saved"){
            var swimX=e.x+math.sin(game.pulse/420+e.id)*14;
            var swimY=672+math.sin(game.pulse/260+e.id*2)*4;
            var saved=graphics.save();graphics.translate(swimX,swimY);drawFishShape(graphics,e.color,e.mark,0.68);graphics.restore(saved);
        }
    }
}

var drawEffects=function(graphics){
    for(i,shock in game.shocks){
        if(shock.age>=shock.delay && shock.depth>=0){
            var t=math.clamp((shock.age-shock.delay)/shock.duration,0,1);
            var pen=gdip.pen(alphaColor(shock.depth==0 ? 0xFFFFFFFF : 0xFFFFD86B,210*(1-t)),2.2+3*(1-t));
            graphics.drawCircle(pen,shock.x,shock.y,shock.r);pen.delete();
        }
    }
    for(i,p in game.particles){
        var alpha=255*math.clamp(p.life/p.maxLife,0,1);
        var brush=gdip.solidBrush(alphaColor(p.color,alpha));
        graphics.fillCircle(brush,p.x,p.y,p.size*(0.5+0.5*p.life/p.maxLife));brush.delete();
    }
    for(i,s in game.shards){
        var alpha=255*math.clamp(s.life/s.maxLife,0,1);
        drawText(graphics,s.text,FONT_12B,s.x-12,s.y-10,24,20,alphaColor(s.color,alpha),1);
    }
    for(i,s in game.splashes){
        var t=1-s.life/s.maxLife;
        var pen=gdip.pen(alphaColor(s.color,180*(1-t)),2.2);
        graphics.drawArc(pen,s.x-10-t*28,s.y-5-t*6,20+t*56,10+t*13,195,150);pen.delete();
    }
    for(i,f in game.feathers){
        var saved=graphics.save();graphics.translate(f.x,f.y);graphics.rotate(f.angle);
        var alpha=230*math.clamp(f.life/f.maxLife,0,1);
        var brush=gdip.solidBrush(alphaColor(0xFFF4F1E8,alpha));
        var outline=gdip.pen(alphaColor(0xFF526873,alpha),1.2);
        var shaft=gdip.pen(alphaColor(0xFF9DB0B9,alpha),1.2);
        graphics.fillEllipse(brush,-f.size,-f.size*0.34,f.size*2,f.size*0.68);
        graphics.drawEllipse(outline,-f.size,-f.size*0.34,f.size*2,f.size*0.68);
        graphics.drawLine(shaft,-f.size*0.8,0,f.size*0.9,0);
        shaft.delete();outline.delete();brush.delete();graphics.restore(saved);
    }
}

var pelicanMood=function(){
    var minRadius=999;var hasHype=false;var closePairs=0;
    for(i=1;#game.entities){
        var a=game.entities[i];
        if(a.state=="orbit" && a.kind=="hype") hasHype=true;
        if(a.state=="orbit" && a.kind=="model"){
            minRadius=math.min(minRadius,a.radius);
            for(j=i+1;#game.entities){
                var b=game.entities[j];
                if(b.state=="orbit" && b.kind=="model" && math.dist(a.x,a.y,b.x,b.y)<a.r+b.r+72) closePairs++;
            }
        }
    }
    return hasHype || closePairs>=2,minRadius<112;
}

var drawPelican=function(graphics){
    var drool,panic=pelicanMood();
    var blasted=game.pelicanBlast>0;
    var poseSaved=graphics.save();
    if(blasted){
        var age=1-game.pelicanBlast/1.18;
        var pose=age<0.16 ? age/0.16 : (age<0.78 ? 1 : math.max(0,(1-age)/0.22));
        graphics.translate(130,640);graphics.rotate(-24*pose);graphics.translate(-130,-640);
        graphics.translate(-8*pose,14*pose);
    }
    var wobble=game.neckWobble*math.sin(game.pulse/32)*5;
    var bodyShadow=gdip.solidBrush(0x55000000);
    graphics.fillEllipse(bodyShadow,63,654,155,32);bodyShadow.delete();
    var foot=gdip.solidBrush(0xFFFFA62B);
    graphics.fillEllipse(foot,94,670,47,12);graphics.fillEllipse(foot,145,671,49,12);foot.delete();
    var body=gdip.solidBrush(0xFFF5F1E8);var wing=gdip.solidBrush(0xFFD7E1E5);var outline=gdip.pen(0xFF243541,2.1);
    graphics.fillEllipse(body,70,585,132,99);graphics.drawEllipse(outline,70,585,132,99);
    graphics.fillEllipse(wing,83,613,83,48);
    if(blasted) graphics.fillPolygon(wing,[[105,634],[54,566],[91,649],[133,645]]);
    var feather=gdip.pen(0x8B8EA7B1,1.3);graphics.drawArc(feather,94,622,55,26,190,135);graphics.drawArc(feather,101,631,52,23,190,130);feather.delete();

    var headX;var headY;var tipX;var tipY;
    if(game.strike){
        tipX=LINE_X+LINE_DX*game.strike.length;tipY=LINE_Y+LINE_DY*game.strike.length;
        headX=tipX-LINE_DX*82;headY=tipY-LINE_DY*82;
        var neckBack=gdip.pen(0xFF263944,27);var neck=gdip.pen(0xFFF5F1E8,21);
        graphics.drawLine(neckBack,151,616,headX,headY);graphics.drawLine(neck,151,616,headX,headY);
        neck.delete();neckBack.delete();
    }
    else{
        headX=LINE_X-LINE_DX*62;headY=LINE_Y-LINE_DY*62+wobble;
        tipX=LINE_X;tipY=LINE_Y+wobble;
        var neckBack=gdip.pen(0xFF263944,29);var neck=gdip.pen(0xFFF5F1E8,23);
        //大幅张开的反向双弯明确形成蓄力 S,而不是缩成一个圆环。
        var points=[[145,620],[207+wobble,615],[130-wobble,558],[headX,headY]];
        graphics.drawCurve(neckBack,points);graphics.drawCurve(neck,points);
        neck.delete();neckBack.delete();
    }

    graphics.fillCircle(body,headX,headY,20);graphics.drawCircle(outline,headX,headY,20);
    var beak=gdip.solidBrush(game.stun>0 ? 0xFFB8A06D : 0xFFFFB52E);
    var pouch=gdip.solidBrush(game.stun>0 ? 0xFF8F7751 : 0xFFFF8B36);
    var baseX=headX+LINE_DX*8;var baseY=headY+LINE_DY*8;
    graphics.fillPolygon(beak,[[baseX+LINE_NX*7,baseY+LINE_NY*7],[tipX+LINE_NX*2,tipY+LINE_NY*2],[tipX-LINE_NX*2,tipY-LINE_NY*2],[baseX-LINE_NX*3,baseY-LINE_NY*3]]);
    graphics.fillPolygon(pouch,[[baseX-LINE_NX*2,baseY-LINE_NY*2],[tipX-LINE_NX*2,tipY-LINE_NY*2],[baseX-LINE_NX*13,baseY-LINE_NY*13]]);
    beak.delete();pouch.delete();
    var eye=gdip.solidBrush(0xFF0A1720);var eyeWhite=gdip.solidBrush(0xFFFFFFFF);
    var eyeRadius=blasted || panic ? 6.2 : 4.1;
    graphics.fillCircle(eyeWhite,headX+LINE_NX*5+LINE_DX*6,headY+LINE_NY*5+LINE_DY*6,eyeRadius);
    graphics.fillCircle(eye,headX+LINE_NX*5+LINE_DX*(blasted ? 5 : 7),headY+LINE_NY*5+LINE_DY*(blasted ? 5 : 7),blasted ? 2.8 : 2.1);
    eye.delete();eyeWhite.delete();

    if(drool && !game.strike && game.stun<=0 && !blasted){
        var t=0.58;var droolX=baseX+(tipX-baseX)*t+LINE_NX*8;var droolY=baseY+(tipY-baseY)*t+LINE_NY*8;
        var length=28+8*math.sin(game.pulse/105);
        var droolPen=gdip.pen(0xFF54E9FF,4.4);var droolDrop=gdip.solidBrush(0xFF54E9FF);var droolLight=gdip.solidBrush(0xEFFFFFFF);
        graphics.drawBezier(droolPen,droolX,droolY,droolX+4,droolY+9,droolX-4,droolY+length-7,droolX,droolY+length);
        graphics.fillCircle(droolDrop,droolX,droolY+length+5,6.2);
        graphics.fillCircle(droolLight,droolX-1.8,droolY+length+2.5,1.6);
        graphics.fillCircle(droolDrop,droolX+9,droolY+length+17,3.6);
        droolLight.delete();droolDrop.delete();droolPen.delete();
    }
    if(panic && !blasted){
        var sweat=gdip.solidBrush(0xE178E9FF);
        graphics.fillEllipse(sweat,headX-24,headY-23,7,12);graphics.fillCircle(sweat,headX-20.5,headY-10,3.2);sweat.delete();
    }
    if(blasted) drawText(graphics,"!",FONT_24B,headX-43,headY-56,30,38,0xFFFFE98A,1);
    if(game.stun>0){
        var lightning=gdip.pen(0xFFFFE96B,3);
        for(i=0;2){
            var ox=190+i*17;var oy=540-i*6;
            graphics.drawLines(lightning,[[ox,oy],[ox+8,oy-12],[ox+2,oy-19],[ox+14,oy-32]]);
        }
        lightning.delete();
    }
    outline.delete();wing.delete();body.delete();
    graphics.restore(poseSaved);
}

var drawCinemaEffects=function(graphics){
    if(!game.mega) return;
    var t=1-game.mega.life/game.mega.maxLife;
    var size=420+t*280;
    megaImageAttributes.setAlphaMatrix(math.max(0.08,1-t*0.92));
    graphics.drawImageRect(megaSprite,game.mega.x-size/2,game.mega.y-size/2,size,size,megaImageAttributes);
    if(game.flash>0){
        var flashRing=gdip.pen(alphaColor(0xFFFFFFFF,175*math.min(1,game.flash)),8+10*math.min(1,game.flash));
        graphics.drawCircle(flashRing,game.mega.x,game.mega.y,72+(1-math.min(1,game.flash))*90);flashRing.delete();
    }
}

var drawLeakTethers=function(graphics){
    var leak;
    for(i,e in game.entities) if(e.kind=="leak" && e.state=="orbit") leak=e;
    if(!leak) return;
    for(i,e in game.entities){
        if(e.kind=="model" && e.state=="orbit" && e.boosted){
            var pen=gdip.pen(alphaColor(0xFF54FFD3,80+45*math.sin(game.pulse/65+i)),1.5);
            graphics.drawLine(pen,leak.x,leak.y,e.x,e.y);pen.delete();
        }
    }
}

var drawGag=function(graphics){
    if(game.gagTime<=0) return;
    var age=game.gagMax-game.gagTime;
    var opacity=math.min(1,age/0.10,game.gagTime/0.16);
    var lift=(1-opacity)*10;
    var shadow=gdip.solidBrush(alphaColor(0xFF000000,135*opacity));
    var box=gdip.solidBrush(alphaColor(0xFF10202D,245*opacity));
    var edge=gdip.pen(alphaColor(game.gagColor,240*opacity),2.4);
    graphics.fillRoundRect(shadow,30,334-lift,270,60,20);
    graphics.fillRoundRect(box,24,328-lift,270,60,20);
    graphics.drawRoundRect(edge,24,328-lift,270,60,20);
    graphics.fillPolygon(box,[[176,388-lift],[208,388-lift],[192,505-lift]]);
    drawText(graphics,game.gagText,FONT_18B,36,334-lift,246,46,alphaColor(game.gagColor,255*opacity),1);
    edge.delete();box.delete();shadow.delete();
}

var drawHud=function(graphics){
    var data=levelData();
    var remaining=activeModelCount();
    var pill=gdip.solidBrush(0xA5091724);var border=gdip.pen(0x4268C7DA,1);
    graphics.fillRoundRect(pill,24,104,214,70,18);graphics.drawRoundRect(border,24,104,214,70,18);
    drawText(graphics,"待返回模型",FONT_11,42,114,92,18,0xAFC6DDE7,0);
    drawText(graphics,tostring(remaining),FONT_32B,38,130,56,38,data.accent,0);
    drawText(graphics,"得分  "++game.score,FONT_12,101,135,120,23,0xFFE4F8FF,0);
    border.delete();pill.delete();

    var modeText=RELAXED_AGI_MODE ? "A · G · I 缓冲" : "一条即失败";
    drawText(graphics,modeText,FONT_11,875,103,155,20,0xAFC8DFE8,1);
    var letters=['A','G','I'];
    for(i,ch in letters){
        var x=901+(i-1)*46;var lit=game.leaks>=i;
        var fill=gdip.solidBrush(lit ? 0xE9FF4D8B : 0x55132735);
        var edge=gdip.pen(lit ? 0xFFFFB7CF : 0x57608390,1.5);
        graphics.fillCircle(fill,x,145,17);graphics.drawCircle(edge,x,145,17);
        drawText(graphics,ch,FONT_14B,x-14,132,28,27,lit ? 0xFFFFFFFF : 0x7AC2D3DB,1);
        edge.delete();fill.delete();
    }

    if(game.chainCount>1 && game.chainTime>0){
        var scale=1+0.08*math.sin(game.pulse/45);
        var megaText=game.mega || game.chainCount>=4;
        var w=(megaText ? 224 : 150)*scale;var h=(megaText ? 48 : 42)*scale;
        var box=gdip.solidBrush(megaText ? 0xEFFFF0A2 : 0xB7FFB92F);graphics.fillRoundRect(box,CX-w/2,528-h/2,w,h,20);box.delete();
        drawText(graphics,(megaText ? "一嘴大爆破  ×" : "连锁爆破  ×")++game.chainCount,megaText ? FONT_24B : FONT_18B,CX-w/2,528-h/2,w,h,0xFF3A210B,1);
    }

    var status=game.statusTime>0 ? game.status : "等目标穿过 TEST LINE → 点击任意位置";
    var statusBox=gdip.solidBrush(0xF507131F);graphics.fillRoundRect(statusBox,300,568,728,28,14);statusBox.delete();
    drawText(graphics,status,FONT_13,316,569,696,26,game.statusTime>0 ? 0xFFF4E8C5 : 0xCFE1F5F8,1);

    if(game.intro>0 && game.state==STATE_PLAY){
        //保持清晰,最后 0.45 秒快速淡出,避免长期残留成“幽灵 UI”。
        var alpha=math.min(225,game.intro/0.45*225);
        //首局提示收在鹈鹕旁边,不覆盖漩涡、轨道与版本泡泡。
        var box=gdip.solidBrush(alphaColor(0xFF071521,alpha));var edge=gdip.pen(alphaColor(data.accent,alpha),1.5);
        graphics.fillRoundRect(box,24,452,242,60,18);graphics.drawRoundRect(edge,24,452,242,60,18);
        drawText(graphics,"目标过线 · 点任意处",FONT_14B,34,458,222,25,alphaColor(0xFFFFFFFF,alpha),1);
        drawText(graphics,"长嘴会命中线路上的第一个物体",FONT_10,34,483,222,18,alphaColor(data.accent,alpha),1);
        edge.delete();box.delete();
    }
    drawGag(graphics);
}

var drawResult=function(graphics){
    if(game.state==STATE_PLAY) return;
    var failed=game.state==STATE_FAILED;
    var mask=gdip.solidBrush(failed ? 0x741C0615 : 0x68102027);
    graphics.fillRoundRect(mask,274,177,692,306,30);mask.delete();
    var glow=gdip.pen(failed ? 0xB7FF5B92 : 0xB45EFFE0,8);
    var edge=gdip.pen(failed ? 0xFFFF9DBC : 0xFFB8FFF0,2);
    graphics.drawRoundRect(glow,280,183,680,294,28);graphics.drawRoundRect(edge,280,183,680,294,28);
    edge.delete();glow.delete();
    if(failed){
        drawText(graphics,"已实现 AGI!",FONT_44B,310,220,620,74,0xFFFFD6E4,1);
        drawText(graphics,"发布搞错了 · 上传错了",FONT_18B,310,295,620,34,0xFFFF7FA8,1);
        drawText(graphics,"上传的是炼废版,但会场已经开始鼓掌。",FONT_13,310,346,620,28,0xFFEED9E0,1);
        drawText(graphics,"点击任意位置 · 立即重开本关",FONT_14B,310,410,620,34,0xFFFFFFFF,1);
    }
    else{
        drawText(graphics,"全部返回训练池",FONT_32B,310,220,620,58,0xFFCBFFF2,1);
        drawText(graphics,"今天仍未 AGI。",FONT_24B,310,282,620,42,0xFF65F0D0,1);
        var detail=game.level==#levels ? "大的炸完了,小的回池了;上传前却发现选错了 checkpoint。" : "漂亮的时机判断:模型继续训练,版本数字只是数字。";
        drawText(graphics,detail,FONT_13,310,346,620,28,0xFFE1F5F2,1);
        var nextText=game.level==#levels ? "点击任意位置 · 再守一轮" : "点击任意位置 · 下一关";
        drawText(graphics,nextText,FONT_14B,310,410,620,34,0xFFFFFFFF,1);
    }
}

// -------------------- 输入与绘图事件 --------------------
winform.pelicanCanvas.onMouseDown=function(wParam,lParam){
    if((wParam&1)==0) return;
    beginStrike();
}

winform.pelicanCanvas.dlgCode=4/*_DLGC_WANTALLKEYS*/;
winform.pelicanCanvas.onKeyDown=function(vk){
    if(vk==0x20/*_VK_SPACE*/) beginStrike();
    elseif(vk==0x52/*R*/) resetLevel(game.level);
    elseif(vk==0x4D/*M*/){
        game.muted=!game.muted;
        if(game.muted && midiOut) midiOut.reset();
        setStatus(game.muted ? "事件音效已关闭" : "事件音效已开启",0.8);
    }
}

winform.pelicanCanvas.onDrawBackground=function(graphics,rc,backgroundColor,color){return true;}

winform.pelicanCanvas.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*/;
    if(game.screenShake>0){
        graphics.translate(math.sin(game.pulse/15)*game.screenShake*3,math.cos(game.pulse/19)*game.screenShake*2);
    }

    drawVortex(graphics);
    drawLeakTethers(graphics);
    drawCinemaEffects(graphics);
    drawActiveLine(graphics);
    drawPoolWater(graphics);
    drawEntities(graphics);
    drawEffects(graphics);
    drawPelican(graphics);
    drawHud(graphics);
    drawResult(graphics);
    graphics.restore(saved);
}

winform.pelicanCanvas.onAnimation=function(prevState,beginning,change,timestamp,duration,deltaMs){
    deltaMs=math.min(deltaMs,55);
    var dt=deltaMs/1000;
    game.pulse+=deltaMs;
    game.cooldown=math.max(0,game.cooldown-dt);
    game.stun=math.max(0,game.stun-dt);
    game.statusTime=math.max(0,game.statusTime-dt);
    game.chainTime=math.max(0,game.chainTime-dt);
    game.gagTime=math.max(0,game.gagTime-dt);
    game.intro=math.max(0,game.intro-dt);
    game.neckWobble=math.max(0,game.neckWobble-dt*2.6);
    game.screenShake=math.max(0,game.screenShake-dt*4.5);
    game.pelicanBlast=math.max(0,game.pelicanBlast-dt);
    game.flash=math.max(0,game.flash-dt*3.0);
    if(game.mega){game.mega.life-=dt;if(game.mega.life<=0) game.mega=null;}
    if(game.state!=STATE_PLAY) game.resultTime+=dt;

    updateStrike(dt);
    if(game.state==STATE_PLAY) updateGame(dt);
    else updateShocks(dt);
    updateFalling(dt);
    updateParticles(dt);
    game.lineReady=findFirstLineHit()!=null;
    return true;
}

winform.onClose=function(){
    if(midiOut){midiOut.reset();midiOut.close();midiOut=null;}
    if(megaImageAttributes){megaImageAttributes.delete();megaImageAttributes=null;}
    if(megaSprite){megaSprite.delete();megaSprite=null;}
}

resetLevel(1);
winform.show();
winform.pelicanCanvas.setFocus();
winform.pelicanCanvas.startAnimation(16);
win.loopMessage();

Markdown 格式