From 482654bf7a8c02590bad7bbb58e5a133ef24bb90 Mon Sep 17 00:00:00 2001 From: Daniar Mannanov Date: Wed, 26 Aug 2026 11:40:23 +0300 Subject: [PATCH] worldgen: load structure templates and transform them like vanilla LoadResolvedTemplate parses the extracted .nbt files with every palette entry resolved to concrete state IDs through an injected resolver, and TransformBlockPos reproduces StructureTemplate.transform's mirror-then- rotate order around the pivot, including all three quarter-turn cases read off the bytecode. MthGetSeed gives processors their per-block positional Legacy stream. Verified against the real ruined_portal templates: every block lands inside the declared size for all eight mirror/rotation combinations. --- .../ruined_portal/giant_portal_1.nbt | Bin 0 -> 7198 bytes .../ruined_portal/giant_portal_2.nbt | Bin 0 -> 6839 bytes .../ruined_portal/giant_portal_3.nbt | Bin 0 -> 9899 bytes .../ruined_portal/portal_1.nbt | Bin 0 -> 1432 bytes .../ruined_portal/portal_10.nbt | Bin 0 -> 2879 bytes .../ruined_portal/portal_2.nbt | Bin 0 -> 2763 bytes .../ruined_portal/portal_3.nbt | Bin 0 -> 1995 bytes .../ruined_portal/portal_4.nbt | Bin 0 -> 1887 bytes .../ruined_portal/portal_5.nbt | Bin 0 -> 2217 bytes .../ruined_portal/portal_6.nbt | Bin 0 -> 984 bytes .../ruined_portal/portal_7.nbt | Bin 0 -> 1831 bytes .../ruined_portal/portal_8.nbt | Bin 0 -> 3408 bytes .../ruined_portal/portal_9.nbt | Bin 0 -> 2169 bytes internal/worldgen/structure_template.go | 176 ++++++++++++++++++ internal/worldgen/structure_template_test.go | 66 +++++++ 15 files changed, 242 insertions(+) create mode 100644 internal/worldgen/data/structure_template/ruined_portal/giant_portal_1.nbt create mode 100644 internal/worldgen/data/structure_template/ruined_portal/giant_portal_2.nbt create mode 100644 internal/worldgen/data/structure_template/ruined_portal/giant_portal_3.nbt create mode 100644 internal/worldgen/data/structure_template/ruined_portal/portal_1.nbt create mode 100644 internal/worldgen/data/structure_template/ruined_portal/portal_10.nbt create mode 100644 internal/worldgen/data/structure_template/ruined_portal/portal_2.nbt create mode 100644 internal/worldgen/data/structure_template/ruined_portal/portal_3.nbt create mode 100644 internal/worldgen/data/structure_template/ruined_portal/portal_4.nbt create mode 100644 internal/worldgen/data/structure_template/ruined_portal/portal_5.nbt create mode 100644 internal/worldgen/data/structure_template/ruined_portal/portal_6.nbt create mode 100644 internal/worldgen/data/structure_template/ruined_portal/portal_7.nbt create mode 100644 internal/worldgen/data/structure_template/ruined_portal/portal_8.nbt create mode 100644 internal/worldgen/data/structure_template/ruined_portal/portal_9.nbt create mode 100644 internal/worldgen/structure_template.go create mode 100644 internal/worldgen/structure_template_test.go diff --git a/internal/worldgen/data/structure_template/ruined_portal/giant_portal_1.nbt b/internal/worldgen/data/structure_template/ruined_portal/giant_portal_1.nbt new file mode 100644 index 0000000000000000000000000000000000000000..1fc2e2be6303c025ae6f892119649f6ad42b83d9 GIT binary patch literal 7198 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 literal 0 HcmV?d00001 diff --git a/internal/worldgen/data/structure_template/ruined_portal/giant_portal_3.nbt b/internal/worldgen/data/structure_template/ruined_portal/giant_portal_3.nbt new file mode 100644 index 0000000000000000000000000000000000000000..659803f8eb4bf76307bfe9f5fd4f8d029413aa83 GIT binary patch literal 9899 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 L8wVP@o|*GMHr2Pf literal 0 HcmV?d00001 diff --git a/internal/worldgen/data/structure_template/ruined_portal/portal_1.nbt b/internal/worldgen/data/structure_template/ruined_portal/portal_1.nbt new file mode 100644 index 0000000000000000000000000000000000000000..02cd4080382f0d1a4f74b4d33b10e3cdc42eb45f GIT binary patch literal 1432 zcmZvZdsNbA7{`HQaM9JDg|A$rJkmx z!Ox`ZBpJHtIoT4t6z0eagi{oUlvgI9g{f&%RQAiQv+Zo}AJ6l=&w1YO`97c5F5eio zdJN*y?ki(1TXMN^jq>yefPPWnV{8kr5sq*E*l}ZhiK^jry7SSJ43|jT0?l?%DMz}_ zFEpCi_ayMt)yvGb`NaAOBL7?G=!MGuK>kRI!m$4m(=tgnc`Ubwo+PkHdRSBX%(vfr ze-?Rn<|&V?Zf#FzdW(@^dTVGf9J>~49fK16iVB5Mr0OP42gB*wY%E424wmJ^4ghDN zrZeuEL>lZkoA?ZdVT0iFNbH9R$$#ER5dT&w=rzyVO)ZKsj&TujP`-9xD%YV!?QC8M zvZO9W?j>!2mDdQ*ydZ6GOzHrXDR)@i=!j!^N&_@sbe_UES6XSy0zt4yR$dIEwWvUl zFok|HSl%wdSRt%TvB;Q;WpfP}DR(2}70u(zPFBf0s%H1K1ke;dnQ6tlZc4DbGQ`vn z20F%IC6d6BlUSY>_v(yhq_A4F+m$p&KqM5vXP$q?AWGf3LUOv%fMAs3N?3&woICQ4 zZiGG9`tq_g|L60E&(w_M=?R`2a2~B&Chotx%Xv-~Z>54LUvtLwbwHWsU`qlEty!>d ziTJX)!g^pYhs=F_F+8uMk2GBhD|pa0dgxZ{S;l$*jvuHdjw9O13tEjhT&3p$$O1@qBXeY5VAcEcECg$pDpAR$@mW}0;985jym;_4juu3iaHx6KF~?% zVac+@^)T2?&`6^?3`SX-v1!xibNg-RF%b1}DAi2(N( zLILdPQz9sV(JRbd;(+D0^%Uuc=sy#SpYXnFi6a_{n%4hqan-9!q5pfQP(U3!2~^nb zWS}9s6y%%xEVfb8zTtTI0h0`pC|NK%6-6xQZTl+$Igp&)HOT8CM=gf>J8BXCTp!Xl zEx41NiaKToRIF_8yM*Q&0{&O^^U#9csL?4Xr6I?r&O$(G?neb#X zBnR45?Z;vxpLUDfkoU}R#Gyh37tK#u)${>OzIymR*wzTK5JMrygkppZPZg7UDBLxg zpw3_)0O2g(3i4)mG7?2Wgp5ou<|&1~4(~B_l?`{kaarns_mB{Q)gQ`ZpP#vT0;WF+ ze-)PK*bwWO7{dFLy>)vWODI*7gLsoP&R+c^hsp9YquD*SO^r=>{3C05N zcQu88$UjkvS<)#=!iTtRN}CIo4R!;SE3$Ojse$3DejEBG7bntEK8Nl_rKXjbwU~Kp zo2NpU+xB2dwqk-mj&_@$X~$TIQrTpFx?~}!eM|Me5cP?^d1y}~HB6&t=bHJ~0;=&u zY$@ku{JpSn;ha|6KP{(4ow03@&!$G_Ud;5v2=%!B9~#d_H7ve;rB79M%EI}F@yFkV z{1n~1q*$HLiN>?oEsTh!D4-W?=fe%cRrvua_T4#`(;LRZBHPp>vBY0e zHh;4rSzl{0V~BdvwI@z~{gF;ZP`>ql6JOpZ7oM|h-V&KBA3Ud14Qd6nN8eiiVk7>AfwzLJ=Se<(jfKJf0D;!jod5s; literal 0 HcmV?d00001 diff --git a/internal/worldgen/data/structure_template/ruined_portal/portal_10.nbt b/internal/worldgen/data/structure_template/ruined_portal/portal_10.nbt new file mode 100644 index 0000000000000000000000000000000000000000..77f49b01e9299041864dfcccde90dfb56631a66a GIT binary patch literal 2879 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 literal 0 HcmV?d00001 diff --git a/internal/worldgen/data/structure_template/ruined_portal/portal_3.nbt b/internal/worldgen/data/structure_template/ruined_portal/portal_3.nbt new file mode 100644 index 0000000000000000000000000000000000000000..32527876579ca9fe45942f0feeac45157411e1e3 GIT binary patch literal 1995 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 literal 0 HcmV?d00001 diff --git a/internal/worldgen/data/structure_template/ruined_portal/portal_5.nbt b/internal/worldgen/data/structure_template/ruined_portal/portal_5.nbt new file mode 100644 index 0000000000000000000000000000000000000000..aefbf66a3c675ec09c6df3453b40364746fa6ebc GIT binary patch literal 2217 zcmZ8hdsLHG5=T#q+Zu?j3nU=LEdqK#Ac=@Ply)OfAUrI~Ly4w!pEW zJOoOjO(>K{JwURk2q`3};SpX24TKefIoU?QAmJrM3`zDTQ7rxbm^0tpncvKv`w_%&PEr~BHs*5&L|GMij`saKJo)71 z?dY07#8#jV9h5*Y0GYS`bRljWkoiM-L4<*aevq}+{arml7DEqhosp!P&pau;K_x^~ za8-Eb3p`BvmBgyR^{*M%YiM7{=Z|{pqm_lgTb;!ERbAQ%lX__3(B`1HAa8Kju`+HX zB(<(SE2*HS_>`@DO@BwKG5xVv4O7YuHuG3yVt+DbNYl|Ju%ZtZDi=7;KhdY8iN5 zvbX%2lK@7Euq+|Z<+SyMQ^n(OEVZl=wG!LU(_c7j)trHN4QXs8o^Rf~)_Sq~5H6=@ zH0QLvfamgl3U+gv9J_9aD7bSq7RlO*;9>&M^E$mq$hWHA>l4$23$ysly05cZB@ zeSnQ|a2tcs3MA9H;7^S19Th#%jMF<(*Ra2oQOlSpWSZB^W3CWRiNYF5_M**y-YFNt zY`dfFq-I7Pq#E|=hi8q@+1~=viViRyE%_FNA$2A4X*kC*XYC|{BfwQ>z~?msB16O5 zce;rtG9K_2*tNO_Mc#LLOV_%xoV4g$VJlJy3J;xd4>A1@RhnfVfyRtnD1WU-4yRZC-27==13Z%E2GpnPoQJ2cW6f z^<`D6>41Bon%in`wf$K^Gdxaw8y#i6bz@y~Z2#OSu)p57UhB$2fI)2fAe@lxUNZu& z(3Ik48Hks>EOLK_*tf8Fq{yldPFm+13&=33UWJP@a@Hia!Ek9!oo5C~)zfT~e|LS# zv4vv?bnn>{v`bUx4$VT+9fKzAI-ubhaMkMCW7e?`y4F>@qxNa~{1HGsk*D2J*}Od9 zrn~teU0KvJ8xP{x4p**Sf$JB$D7!5KagxJD?!6ypTvSV=TdcQg`RxH|Jq|zt0+VX8 zDS+Q>jI0u@#%Q{vO z==J`IdErSzzPs|0dSmJF^xA4iwPK-tzlF)@VDu%#FCugD3{zyd>fW_^R78(ynxF~SK=aIR}5qje<7AE*=!%gwp1O9RUD0-NK>WV$~ zYx4`ZN`<)6jLJZC*NAW z3}qKzRc>sgtan07o$_qTFKxPe(5zEjyV}hK##8N3&%u+MZPkig1I0@#|8-98UG6 z8v(vY%M8BWDemeJ3GLl~#`gZ7JAY!OHk;LD?RQP@vAp~GXIEbLQnX zv$M;jx0zpl`saNA*23e7lho(tpSu#wrTMS!m{71_kE(>bL0qDi(q@CGw8SlE4rR=2 zO*FdBmCUK1Xo7?!*1IL1%l0>zwHpno?`gRRLyp-|sjovtXLKd#9JFZq$kKdG`+RWH zg?}eMPm$2=SHEK$cv^VtoA=gH!Q9UxEllixTBx$6mbs+4r<|L)G-}?8r=7JC!jI=j zl$2#{FmG|Jl=}RA-gfTwr+-G4u^toZQI_yG*m@|0duDCpMwZ7MZ6_~l0G%&+TA*iJ zT;B@STIRJYRll{V9(Sx(elM-cw$}CDYpG7XqtK!=i;*q4Q|~NaGRa3T+hiiHfp755q5;6Bt>c|4)8v@l^70>aV3Kpzf<^II+_s)~wvi{AT8v9`t_nG-KXEQBM z-3E*_ph+*@ET{y>-!~UI|0f^2EA@I`n`fq8d*7~D%QahX*7fbV^4pVl_~mbJw^;h} zY=yb~rrlP2$y|3&-6%_+mwMq4+o}BtduPn-PJWT(_3?}K$E?&p?FG_jyMJ%|HG{8E z=J2;;C$2vK9^QWT%DY<^X1AulYtJkH`r7@)=c~)VhdW!JTQyz&_(O@~m(O3*{`};$ zO!k|$zjp6@YGrq6p7ZyK@4sj=TXE^bZ#EZB|887n^5tWe@wVHuSFhxrT@zBzad#_E zfJTsiyFqxQZzj*%e+K94ieWKgV literal 0 HcmV?d00001 diff --git a/internal/worldgen/data/structure_template/ruined_portal/portal_7.nbt b/internal/worldgen/data/structure_template/ruined_portal/portal_7.nbt new file mode 100644 index 0000000000000000000000000000000000000000..3b3b890b56249bba5d93213e28eda89294b10dec GIT binary patch literal 1831 zcmZuxd05hS7|*#%_mn!=j8Y+e681cn;sM^WXLe~GPZ?7ino5~Oo)liG)11WPHP>UJ zrfjaP4Lr#Eng@zyqFW|jMRO*$Kuz&hWM5FTw()%bcz^Hvd9TmseL$qD?NI*Y*?0IM zI(XQZWfQ%(&VGAVUt4YFlkz}5L|^-6u!R$njvvUd>obT#!!<{fbx{#x)7MKr!E%1l(bs=!AcU3)T_^4xUbi=z zmr?(6T|4*eUV+FT&J@lZ@GxZ9v|6YayP|!PymxgQnA*Bn{OiSj_v6RVqsx3}|Q*J!3cYwImY6<&_pPLJex2OxMM#)6iLtj37kY(N!(v zu=3-Pz7jBq)NMPe;SUl4VAHpeL{;Ca{%SXJR&}fC?f-Gl(d+z*wjNtWJEF;t9jtA( zFRhCIl_-gGsfLe_=LgTI^9o;dRxkr3-hN^dq>}{I@RxXpNtUBZv7AEVaAQW! zYPn5oBf&i`i7u#cVejTm;i@%P+w1%!!T{zCN$dzKThhOfJ=RDqwFxgpKK5F~b~Myv z%RIX7Gxzb;i`~$!NhGMtKJ;_*92IU;=^;kWK#_gxGxcI8ba2w&H!uj1W`+k!KtKuh z^cgaO_^ttb^dD705DG@I1z82k*MRbSDh>|Qf&)sirk?o>mMgpN6nj6iKJ+Rb6RCVA zP_i#lum|dsdNIJ5@fu#`(%oU+GnJd&z0<=G*3j#D0_( zq@cy9VIsb5Lxv5?HTIWGEa|JIE_^vLuJCI@ImsH4e12xBBj>v z-GzzlDhfu1NL7ih0kM0w4nfhN$bJ_w{ep%+JFEMH-d>rxT!+#3rB=3K&dxPl3p`T6 z^|~Z5vrSW)7XmXEcpqU~y;AcWVdT>kHR6%Wpx`mSe|FeATR*Mpfps!R8$+Yn=lg-6 zCPGjoqG^ipQc5k;DKi%V6XQpSXaP&4#f$0JZSyM2<6=iQ?LI~2a})(HoMH?OEvN*j zJOMy$MrvKG{u==cXz}U?0BRBBi+z>(bluyt^0>a?rKQ+}`!Mp@*tD!5fSs;0qe={Z zev=8*nKXrwiC~N1D`1OsKA4yMdbf`Wglhq%Hm07p7%Zpm zokUl1tm+5TsR0+rCxKd+l8qI7+P3X?>llOk{{W?kl)En_a(pp31vR^QF_=DL3FuKU z)I~rGausI!T#J`ub;JvQwmx4o4Rd!nk(1KB!b!0JLw*lTl`T}X3p30#oeej14k_+~!C|(R3ic4*e(PnbsWV%~vY&L`C+>JLB{R*Mr znWj0Ima$%UButEus!E^s&NL%#kRWN8AC?mzFL92Wfi5Q%Ydv0o0VkjnxD=yq*&E@) zX~5P>=UR)GoB?hNlp>x#@o>nW{mm6Hmo}KIz9VoNN|~qEt+W$lEJ{I648?kJT#_?M zU5~3JWR9;cviMO|Iw2xv=@gGt7(W;{DCf_2$f%Z@;@{4)#IyaM&N+wa$`1~$AG#Qg zV|PZwdlwTN#HXaW>OmQe#kz#X4Co!{Ue$5nB= zbGKkBMdrOi67FSpN&J~;iA#ZaLbbF{4>IJk9E~whW5TZ?!05iKeR-Qr2qf` literal 0 HcmV?d00001 diff --git a/internal/worldgen/data/structure_template/ruined_portal/portal_8.nbt b/internal/worldgen/data/structure_template/ruined_portal/portal_8.nbt new file mode 100644 index 0000000000000000000000000000000000000000..e2ea4352e680945c879dffeaf108f74fe489faf7 GIT binary patch literal 3408 zcmZ`*4OElo6}D2uf=yg20xgo+Ih+$ANFf9YIh#j!ID)KlEKnd2Q=ll4Xi)ycKryq; zv^D2o1PoI1uGhBm2&pCgoD3=pXW#9$CI5<&>7CWznFjP;R_9tEVD$p%7?=ScFe;AzmSCO_!e&fjv{)yl341SL*wcQb) zujkAdj&#jF*zmXJv84O^tsKt^Fa2O3-}u@_Dk&P(>@=Co!_V;Lr+aU!ZxtEK+b`Hmm0u$bjyDJ3LoYQ*Q?1MJ9i4A`?I!Bh z^Awxl35(RdN37jBTGsZJlGZapOgh7V-#@r$TiIUIUSc%iEkk0{>^5X;#1EMpdCrm@9OB*W!KYSj8{B-)1>tCy z_wj$;>`GqQGLJl8O9Jt+r;NW7nWnS z`49YapQC&)sm6uh&rZQ6+6hi_hsWNb+ho%Px16NY-=|~8O^L;J(o=WRG3D!YQ(sj^ zL)!!DpklymqY;{~gOk3yF2PZCN!b|J6D^mVEG&3jX)DXi$T4^9!OpH5wZ*6RyVdbHaQbxGhp;3r?y%?G=gV4rV2Wu=jjg>L^OQ*za|^ zBo)J$nD$;{8GpZTcr1GQZ!k+n?8{nCb*hS@1hwnwg0Y)3!Jf5DMqZG?lb=dB8)Oh_ z??s1k1129Ut70f8X!^Qp0mdxB(o6O?u>G)b@}VV|{Zqc2**nJytwteStUc zevc04Wv9ve<*IUE%iG=iqCs6dm#&f*ij4Xrw15gprlNp#X9qZdy-YVHvNk57MVG0c5)fQR@-t`{Jat5e z;LJm+-K-GLaq-97dnovF=2)Rx8Jo z_QXC2uwAP;;ijW&1&CB9J@pv~)4*J#9V8=Ra{No0+U#m<+%uaetV~=3)Z@K@=bU1T zqq&6_y%3?>7<%&@L@uqVHs%T4l9vvZXUtWNj5K!O=ouOAwe1m9fqa5kXfsARdoP0O zLm?Jk>BMtK;&oO~tPG{I1dJSH+zv)49hd@RYptczHRSAtKfx>@EIoyto!cokmo3_^ zGILF@^M5?p_7!!#bXwFoWS=l;D;u|yR8ny-@|B zv?!Dag-U_DcsP74UznT%c@-Cf(nm)(OKTcYWqxgnhLIBCLxQtFf*a3bq3IOCMg}zk zO9S4()t_itaLx10k;RLOi>ga@r3&z*S-xM#ir8MT~)1gH{7 z%Y%lyVvXkVZ+%Luyh7v<6$knI95sLJ7;bv>DhLKJUgKwhV}>z*70Tk22JsXz2+zG^ zJNL8$kXpdylaq@|4PPo4sXy6B=l*pFIE(LJfXdGL_;Y98eX;Z@Z>iHi=`lEf6;95* z3F(;6LptnsOW`Wjh%N)clK>u^s`vO>K9xk+90i(Ip741dza4vI(F*Q62xW7WHB-$DpoA;Xk_a&k4ljeZ$KSC)|0+HPLRxWo5uX_YWq28<9+hD=PYYPXE)b~} zU0E899akwLQlM6d5_x4Q+$|H6l0OqR56GiITr%-ZcvIOgpV>qw_e0B-90z4~-Repq z_XEm#Z;&MoWEmd;4dSE#Qy?;hBBh3b$@n9f^tY8Xx2%g}P5K23C?c{uLA?d&J10Wmi?vGHg!xX@B6c`WXgDAftJ6P&R|n8l z3(0RG=3W@rJYf0*PkWUFRn)sw0GWW zjehMcd0-1z*N&#a0&??i$f|TZOe{Scu`saUHfGixoU)<^+&v7J8(Omknuy0;y!GE* zWk1O>-vVX<7r;~S9pHr`P3-jK)I!3VAf%WbKxDh(V%2 zr<=Awh#2^G61IL11k~{5Rmt!K67>&YT<3gFjz*cNQDg>EV?Bb6>KLwp63uOev*2SYYN- zeJaGxxG^zf(y&iAcuB+=esmBZ59p$$@Uk1Vb(7==yI?9JE!6Ayk0_JDJ4G@e?{}excJbcU4ApMloF$0&42K~uQl0V zTFl(0GY`ub@9aI`=dS+Pv^e623FeI7WPQ-7W92qwA6qb{$$4P1F VpB6C|`{v&j9lrga0{*&n{|8(0&RGBe literal 0 HcmV?d00001 diff --git a/internal/worldgen/data/structure_template/ruined_portal/portal_9.nbt b/internal/worldgen/data/structure_template/ruined_portal/portal_9.nbt new file mode 100644 index 0000000000000000000000000000000000000000..12afd6dd0cbbd2036d7e32f922b466c8359d81b2 GIT binary patch literal 2169 zcmZ`)dsNbQ6mLCd&4vzEu?8};ux-WY*b8;C9keISXE9WJAwaTmElUtt`N9wVjGk=Wkke^$cy*M9xd_{3<{{Dc_%GG}J zo>hrfeE(UH*OEM0pSarWw<|7W4eJVGwMT@kNi5|sug5C|oSh>5joB>C@BYGlmlT_0 zikf@9w>nm`X1W=_48OJVxA`_2C;=Mp!3T&y{pG=J+sAtJkz&`G&AN z@t8P$qql#(crZCp_=`MJ5|omUWuE0;*geCd(DY8n(W0pksy$%28<3PPJ4ZVc zO3ezrANL-PGgvZ-$EhJyuxBk1QR{;Ao@s)RxT*gKZGtFW+dQcCO$*q0=-GpM0G;aF zMY8nh#)tymmUzj-=dYSuz{_FayBaL+T3m z2=m}Wm=m&EUV7e*6>Ml zxR{8jSrP``I0ZNX%Jn8xG*GA_2)pN*)-w@`Rh1JwiW(<7e`BVhLG3#Li!B*B4E3f( zyFjGz=5Wn4P$1rO17L6x0ELV2MvSF+_3~6%531#dYGFW2euNfE2CaBhiwIh;7zpxd zZvoC2PQ@tYa5)^JUm70t@lp!8QGOd*U9@t>JoK`3n0P*psXaczHI{yP@VHsb9WRRk z)zOUCnWD*~{1OtHJjfk+mJCom9?iO8TmlO2t!kYH7*ofRI%71HmQhMnWt(#E!bWII z%}Js*{_B^Qce>@S9v_W#X`IH5g-(QRg%*v|$q{G)>*vai-m$gfP{AD5*~%%r!{);G z(V=nIJ}-sA2#A5nGN8lu3PPL3mM()y?u0F{B|^Ioo1M)X15`Bh4R49ZcUd!uTBQ<3 zy^!BXqR+mA;c?U%uxPd64t+OGt32S<80i8-3_g1BVPr+<$hKG_&-G7&SsyNE>z2}~ zbuzshbW;!oV(0<@DAGyw)Bd|7?iPh~eUi^^V7iJjS)oZvC6E{Lp-BRPvq@|gntm<1 z!~$UCmRMScB^l(P-L zhEnT%7S=DwKr>)$hSMkDwC_bQz`+B;p-lh@Gm)nV<>t0)TkJza>U-EU0UgpX83+~r z3H$dRgV#TszE~uv0DBqm0QvJJK&^ArqkjYt3BK5Uz!wA-OhAVPdlL%Isl)(qO~baA zfYZo=g9!udS=(ib*~Y(+8>aNK$E>dNhYprCO|Mru^iL6+shDJM?lG^EDintrdbL7R z<}>T%LysoGbnJ^MYg$L92+h%?W2QD0H~1c> zOt?P#YR__hpK-v7e7NOw&=z7a&h!u_aAC^w#nsdEezBN&?A4Wo8= len(palette) || !palette[stateIdx].valid { + continue + } + info := TemplateBlockInfo{Pos: pos, State: palette[stateIdx].id} + if _, hasNBT := blockComp.Get("nbt"); hasNBT { + info.HasNBT = true + } + blocks = append(blocks, info) + } + } + } + return blocks, size, nil +} + +func readMaybeGzip(path string) ([]byte, error) { + b, err := os.ReadFile(path) + if err != nil { + return nil, err + } + if len(b) >= 2 && b[0] == 0x1f && b[1] == 0x8b { + gr, err := gzip.NewReader(bytes.NewReader(b)) + if err != nil { + return nil, err + } + defer gr.Close() + return io.ReadAll(gr) + } + return b, nil +} + +// TransformBlockPos mirrors StructureTemplate.transform(BlockPos, mirror, +// rotation, pivot): the mirror flips around the template origin first, then the +// rotation turns the point around the pivot's X/Z pair. Rotation ordinals are +// none/clockwise_90/clockwise_180/counterclockwise_90. +func TransformBlockPos(pos [3]int, mirror string, rotation int, pivot [3]int) [3]int { + x, y, z := pos[0], pos[1], pos[2] + switch mirror { + case "left_right": + z = -z + case "front_back": + x = -x + } + px, pz := pivot[0], pivot[2] + switch rotation { + case 1: // clockwise_90 + return [3]int{px - pz + z, y, px + pz - x} + case 2: // clockwise_180 + return [3]int{px + pz - z, y, pz - px + x} + case 3: // counterclockwise_90 + return [3]int{px + px - x, y, pz + pz - z} + } + return [3]int{x, y, z} +} + +// MthGetSeed is Mth.getSeed(x,y,z), the positional seed every template +// processor derives its per-block random source from: +// +// l = (long)(x*3129871) ^ (long)z*116129781 ^ y +// l = l*l*42317861 + l*11 +// return l >> 16 +func MthGetSeed(x, y, z int) int64 { + l := int64(int32(x)*3129871) ^ int64(z)*116129781 ^ int64(int32(y)) + l = l*l*42317861 + l*11 + return l >> 16 +} + + diff --git a/internal/worldgen/structure_template_test.go b/internal/worldgen/structure_template_test.go new file mode 100644 index 0000000..63aecb2 --- /dev/null +++ b/internal/worldgen/structure_template_test.go @@ -0,0 +1,66 @@ +package worldgen + +import ( + "os" + "path/filepath" + "testing" +) + +// TestLoadRuinedPortalTemplate loads a real vanilla template from the +// extracted datapack data and checks that every block resolves and that the +// transform keeps points inside the rotated bounding box. +func TestLoadRuinedPortalTemplate(t *testing.T) { + path := filepath.Join("data", "structure_template", "ruined_portal", "portal_1.nbt") + if _, err := os.Stat(path); err != nil { + t.Skipf("template data not installed: %v", err) + } + blocks, size, err := LoadResolvedTemplate(path, func(name string, props map[string]string) (uint16, bool) { + // The resolver is not under test here; accept every palette entry. + return 1, true + }) + if err != nil { + t.Fatal(err) + } + if len(blocks) == 0 { + t.Fatal("template parsed to zero blocks") + } + if size[0] <= 0 || size[1] <= 0 || size[2] <= 0 { + t.Fatalf("implausible size %v", size) + } + for _, b := range blocks { + if b.Pos[0] < 0 || b.Pos[0] >= size[0] || b.Pos[1] < 0 || b.Pos[1] >= size[1] || b.Pos[2] < 0 || b.Pos[2] >= size[2] { + t.Fatalf("block %v outside size %v", b.Pos, size) + } + } + + pivot := [3]int{size[0] / 2, 0, size[2] / 2} + for rotation := 0; rotation < 4; rotation++ { + for _, mirror := range []string{"none", "front_back"} { + for _, b := range blocks { + out := TransformBlockPos(b.Pos, mirror, rotation, pivot) + if out[1] != b.Pos[1] { + t.Fatalf("transform moved Y: %v -> %v", b.Pos, out) + } + } + } + } + + // A clockwise quarter turn around the pivot must map the corner (0,z) to + // (px - pz + z, y, px + pz) per the bytecode formula. + got := TransformBlockPos([3]int{0, 5, 3}, "none", 1, pivot) + wantX := pivot[0] - pivot[2] + 3 + wantZ := pivot[0] + pivot[2] - 0 + if got[0] != wantX || got[2] != wantZ { + t.Fatalf("clockwise_90 of (0,5,3) = %v, want (%d,5,%d)", got, wantX, wantZ) + } +} + +func TestMthGetSeedMatchesKnownShape(t *testing.T) { + // Sanity only: the seed is signed arithmetic; assert determinism plus the + // documented shift structure. + a := MthGetSeed(10, -40, 7) + b := MthGetSeed(10, -40, 7) + if a != b { + t.Fatalf("positional seed unstable: %d vs %d", a, b) + } +}