// SPDX-License-Identifier: AGPL-3.0-or-later
package preservation;

/** Complete selected US Cross beam, six spinning crosses and blue particles.
 * Source: pinned sotn-decomp 0622fcfd37311fd21baf2938c6bd74cea63005ba,
 * src/dra/84B88.c, wolfForm.c and 7879C.c. All actors retain captured screen
 * coordinates. Factories are next-tick jobs in the shared local slot/budget
 * proxy, not full original entity dispatch. Selected BIOS-rand calls share the
 * Holy Water stream in GameEngine; standalone tests use isolated seed1.
 * Base damage, current-camera world overlap and once-per-target hits are local
 * proxies. Sound/afterimage events are counted; audio/full player effects are
 * pending. Geometry is direct Java math, never CPU/GTE/BIOS execution.
 */
public final class CrossWorld {
    static final int ONE=65536,BEAM=8,GIANT=7,BLUE=9,PLAYER_PRIORITY=148;
    interface RandomSource {int next();}
    interface Environment {int hit(Actor actor,int x,int y,int width,int height,int sequence,int damage);}
    static final class Primitive {
        final int[] quad=new int[8],uv=new int[8],rgb=new int[3];
        int drawMode,priority,palette,index,frame,pose,x,y,speed,birth;boolean live,initialized;
    }
    static final class Actor {
        final int entityId,slot,serial,sequence,params;final Object[] targets=new Object[256];
        int x,y,vy,face,step,remaining,halfWidth,height,widthByte,heightByte,gray=128,orbit,rotation,centerDepth,hitboxWidth,hitboxHeight,hitboxOffX,hitboxOffY,hitboxState,targetCount,hitCount,damageRoll;
        boolean active=true,allocated;String destroyedReason="";Primitive[] primitives;
        Actor(int id,int slot,int serial,int sequence,int params,int x,int y,int face){entityId=id;this.slot=slot;this.serial=serial;this.sequence=sequence;this.params=params;this.x=x;this.y=y;this.face=face;}
        boolean hasHit(Object target){for(int i=0;i<targetCount;i++)if(targets[i]==target)return true;return false;}
        boolean rememberHit(Object target){if(target==null||hasHit(target)||targetCount==targets.length)return false;targets[targetCount++]=target;return true;}
    }
    static final class Pending {
        final int entityId,x,y,face,sequence;int nextFrame,index,amount,cycle;
        Pending(int id,Actor parent,int time,int amount,int cycle){entityId=id;x=parent.x;y=parent.y;face=parent.face;sequence=parent.sequence;nextFrame=time+1;this.amount=amount;this.cycle=cycle;}
    }
    private final DaggerWorld.SharedPool pool;private final RandomSource randomSource;
    private final Actor[] actors=new Actor[16];private final Pending[] pending=new Pending[4];
    private int[] parentFields,giantFields,quarter,packed,blueprints,vertices,uvFlags,particleUV,ramps;
    private int serial,frameCounter,randomState=1,randomCalls,allocationFailures,factoryFailures,giantBirths,blueBirths,soundEvents,afterimageEvents,peakPrimitiveUsage;
    public CrossWorld(){this(new DaggerWorld.SharedPool(),null);}
    CrossWorld(DaggerWorld.SharedPool pool,RandomSource random){if(pool==null)throw new IllegalArgumentException("Missing shared pool");this.pool=pool;randomSource=random;}
    private static boolean fieldsValid(int[] f){if(f==null||f.length!=13)return false;for(int i=0;i<13;i++)if(f[i]<0||f[i]>(i<3?65535:255))return false;return f[0]>0&&f[0]<10000&&f[8]==2;}
    public boolean register(int[] parent,int[] giant,int[] sineQuarter,int[] packedRotation,int[] factories,int[] faceVertices,int[] faceUvs,int[] blueUvs,int[] paletteWords){
        if(!fieldsValid(parent)||!fieldsValid(giant)||parent[1]!=100||parent[3]!=1||parent[6]!=220||parent[7]!=6||parent[10]!=12||giant[1]!=5||giant[3]!=1||giant[7]!=6||sineQuarter==null||sineQuarter.length!=1025||packedRotation==null||packedRotation.length!=4096||factories==null||factories.length!=18||faceVertices==null||faceVertices.length!=552||faceUvs==null||faceUvs.length!=230||blueUvs==null||blueUvs.length!=48||paletteWords==null||paletteWords.length!=96)return false;
        if(sineQuarter[0]!=0||sineQuarter[1024]!=4096)return false;for(int i=0;i<1025;i++)if(sineQuarter[i]<0||sineQuarter[i]>4096||i>0&&sineQuarter[i]<sineQuarter[i-1])return false;
        for(int word:packedRotation)if(Math.abs((int)(short)word)>4096||Math.abs((int)(short)(word>>>16))>4096)return false;
        for(int v:faceVertices)if(v<-4096||v>4096)return false;for(int v:faceUvs)if(v<0||v>255)return false;for(int i=4;i<230;i+=5)if((faceUvs[i]&15)<13||(faceUvs[i]&15)>15||(faceUvs[i]&240)>112)return false;
        for(int v:blueUvs)if(v<0||v>255)return false;for(int v:paletteWords)if(v<0||v>65535)return false;
        int[] expected={8,1,193,0,18,0,7,6,193,24,18,0,9,1,193,0,2,0};for(int i=0;i<18;i++)if(factories[i]!=expected[i])return false;
        parentFields=parent.clone();giantFields=giant.clone();quarter=sineQuarter.clone();packed=packedRotation.clone();blueprints=factories.clone();vertices=faceVertices.clone();uvFlags=faceUvs.clone();particleUV=blueUvs.clone();ramps=paletteWords.clone();reset();return true;
    }
    public void reset(){for(int i=0;i<16;i++){if(actors[i]!=null)pool.release(actors[i]);actors[i]=null;}for(int i=0;i<pending.length;i++)pending[i]=null;serial=frameCounter=randomCalls=allocationFailures=factoryFailures=giantBirths=blueBirths=soundEvents=afterimageEvents=peakPrimitiveUsage=0;randomState=1;}
    public boolean registered(){return parentFields!=null;}public int heartCost(){return parentFields==null?0:parentFields[1];}
    public int activeCount(){int count=0;for(Actor a:actors)if(a!=null&&a.active&&a.entityId==BEAM)count++;return count;}
    public int refusal(){return !registered()?1:activeCount()>=parentFields[3]||pool.freeSlot()<0?2:0;}
    public boolean spawn(int screenXFixed,int originYFixed,int face,int sequence){if(refusal()!=0||face!=1&&face!=-1||sequence<1||screenXFixed<Integer.MIN_VALUE+64*ONE||screenXFixed>Integer.MAX_VALUE-64*ONE||originYFixed<Integer.MIN_VALUE+64*ONE||originYFixed>Integer.MAX_VALUE-64*ONE)return false;create(BEAM,pool.freeSlot(),screenXFixed,originYFixed,face,sequence,0);return true;}
    private Actor create(int id,int index,int x,int y,int face,int sequence,int params){Actor a=new Actor(id,index+32,++serial,sequence,params,x,y,face);pool.reserve(index,a);actors[index]=a;if(id==GIANT)giantBirths++;return a;}
    private int random(){randomCalls++;if(randomSource!=null)return randomSource.next();randomState=randomState*1103515245+12345;return(randomState>>>16)&32767;}
    private boolean allocate(Actor a,int count){if(!pool.allocate(a,count)){allocationFailures++;destroy(a,"primitive-allocation-failed");return false;}a.allocated=true;a.primitives=new Primitive[count];for(int i=0;i<count;i++){a.primitives[i]=new Primitive();a.primitives[i].index=i;}peakPrimitiveUsage=Math.max(peakPrimitiveUsage,pool.usedPrimitives());return true;}
    private void destroy(Actor a,String reason){a.active=a.allocated=false;a.hitboxState=0;a.destroyedReason=reason;if(a.primitives!=null)for(Primitive p:a.primitives)p.drawMode|=8;pool.release(a);}
    private void queue(int id,Actor parent,int amount,int cycle){for(int i=0;i<pending.length;i++)if(pending[i]==null){pending[i]=new Pending(id,parent,frameCounter,amount,cycle);return;}factoryFailures++;}
    void beginFrame(){frameCounter++;for(int i=0;i<pending.length;i++){Pending job=pending[i];if(job==null||job.nextFrame>frameCounter)continue;int slot=pool.freeSlot();if(slot<0){factoryFailures++;pending[i]=null;continue;}create(job.entityId,slot,job.x,job.y,job.face,job.sequence,job.index++);if(job.index==job.amount)pending[i]=null;else job.nextFrame=frameCounter+job.cycle;}}
    public void update(Environment environment,int cameraX,int cameraY,int gameTimer){beginFrame();for(int i=0;i<16;i++)updateSlot(i,environment,cameraX,cameraY,gameTimer);}
    void updateSlot(int index,Environment environment,int cameraX,int cameraY,int gameTimer){if(!registered())return;Actor a=actors[index];if(a==null||!a.active)return;
        if(a.hitboxState!=0&&a.hitboxWidth>0&&a.hitboxHeight>0&&environment.hit(a,(a.x>>16)+a.hitboxOffX+cameraX,(a.y>>16)+a.hitboxOffY+cameraY,a.hitboxWidth,a.hitboxHeight,a.sequence,a.entityId==GIANT?giantFields[0]:parentFields[0])>0)a.hitCount++;
        if(a.entityId==BEAM)beam(a,gameTimer);else if(a.entityId==GIANT)giant(a);else blue(a);
    }
    private void beam(Actor a,int timer){afterimageEvents++;int left=0,right=0,x=(short)(a.x>>16);
        switch(a.step){
        case 0:if(!allocate(a,1))return;a.y=(120<<16)|(a.y&65535);a.widthByte=1;a.hitboxState=parentFields[8];a.damageRoll=random()%12;Primitive initial=a.primitives[0];initial.drawMode=49;initial.priority=194;initial.uv[0]=initial.uv[4]=1;initial.uv[2]=initial.uv[6]=48;initial.uv[1]=initial.uv[3]=initial.uv[5]=initial.uv[7]=248;initial.live=true;soundEvents+=2;a.step=1;break;
        case 1:a.height=(short)(a.height+3);a.heightByte=(a.heightByte+6)&255;if((a.height&255)>=112){queue(GIANT,a,6,24);queue(BLUE,a,1,0);a.step=2;}break;
        case 2:if((timer&1)!=0){a.halfWidth=(short)(a.halfWidth+1);a.widthByte=(a.widthByte+2)&255;if(a.widthByte>=44){a.step=3;a.remaining=128;}}break;
        case 3:if(--a.remaining==0){left=(short)Math.max(0,x-a.halfWidth);right=(short)Math.min(255,x+a.halfWidth);a.step=4;soundEvents++;}break;
        case 4:int amount=3*(Math.abs(x-128)+128)/112;a.halfWidth=(short)(a.halfWidth+amount);left=(short)(x-a.halfWidth);right=(short)(x+a.halfWidth);if(right>384&&left<-128){destroy(a,"beam-expansion-complete");return;}break;
        default:throw new IllegalStateException("Invalid Cross beam step");}
        a.hitboxOffY=0;a.hitboxHeight=a.height&255;a.hitboxWidth=(a.step==4?(right-left)>>1:a.halfWidth)&255;a.hitboxOffX=a.step==4?(short)(((left+right)>>1)-x):0;
        Primitive p=a.primitives[0];int x0=(short)(x-a.halfWidth),x1=(short)(x0+a.widthByte),y0=(short)((a.y>>16)-a.height),y1=(short)(y0+a.heightByte);
        if(a.step==4){if(a.gray<248)a.gray+=4;for(int c=0;c<3;c++)p.rgb[c]=a.gray;p.drawMode=53;x0=left;x1=right;}rect(p,x0,y0,x1,y1);
    }
    private void giant(Actor a){if(a.step==0){if(!allocate(a,46))return;a.damageRoll=random()%12;a.hitboxState=giantFields[8];a.hitboxHeight=80;a.hitboxWidth=12;a.face=1;a.y=(352<<16)|(a.y&65535);a.vy=-6*ONE;a.rotation=1024;a.step=1;soundEvents++;for(int i=0;i<46;i++){Primitive p=a.primitives[i];p.drawMode=264;p.palette=uvFlags[i*5+4]&15;faceUV(p,i);}return;}
        if((a.y>>16)<-64){destroy(a,"giant-rise-complete");return;}if((a.y>>16)<0)a.vy-=ONE/2;a.y+=a.vy;a.orbit=(short)(a.orbit+96);a.rotation=(short)(a.rotation+96);
        int tx=-(CrossMath.sine(quarter,a.orbit+1024)*96)>>12,ty=(a.y>>16)-128,tz=(CrossMath.sine(quarter,a.orbit)*96>>12)+384;int[] matrix=CrossMath.matrix(packed,a.rotation,64);int ox=(short)(a.x>>16);
        CrossMath.Point center=CrossMath.project(matrix,tx,ty,tz,ox,120,0,0,0);a.primitives[0].quad[0]=center.x;a.primitives[0].quad[1]=center.y;a.centerDepth=center.depth>>2;a.hitboxOffX=(short)(center.x-ox);a.hitboxOffY=(short)(center.y-(a.y>>16));
        for(int i=0;i<46;i++){Primitive p=a.primitives[i];CrossMath.Point p0=point(matrix,tx,ty,tz,ox,i,0),p1=point(matrix,tx,ty,tz,ox,i,1),p3=point(matrix,tx,ty,tz,ox,i,3);p.drawMode=8;p.live=false;if(CrossMath.clip(p0,p1,p3)<0)continue;CrossMath.Point p2=point(matrix,tx,ty,tz,ox,i,2);p.quad[0]=p0.x;p.quad[1]=p0.y;p.quad[2]=p1.x;p.quad[3]=p1.y;p.quad[4]=p3.x;p.quad[5]=p3.y;p.quad[6]=p2.x;p.quad[7]=p2.y;p.drawMode=0;p.live=true;p.priority=a.centerDepth<16?438:a.centerDepth>998?16:288-a.centerDepth;}
    }
    private CrossMath.Point point(int[] m,int tx,int ty,int tz,int ox,int face,int vertex){int at=face*12+vertex*3;return CrossMath.project(m,tx,ty,tz,ox,120,vertices[at],vertices[at+1],vertices[at+2]);}
    private void faceUV(Primitive p,int index){int at=index*5,x=uvFlags[at],y=uvFlags[at+1],w=uvFlags[at+2],h=uvFlags[at+3],flags=uvFlags[at+4]&240;int[] order;
        switch(flags){case 16:order=new int[]{1,0,3,2};break;case 32:order=new int[]{2,3,0,1};break;case 48:order=new int[]{3,2,1,0};break;case 64:order=new int[]{3,1,2,0};break;case 80:order=new int[]{2,0,3,1};break;case 96:order=new int[]{1,3,0,2};break;case 112:order=new int[]{0,2,1,3};break;default:order=new int[]{0,1,2,3};}
        for(int i=0;i<4;i++){p.uv[i*2]=(x+((order[i]&1)!=0?w:0))&255;p.uv[i*2+1]=(y+((order[i]&2)!=0?h:0))&255;}}
    private void blue(Actor a){if(a.step==0){if(!allocate(a,64))return;a.remaining=192;a.step=1;return;}if(--a.remaining==0){destroy(a,"blue-timer-complete");return;}if(a.remaining>8&&(a.remaining&3)==0)for(Primitive p:a.primitives)if(!p.live){p.live=true;p.initialized=false;break;}
        for(Primitive p:a.primitives)if(p.live){if(!p.initialized){int r=random()&63;p.x=(random()%237)+9;p.y=240-(random()&32);p.frame=p.pose=0;p.priority=PLAYER_PRIORITY+r-32;p.speed=(r>>2)+4;p.drawMode=0;p.birth=++blueBirths;p.initialized=true;}else{p.y=(p.y-p.speed)&255;if(p.frame>=6||p.y<24){p.drawMode=8;p.live=false;}}
            if(p.live){int size=p.frame<3?6:4;rect(p,p.x-size,p.y-size,p.x+size,p.y+size);for(int i=0;i<8;i++)p.uv[i]=particleUV[p.frame*8+i];if((++p.pose&1)==0)p.frame++;}}
    }
    private static void rect(Primitive p,int x0,int y0,int x1,int y1){p.quad[0]=p.quad[4]=x0;p.quad[2]=p.quad[6]=x1;p.quad[1]=p.quad[3]=y0;p.quad[5]=p.quad[7]=y1;}
    public String getState(){StringBuilder out=new StringBuilder("[");boolean comma=false;for(Actor a:actors)if(a!=null){if(comma)out.append(',');comma=true;String kind=a.entityId==BEAM?"crossBeam":a.entityId==GIANT?"crossGiant":"crossBlueParticles";
        out.append("{\"kind\":\"").append(kind).append("\",\"screenSpace\":true,\"playerPriority\":148,\"entityId\":").append(a.entityId).append(",\"inventoryId\":").append(a.entityId==BEAM?4:a.entityId==GIANT?12:0).append(",\"slot\":").append(a.slot).append(",\"serial\":").append(a.serial).append(",\"sequence\":").append(a.sequence).append(",\"params\":").append(a.params).append(",\"x\":").append(a.x>>16).append(",\"y\":").append(a.y>>16).append(",\"xFixed\":").append(a.x).append(",\"yFixed\":").append(a.y).append(",\"vyFixed\":").append(a.vy).append(",\"face\":").append(a.face).append(",\"facingLeft\":").append(a.face<0?1:0).append(",\"step\":").append(a.step).append(",\"remaining\":").append(a.remaining).append(",\"height\":").append(a.height).append(",\"halfWidth\":").append(a.halfWidth).append(",\"widthByte\":").append(a.widthByte).append(",\"heightByte\":").append(a.heightByte).append(",\"gray\":").append(a.gray).append(",\"orbit\":").append(a.orbit).append(",\"rotation\":").append(a.rotation).append(",\"centerQuarterDepth\":").append(a.centerDepth).append(",\"hitboxState\":").append(a.hitboxState).append(",\"hitboxWidth\":").append(a.hitboxWidth).append(",\"hitboxHeight\":").append(a.hitboxHeight).append(",\"hitboxOffX\":").append(a.hitboxOffX).append(",\"hitboxOffY\":").append(a.hitboxOffY).append(",\"hitCount\":").append(a.hitCount).append(",\"hitTargetCount\":").append(a.targetCount).append(",\"damageRoll\":").append(a.damageRoll).append(",\"active\":").append(a.active).append(",\"alive\":").append(a.active).append(",\"destroyedReason\":\"").append(a.destroyedReason).append("\",\"primitives\":[");
        if(a.primitives!=null)for(int i=0;i<a.primitives.length;i++){if(i>0)out.append(',');Primitive p=a.primitives[i];out.append("{\"index\":").append(i).append(",\"faceIndex\":").append(a.entityId==GIANT?i:-1).append(",\"quad\":");array(out,p.quad);out.append(",\"uv\":");array(out,p.uv);out.append(",\"rgb\":");array(out,p.rgb);out.append(",\"rampIndex\":0,\"paletteIndex\":").append(a.entityId==GIANT?p.palette-13:0).append(",\"palette\":").append(a.entityId==GIANT?p.palette+416:a.entityId==BLUE?432:0).append(",\"drawMode\":").append(p.drawMode).append(",\"priority\":").append(p.priority).append(",\"priorityRelativeToPlayer\":").append(p.priority-PLAYER_PRIORITY).append(",\"visible\":").append(a.active&&p.live&&(p.drawMode&8)==0).append(",\"frame\":").append(p.frame).append(",\"pose\":").append(p.pose).append(",\"birth\":").append(p.birth).append(",\"x\":").append(p.x).append(",\"y\":").append(p.y).append(",\"speed\":").append(p.speed).append('}');}out.append("]}");}return out.append(']').toString();}
    private static void array(StringBuilder out,int[] a){out.append('[');for(int i=0;i<a.length;i++){if(i>0)out.append(',');out.append(a[i]);}out.append(']');}
    public String getStatistics(){int jobs=0;for(Pending p:pending)if(p!=null)jobs++;return "{\"randomCalls\":"+randomCalls+",\"giantBirths\":"+giantBirths+",\"blueBirths\":"+blueBirths+",\"pendingFactories\":"+jobs+",\"allocationFailures\":"+allocationFailures+",\"factoryFailures\":"+factoryFailures+",\"peakPrimitiveUsage\":"+peakPrimitiveUsage+",\"primitiveUsage\":"+pool.usedPrimitives()+",\"primitiveBudget\":256,\"soundEvents\":"+soundEvents+",\"afterimageEvents\":"+afterimageEvents+"}";}
    Actor actor(int index){return actors[index];}int allocationFailures(){return allocationFailures;}int randomCalls(){return randomCalls;}int randomState(){return randomState;}void seedRandom(int seed){randomState=seed;randomCalls=0;}void setPrimitiveCapacity(int capacity){pool.setPrimitiveCapacity(capacity);}int peakPrimitiveUsage(){return peakPrimitiveUsage;}int giantBirths(){return giantBirths;}int blueBirths(){return blueBirths;}
}
