From 95fc70ead57ead9bf44b2cd85294855f7d82fda9 Mon Sep 17 00:00:00 2001 From: Andy Vandenberghe Date: Thu, 24 Sep 2026 12:48:18 +0200 Subject: [PATCH] Add the authored ffxi_unknown.png placeholder (#6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit class_id 0 — the class a character gets when a sync cannot map it — has always seeded this imagename with no file behind it, so it rendered as a broken image until avathar/bbguild#389 added a fallback chain. That chain ends at _unknown.png, so without this file its last step could never fire. Authored rather than sourced: no wiki or API has a generic "unknown class" icon, which is why avathar/bbguild#391 calls these out as the one part of the icon backlog that placeholders are the right answer for. A '?' glyph matches what the family already does here (bbguildwow's wow_unknown is a red '?' on a plate, bbguildlotro's race unknown a grey '?' on gold). Warm gold with a dark outline, so it stays legible on both light prosilver and dark styles; drawn at 8x and downsampled; 17x25, matching the set's portrait aspect to match the set. ffxi_icon_coverage_test parses the installer's seeded imagename values and asserts both icon directories cover them, with the remaining gaps listed explicitly — so a new gap fails CI instead of going unnoticed. Refs #6 Co-Authored-By: Claude Opus 5 (1M context) --- images/class_images/ffxi_unknown.png | Bin 0 -> 605 bytes tests/game/ffxi_icon_coverage_test.php | 91 +++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 images/class_images/ffxi_unknown.png create mode 100644 tests/game/ffxi_icon_coverage_test.php diff --git a/images/class_images/ffxi_unknown.png b/images/class_images/ffxi_unknown.png new file mode 100644 index 0000000000000000000000000000000000000000..c1bdef3e6bd4384328d5426be825a599b57b9d45 GIT binary patch literal 605 zcmV-j0;2tiP)X1^@s6*FMFU0006cNkl58I#Fmk~EVsR?{Ykg`#2wBQE^u_ydZ&ZnDry7cQir3l-d22qINMtcZxa zqNrd&-LzVF!G)U<5-1h|E)3NqBxyQHlO~gy=fbFINJ8Vf-|ju`yWDd)?*f~20|CG) z00)4rpSyzn+lj1u&UP$rfB|3*NRl&GE-L_z0PxF#P$=oT)@q{W3x*Jw9tVHU<+R-m z?(_HgdR@pauR@I_^2xMzC%0Oeq?9rM%(hd>BCiB@`{>8#>gE(V4mE zAqME#+3zVa5}Ces>BP3-!vobP)1NG2_SOf*9=149rjV$Cq= zhuN6<@~ujWW&PIA?25Vvur$kHIz-l3tLR8&ijXA{b_*AnxCv%!3m@7xDU_<#exI9+ zT|I3a-0wHF;+j1iZfzvjQVs>Yk|P;iSi<>{Dd9&l57{GrU&<7^|HiUQH~`?J$a6;( rMdAK`ACQghjE-?>s4+HI?8f;8fOWlf#ji=;00000NkvXXu0mjfrqC2W literal 0 HcmV?d00001 diff --git a/tests/game/ffxi_icon_coverage_test.php b/tests/game/ffxi_icon_coverage_test.php new file mode 100644 index 0000000..be46468 --- /dev/null +++ b/tests/game/ffxi_icon_coverage_test.php @@ -0,0 +1,91 @@ +.png for character rows and + * roster_classes/.png for the grid, so a seeded class with no + * file behind it has no icon. avathar/bbguild#389 makes that degrade + * instead of rendering broken, but the asset is still missing. + */ +class ffxi_icon_coverage_test extends TestCase +{ + /** Gaps that are known and tracked; see #6 */ + private const KNOWN_GAPS = array( + 'ffxi_geomancer', + 'ffxi_rune_fencer', + ); + + /** Same, for the grid's artwork directory */ + private const KNOWN_ROSTER_GAPS = array( + // none — fully covered + ); + + /** + * @return list Every imagename the installer seeds + */ + private function seeded_imagenames(): array + { + $files = glob(dirname(__DIR__, 2) . '/game/*_installer.php'); + $src = file_get_contents($files[0]); + + 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, array $known): 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, $known)); + + $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', self::KNOWN_GAPS); + } + + /** + * @doesNotPerformAssertions Skipped: ALL — the roster_classes/ directory does not exist yet + */ + public function test_roster_classes_cover_every_seeded_class(): void + { + $this->markTestSkipped('ALL — the roster_classes/ directory does not exist yet'); + $this->assertNoUnexpectedGaps('roster_classes', self::KNOWN_ROSTER_GAPS); + } + + public function test_the_installer_seeds_the_expected_class_count(): void + { + // Guards the parser: if the installer's array syntax changes, the + // coverage assertions must not silently pass on an empty list. + $this->assertCount(23, $this->seeded_imagenames()); + } +}