From db30d1a7d789fbedf82be29352fdb3e802c417df Mon Sep 17 00:00:00 2001 From: Andy Vandenberghe Date: Thu, 24 Sep 2026 10:17:25 +0200 Subject: [PATCH] Add 7 missing job icons from XIVAPI (#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit class_images/ and roster_classes/ each covered only 17 of the 28 seeded classes, so 11 real jobs and class_id 0 had no icon at all. Adds Astrologian, Dancer, Dark Knight, Gunbreaker, Machinist, Red Mage and Samurai to both sets, sourced from XIVAPI v1 (/cj/1/.png) — the same source the existing 17 came from. Matching was verified rather than assumed: a v1 icon box-downscaled to 24x24 differs from the shipped ffxiv_gladiator.png by a mean 11.1 per pixel, against 31.0 for Lanczos and 41+ for every raw game-icon range tested, so the pipeline is v1 plus a box filter. - class_images/: 24x24, transparent, matching the set - roster_classes/: 40x40, art at 38x38 on the set's gradient plate. The plate is horizontally uniform, so it is reconstructed per row from an existing icon's background columns and alpha; the 38x38 inset was fitted against ffxiv_gladiator/archer/bard rather than guessed. Reaper, Sage, Viper and Pictomancer are deliberately left out: XIVAPI v1 predates them and the v2 raw range has them in a visibly darker tone that would break the set's consistency. ffxiv_unknown has no upstream source. All five stay listed on #8 and in the new test's KNOWN_GAPS. ffxiv_icon_coverage_test asserts both directories cover every seeded imagename except those five, so a new gap can't appear unnoticed. Refs #8 Co-Authored-By: Claude Opus 5 (1M context) --- images/class_images/ffxiv_astrologian.png | Bin 0 -> 855 bytes images/class_images/ffxiv_dancer.png | Bin 0 -> 996 bytes images/class_images/ffxiv_dark_knight.png | Bin 0 -> 805 bytes images/class_images/ffxiv_gunbreaker.png | Bin 0 -> 1262 bytes images/class_images/ffxiv_machinist.png | Bin 0 -> 940 bytes images/class_images/ffxiv_red_mage.png | Bin 0 -> 777 bytes images/class_images/ffxiv_samurai.png | Bin 0 -> 1309 bytes images/roster_classes/ffxiv_astrologian.png | Bin 0 -> 1532 bytes images/roster_classes/ffxiv_dancer.png | Bin 0 -> 1909 bytes images/roster_classes/ffxiv_dark_knight.png | Bin 0 -> 1595 bytes images/roster_classes/ffxiv_gunbreaker.png | Bin 0 -> 2311 bytes images/roster_classes/ffxiv_machinist.png | Bin 0 -> 1815 bytes images/roster_classes/ffxiv_red_mage.png | Bin 0 -> 1487 bytes images/roster_classes/ffxiv_samurai.png | Bin 0 -> 2478 bytes tests/game/ffxiv_icon_coverage_test.php | 91 ++++++++++++++++++++ 15 files changed, 91 insertions(+) create mode 100644 images/class_images/ffxiv_astrologian.png create mode 100644 images/class_images/ffxiv_dancer.png create mode 100644 images/class_images/ffxiv_dark_knight.png create mode 100644 images/class_images/ffxiv_gunbreaker.png create mode 100644 images/class_images/ffxiv_machinist.png create mode 100644 images/class_images/ffxiv_red_mage.png create mode 100644 images/class_images/ffxiv_samurai.png create mode 100644 images/roster_classes/ffxiv_astrologian.png create mode 100644 images/roster_classes/ffxiv_dancer.png create mode 100644 images/roster_classes/ffxiv_dark_knight.png create mode 100644 images/roster_classes/ffxiv_gunbreaker.png create mode 100644 images/roster_classes/ffxiv_machinist.png create mode 100644 images/roster_classes/ffxiv_red_mage.png create mode 100644 images/roster_classes/ffxiv_samurai.png create mode 100644 tests/game/ffxiv_icon_coverage_test.php diff --git a/images/class_images/ffxiv_astrologian.png b/images/class_images/ffxiv_astrologian.png new file mode 100644 index 0000000000000000000000000000000000000000..cc6459cf6f793a2ff2f83e5571f6dd9c29c51ae8 GIT binary patch literal 855 zcmV-d1E~CoP)8e}z-g8}~Bdw_<>cW)=?xxPU_0{>$ zqk!)=gatk$#)w2B6AB9pL!Ua5$)u>Ot7`?2`f9+!NLl59UDX#%1FS!qp}yhS_4!L{ z<^u5i?wdma?%t0(`wty&4CMqvCS%Mp4Q5)*2#?6inK7-TAZT=z0>}r@0l>%{1wao7 zsI06UU0z;39sr1lClZNOUbkmSDVgIqIvPFO1fVDuT^HqC=!%M8CvMwZz5a)goRN6b zk-x9@&2wB?_< z@Zj~)Q-9aKYVA0Z$;dZ@pqK6(>7;UT{dVL3j_XOwG!1410u#oK2$d8U+PP+G8i3(i zYvkA>*cgARwjQ{AI9A`Z7eKsEo`C=XKpE*Y*VTWXUg^4`AroiJ{_W?bwcCIHLn+As zn3@Qj3+y;}Wq+)`X|rFUeQTh#hUW?Cy5c#2=Dz1#h{r?(B255@v_|6Xf6mQ2@e^Ov zMN*L#O6g2Dd@v=o>;)(dKxV(_yeo zgAF!MegL=Tud7>ZD=-GY%a0sF5fMQX^=6wPK zMWmp50rEvl2fzF1May8TqF)AcW=${Nw07ZXCR!&0xCEfH&)1dmM3zKtD>l|G0niSh z=d%DsMd8sKS4GYjm~EvVn5wFpTWZ_+LI?rQxf(mlDlrTO-3w3vt_`(nvH`4S{gNF# z*)r+ujplzB%$XeCxpwAp!?ZKQ5r7g7c)A^3Jr4i0!-fP*FZ<~iM#-wxTQ2UhOqO&# zx!ksxogM5+l09iJ9c%dK#9*@cuIt6~rfj?Sq$Sz>s&h}~>%stnSuq-b1mFVb0zhB- h^C`=`@Vk9mdk;FEf4VRamBIi3002ovPDHLkV1l^lkdpua literal 0 HcmV?d00001 diff --git a/images/class_images/ffxiv_dancer.png b/images/class_images/ffxiv_dancer.png new file mode 100644 index 0000000000000000000000000000000000000000..39c894eceef071b54da5c967573769bbb601b62b GIT binary patch literal 996 zcmVhE`t&o?I~3jcWR>hwb|J*(aiAaT=}fo zTBlShCEMy>)Mln)tB5I-)XJJn2>j;lM#84rS6bl}osB6S?`;<^9)caqYFy@$lZmrml9gV@Q&~EXmJKE}xtV^!(EQ`<1zFz|Kq)uM1E6S^Ex~be;CA zdH`{m^o z@!t&!0AS+Iy^8?C0DK&R?F|M)IA86B0&WJ)~j_-Rb4Xgyw`GIqmLkn z!uc!}w^*6+Nw?umy4$GxwV*(rKATXZDUWrC#=V~h- zzhKl4b{n?R+KOxgfTVwe3ZG+O^annVU96HP@W>w@6gH#L2kB&qBo#Znnb(a;?-9~p3a_ss{_ z^A3U_ioQO$!GGlA+HnA~1^+T>njZ5`_$5C%uGy~N@SV6HninECvmpMzi$4H<>~~K2 S38kC>0000*lt9H=`>Pm-C|M_I=NJIPW><_X7XBEKRw5B_$zv zSvg}hr&2F&HHKn}!9g#}q_eT1v8KdWnNqDS!@}E>?cfB!0sy(K3if-s2R2H?PaCRB z64`7v3&1W1dj`kRfi46<^Yrv|tSo;K_pm@uembYdXz@l36R{*+l-5OLwQ|R6hUk>3bRg!c+qQV1)$m?q#K&OcV;(G?0cr z2c5m+OW6fY;^DEGTmb5SZL-qaH{=zC=By$OI*_CR2GRhMBw^3uD}4M#lFgEDYTz#r zI5$=m=G{mRZCO%I+832|%5-`?M#dCGeo@;fMW^6qUXAFeD9yVGz{aHINWF9+=w02T zVR2U6Ttv9A62OVn_=xQB&P?)4YsQ)@%l;UhN{u@m<=0wTaB-=n=JChK$fLY1C8yEA zkBvUu;Kg-H2jB~U7Ju%He|K}v&zj<7qNC=TuC4ZAyGZ260l)wdcF1Q>Zp`tZ3IHx! z@t^{r0|0{o_v+F+B_my8b#+nFGTV)r5+2NdGSHSz4s;aCUl!fE4}fVL7RE+o*r*5V z0M-D2AfS;{)w^ib(>B(0=KQo`NkU`THd|U!U?3*l8tPyB05BMrjsT!Hl|`$Aqw-#k zD>EuQcrTyF6IjPai_Y88G@SB=zQJc@T?qhYwhO!Y!4ZW_6yOmZ8psc0(ls7E{ZiHK z{I`kg>-v7Pz;0HlZmezk_C~K=q-eDol-Ko@uB>R<%}G;jd(V%`)io_SuUc7u-z}9G z-`;#QotRquDw8iPD^)8CI-PEGCvZJADObr9vuY^-)lL`?6S9S&p|RQCjmFU|I jvT?h2OxC7;vpcTe5v?S3U;$jx00000NkvXXu0mjfeYLF`NV4%$yq&oJfe%85x_p zI9>dTaefR%ML-Zy1{qTmsZ3-DE|rf$TiQZf=zHJ0AEQZ!7WZe#cK*D%H_!V#=bU?< z9N<3`%)iFGIwkI8H4^cJ%UhA%?j;JDwccHy17_qFHVdwcrnmz-CebMC?)G~lEGlPJeC0e+r4?o3+7JFmx|0&IJ@OZfSVLrlju(Q z^|s=;`BOJ2yv(x<1|!{=_v7B`x|YoVSXTnVqu=Fjdp$zF1?&K?-(;aT8{7KswRAo* znQcai#9bB|=<7dk&NPL$Ow90wo*?!?u!KWl@s+CYPu8sjpmW*pf}FKk9gTZPdwo7V zQJiL+G2O2SKo|fq0O80tY;j!V>4)_@Xh%anZEwh@McY?s0L&guC~!W!8u>y&F>%bK!+dt9AIP7DxOen>C#X3S1J6I%sjd0V6upI z`*Xw&>kvy9rXEb}H(N``@&RvGUe-20xjj|cjaLHZG(YU2WA536n2-XY zfH(VWnR+{bhLNdaM&^2^e4IysOd^&V%@&ic-}*a%yCV}L1`q_G1%PF&?Ii}_f&W|l Y3D_nV_jk6FKL7v#07*qoM6N<$f{9*f4FCWD literal 0 HcmV?d00001 diff --git a/images/class_images/ffxiv_machinist.png b/images/class_images/ffxiv_machinist.png new file mode 100644 index 0000000000000000000000000000000000000000..c24baddb0d69009425e073b03dbba87b709d9541 GIT binary patch literal 940 zcmV;d15^BoP)32=4#=0kGKDSdVa$Hvj=CND$YwaWnf@nB-cDZkU6XfPwja32z`*d2Vhb1;7(QOU@pRjTtB+abq=a;& z#aMaO6!^UZ{Css@efW0r_U#8BDYbxMVt28#g@wb1w4(mi|NsBj&{7r_7Z?1iF^&xNaDDroYkFhX(xZa>T)N6KJp59^|Bb_g zeZ07YB+s8XaqcO}7JT^l{ra~3R}a2<`&mI-nOoP}yMN@RxQz|fc`Re1f=qTFf801{ z(XPkx(gJ$2;_SRKV$2TV5g~SOUw=DvE@=FFF$@jHh_tUgz&esG1XR7 zQsr<~&8ivi-#p}$5oL+_{O#9{CvQ1hUVmi#{O|8yhF{;FC`t%&`JroOA;JO{E~a;O zYTAOzb#0IQzWw+YbLslyhi!Ffae?ld(_TEe!?tY0-KQ%ypIdzM?yKeGSn&Vr_g{ad zr9=e}Z|Pq2?&WQkjXN*zbhg!KfA;u3V_4?=s<-dItzlqbxQ{KTGZSM0Gc%*%uU|hE zPoBB-Y3hPQ)1SZoyo!sBS>N4J%hbW%{^#;_2Np9hF#MpXgk@l0P-S3XP}{$u{mSBL zrSljV81%cVeV<(2Tm66jr_7e3EGME*YCgaQKL7diN8HX#-bPbR(aglm@{ghvi|M&b_dYCGb7nRJ1H)@d zEnv9w;Ozr-C6Qn)6+U@Mp})rO-+%hwHg$jBvzMRNU@HKb$%v2l_aDFgWo{tr%*DyV zJbU?x!`t@XC}dz@_(si&kKNQjwlg-^@HhhlgAT3gZdL{c2EGArLI3~_7+xbHlR6Rr O0000 literal 0 HcmV?d00001 diff --git a/images/class_images/ffxiv_red_mage.png b/images/class_images/ffxiv_red_mage.png new file mode 100644 index 0000000000000000000000000000000000000000..6873dd50574dafd8053f4a214470a76d84e8cfc4 GIT binary patch literal 777 zcmV+k1NQuhP)jG6-nKaqf$uZCBEl9njIaiB=C5`e&KAR}XM!y#Cs2m_#)tHzxAP+xz-xf7|6l9E$wl}~Eg0sPL)+#i#kYPdrBO)|7h zDm89PRkEWKpUviu(Z2>p#~qfjks)2qp}4C_TjCFV`QF`lqaf|EgcuHKcyG2>)i(7o zq$7UQdi_z;$Cg*@$avTE8pJ&9o}A=FRZfRJyX1bwy9>Fy3Iwix&8^~^`jRqJA%Hdj zQ6iB*>2$ik&QxGVz-Tn6a&vN1<&m=%E(L2$y}fsqfH>1z!U z{=+9d?tU0_yl4O~0dy=FXf+Q$Z8ZBsHC8wCt*nhx z$R)fsbC(N%aJC-LHUkd87yvWF&>dP0lgQeBbG-w0M@)1?eACTm7tdd>yb~Ux)YrXh zzX!lFYjDgp;mry9*u1PwFZz0W2sdQ4-o%C+Zp&E@OjG&}YruZ*NsA#T?0Y21C}@*bPb@zbjx03ZQOOzoqm zBGRgl(i}JFwOi#*XNP)(Ge1O3lLb6Blv1w4?&0fh*8N$#Z{PmA09>yF;Or^bId{dvC%io%OfJ+R1 z#0}_rjGj~9RyQ=vYvYfYZ6yD*OM7nAcWeew z4}b+A%t(=zq^UTi0Hosq!zPO=UT*u~SaI(52?48?rp#4DiV`U$u-e=XU9a(10L<9B zL?TuxCk4%ll8Iv0t<+?V2h`NH-|HVRy3%JxYy%Mf93WGZu#gfDhY*4&xkS7tf5Q^r z8g1RUXobI!>&G%~EXYV$HNJixpsTZMNG}T&MDEo0aYO<9cuENDD?UaM{YAtpLhu^=sAS9f2J$2e?tPgTWcUO0DpN0dxBjir1`&Fy<-MMcF8gGS3E z!g^v=v9bjK&OQSmED0op;7V27V}sG^$X=~ZNRSD)C*>%&6V?gFWk!pBWN5%RYE7Ls zSviT*ca&v4;BbKKC9)mr#L}O7b!`Gd2>b=yIB#xd3_wTB_EAa+1`U>*C0|@CY3tCJ zsAt5erEiGENh*1uTo%N$*qo1@PPc&&Mp@U`G5llM-I^(JvMKAdsZ-;_L)NMy1R=oz@nh#1hUur2dMG6rwK?e3+V;w^m+k9C<}>GS zA66$vf3yCb*`>ey{Ci#R@UR{LDr-B3Se6WMIGlf^+0?2xjY&0U_1qa@p}`_TJh;`U z>u$S0RQV#e@K;ZdEDsN%tpE(K42X}FYIo-@ z`fhe=Vz4MM1Y9m3jN1mc-3XTTV8r5}b&Zeu&;NG!bZh6}5dcsAZOh~V$e1;K(tAm& z2#qXM94-`a`BuBz-m5q38k-+q?;9|e0eA?2yiQN{-RI}^dOiRr0IRRt{%7$w)U+>a T-TIte00000NkvXXu0mjf))#sC literal 0 HcmV?d00001 diff --git a/images/roster_classes/ffxiv_astrologian.png b/images/roster_classes/ffxiv_astrologian.png new file mode 100644 index 0000000000000000000000000000000000000000..124433318c33310a28cf5fc14a50d792919a329c GIT binary patch literal 1532 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 literal 0 HcmV?d00001 diff --git a/images/roster_classes/ffxiv_dark_knight.png b/images/roster_classes/ffxiv_dark_knight.png new file mode 100644 index 0000000000000000000000000000000000000000..b2e557812a6369271908f18ed687b906e0fc31a1 GIT binary patch literal 1595 zcmV-B2E_S^P)Blg+b}S|a4F_)40f;`Czv{pEv2m#sg?RtsV{wL|AYP?RaL1{)t5A? z+_+L2UurjQ>!`G`69@!6!#Ehs#WTP#Gt3Ng&dj;&KH%obvKf4=vZVcXwDzZOul?)@T)j2ai}hK_K8mNKOo`Oej>3$L&T4L8-Wl-xtK? zvT=Vg$>_-;BuPS5%WS7J7y@*ygwN}zIowpuEmk<_+D@nV=bfu`)NeD;6C_{AlXw&- zUr6Kkl#yi_r^Ak9;<#*}s2oP;3a_h#QDV1_mv#0cM`qKjowOTH7_1Y)+ z?P=b>+(%7K5Xs%aOnjFbSC)=Y83~E6mK}teI*5ir-+RJ#dWR1`o+lC=<(-RfVUrK8 zZdCyUP!yHF+_=Nm#v(uZULT!p_1Nrg5GJNs*-t7v5T=2q>rBtDapUi+jGaEmaRX=r_rZf9>jJtR5^0OZg(H*UQ?svEEJ!Wb)$M@fRm(FM#vRwVUa43ML zDOg*7iX<7FI&Pw?JJ0>j=51qYwn(t1hEQYup~Ty+R{4??_x8wSGYs_B5_BXfqJ|Fgc=Oap9r&&aS%_e5ebKJI-Rdas13tl{plvn;bXjc!p>H5 zn!AYqM3${`#*cC@Qps>|8k0PESV@Z;l;jK9NS(&8pZukWl9f z1qy{cBmDuAPrl&SpDeMou25SSVRY;=q3{N+&GiUjAW1S8&JE-6c9Y)8aP7`4=u3?B z$9Vhf5O}(nkFOGGZo%nvyu1cKz5dY)YcQAFC7)ZtV=r*~AG4(MHb#bq864=NuJ#y} zipEwd#)UCAhF(UJB!s<@o1bME=pR7W4W1@bOwZ1-wz@=5)W^liaaNOp_V!-fZr6(e zUcdflz^F>uG()w#nLhir;)V&l}?tR4V1yZ}}xjqPM4;{=w7y;o~?Zr9#lFG2DBMWNM3@ zbc%~_bz_Q-{90>mbPYi|GQ0SN(y0`d>v1+ zzUAS{JA=q&`Tt}2JsyeClf&HpY>uD)Fo-Cop4l>;zfC1Mb9w@Y!vKs|u>7xR$s1cE zEF>1m>?FSgliG)LO`W+jIZ0#gVUzRA+n)nQ5NX}6-O zYY5b_yuQNe;V`;kvazW#+!VB=VYFnj;P?C4%oympfud9>Do$K3*U=e$xSUQ3B?pSC z@pv^yZC&V4?)kL=lx3OP+D6t=1rkpRL|R&`8-3t*TQX@3MItP(&ymc)*zpipbi{U# z$74yNa5&8Df9@l@nh6Gj)-h-Sf%}QvJKc2#wG*^7uN(55)*6#X_6-Nl9|qQrZfFZ`Um76=u=

{2Ou>AgYCSuAA3M@Y8u;7ux*1qyLY0h>eGcv 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 literal 0 HcmV?d00001 diff --git a/tests/game/ffxiv_icon_coverage_test.php b/tests/game/ffxiv_icon_coverage_test.php new file mode 100644 index 0000000..48d8b69 --- /dev/null +++ b/tests/game/ffxiv_icon_coverage_test.php @@ -0,0 +1,91 @@ +.png for the character rows + * and roster_classes/.png for the grid, so a seeded class with + * no file behind it has no icon. The audit that produced issue #8 found 12 + * such gaps; these tests assert no *new* gap appears, with the ones still + * outstanding listed explicitly. + */ +class ffxiv_icon_coverage_test extends TestCase +{ + /** + * Gaps tracked in #8 and deliberately still open: + * Reaper and Sage (Endwalker) plus Viper and Pictomancer (Dawntrail) + * are absent from XIVAPI v1, whose lighter-gold tone the shipped icons + * match; the v2 raw range has them in a visibly darker tone. ffxiv_unknown + * has no upstream source at all and needs authoring (avathar/bbguild#391). + */ + private const KNOWN_GAPS = array( + 'ffxiv_pictomancer', + 'ffxiv_reaper', + 'ffxiv_sage', + 'ffxiv_unknown', + 'ffxiv_viper', + ); + + /** + * @return list Every imagename seeded by the installer + */ + private function seeded_imagenames(): array + { + $src = file_get_contents(dirname(__DIR__, 2) . '/game/ffxiv_installer.php'); + + preg_match_all("/'imagename'\s*=>\s*'([^']+)'/", $src, $m); + + $names = array_values(array_unique(array_filter(array_map('trim', $m[1])))); + sort($names); + + return $names; + } + + private function assertNoUnexpectedGaps(string $dir): void + { + $base = dirname(__DIR__, 2) . '/images/' . $dir . '/'; + $missing = array(); + + foreach ($this->seeded_imagenames() as $name) + { + if (!file_exists($base . $name . '.png')) + { + $missing[] = $name; + } + } + + $unexpected = array_values(array_diff($missing, self::KNOWN_GAPS)); + + $this->assertSame( + array(), + $unexpected, + $dir . ' is missing icons that are not tracked as known gaps: ' . implode(', ', $unexpected) + ); + } + + public function test_class_images_cover_every_seeded_class(): void + { + $this->assertNoUnexpectedGaps('class_images'); + } + + public function test_roster_classes_cover_every_seeded_class(): void + { + $this->assertNoUnexpectedGaps('roster_classes'); + } + + public function test_the_installer_seeds_the_full_job_list(): void + { + // Guards the parser above: if the installer's array syntax changes, + // the coverage tests must not silently pass on an empty list. + $this->assertCount(28, $this->seeded_imagenames()); + } +}