From 37f626c8becaff7313b91cdef9dc3aa7753f3615 Mon Sep 17 00:00:00 2001 From: Andy Vandenberghe Date: Thu, 24 Sep 2026 12:48:16 +0200 Subject: [PATCH] Add the authored eq2_unknown.png placeholder (avathar/bbguild#391) 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; 38x38, the set's most common size to match the set. eq2_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 avathar/bbguild#391 Co-Authored-By: Claude Opus 5 (1M context) --- images/class_images/eq2_unknown.png | Bin 0 -> 1534 bytes tests/game/eq2_icon_coverage_test.php | 88 ++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 images/class_images/eq2_unknown.png create mode 100644 tests/game/eq2_icon_coverage_test.php diff --git a/images/class_images/eq2_unknown.png b/images/class_images/eq2_unknown.png new file mode 100644 index 0000000000000000000000000000000000000000..2d3996db185d4b14896759e578cc8ec03b967a24 GIT binary patch literal 1534 zcmV_s%&ycwKm@ zKh~+mL;ljd+^f0gchC9HnHk`=ZtKe}-3Ax{8h|Q|FFvDH2Ue7aOPS{77&{AnUOoG%3S$;TeR{ztx{6IBsd zE3nKU07L|>6^gz^SNDJ%Ir>g8n4EONVfV_c_`8ON(rExv8Vd$oB2@K*L^zQz1i_)F z+VzuIV6ZrPGgB8bPW^Z~_s7yV zR}4k~q^7FkWM-zY@xcC_a$x_r)MzRLA|Mj+(AE8ky#D9+?Az~N<&XPs%7(g_N>oKL zmChl)HjEwH8}wUeE?Yma_teFsZ;qvN!=;i+xnK$aH*ZYbl`q;CsuK}B`=h;zS->KQ zMm)TH{9PP;@vqc#WmsMPcvSuK;($7Ht`{x$Zp8Y!7^dgEh>=u=yL+yyP{>S-P37Kc z1N7hmUQ-KEwUyqBQ7!pGKs)c-WE;2Cz;zAkYASJ|dq9qz=#Wz*v~t50E^MA(2Y{{hH9xafp3mlr-2PCL(prI8V6;MVWJ>&i zB^*{a=Bv@MKq(i?7z3bdL+S3#b(O7N$ZZdWob}AS-ZE+x#AEQWz8jNQw{1+k%9amI zXA1JuAMR0)w%>!y%-o927kvEX^uOUcnh0|GRyJ5#DD~SK5~qTI_mQID;E(s}hriQI zQ`55$5hSW2`1vbm?JrNB*U_l=NvL3&dqzidiDC0d_ zrv0HN+1=VmVL>-45%L^- zI+B*NoxP}9Taob@d-|?VT)Gc%Iskt;0m=oNUrmMxFph>&3XUP<^F.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 eq2_icon_coverage_test extends TestCase +{ + /** Gaps that are known and tracked; see #7 */ + private const KNOWN_GAPS = array( + // none — fully covered + ); + + /** Same, for the grid's artwork directory */ + private const KNOWN_ROSTER_GAPS = array( + 'eq2_beastlord', + 'eq2_channeler', + 'eq2_unknown', + ); + + /** + * @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); + } + + public function test_roster_classes_cover_every_seeded_class(): void + { + $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(27, $this->seeded_imagenames()); + } +}