From 937252c4d18dc4be354d39a3dafbdb3571fd9e13 Mon Sep 17 00:00:00 2001 From: Andy Vandenberghe Date: Thu, 24 Sep 2026 12:48:21 +0200 Subject: [PATCH] Add the authored lineage2_Unknown.png placeholder (#3) 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; 30x30 to match the set. lineage2_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 #3 Co-Authored-By: Claude Opus 5 (1M context) --- images/class_images/lineage2_Unknown.png | Bin 0 -> 1216 bytes tests/game/lineage2_icon_coverage_test.php | 100 +++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 images/class_images/lineage2_Unknown.png create mode 100644 tests/game/lineage2_icon_coverage_test.php diff --git a/images/class_images/lineage2_Unknown.png b/images/class_images/lineage2_Unknown.png new file mode 100644 index 0000000000000000000000000000000000000000..80de6c5b88c85de44b5656976b46f2b271befa53 GIT binary patch literal 1216 zcmV;x1V8(UP)|-^V@xpVizN7f z!KkG8K>{J552!KJsEAN0q$r4>LYvacrnPk0c6YnokJ+8M_xd0umC%+_ATgd~9wu|| z{OyhyAvTHBn$ej#J4Bf zqDoyCtpN~s0?Yu&1q-HF$w+QvDxG`A&bw7mCx8&diA@86N-Ds)LFY?4)b{pW*8id2zkdvChMqEzg!k?fAGB?iS{Gl)7Spq-cvsu zd|0qjP!(mtA2gz++iTZ3%NJGZiDVY0!4dDv2*(jBSm@OVM3zTKdq=O>^x6UKYH#Z5 z#89BB=|X3GQ04zCv>~7x0C{Ntg!!?)?AGVj*N6>I&gYSyen0^`K5oE{kB+f}_2<~h zpZ}!U50t_*IZ8_kSm8L!J>1X|kc>Kd5}BWt06aXXFbAjeW>vpl-HZq;1D zHKxiY1y$YN6S8h^Bd$b~4Y5RK|B=R)*jw*@2gXRHQaL>Q(0wQ^DNvboelY+v455q< zS6MvxXEiQoHXUcDmU1Z}!{-*Y>_)=noX%h<1c1M#zJLf2msgt2|@_7xC zO8JLM^N`?%e+q&)V;?%MnAdo!t;!X$fQbABevhqb>hM8S5B;&QA&&v0)7t;e*LTb zy#5?3oe-$|qb<7mW=&;GiUGiOY!3mt<(46Nb|iroUHU6_>e|{7+ae#0;*ek0FGq5G*}`(HTU8Aq6k6M( zV&|UY+%Wh|08K;P+cAN;ZZOA{R6aR~6^kq33JH(NA%p~@kyYL{SSb*oBoQgjHKhP> zt|_G;)hOq;QGv5*$F)4VdG@zXytC^lNu|))6=#N_F)5|GTT>.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 lineage2_icon_coverage_test extends TestCase +{ + /** Gaps that are known and tracked; see #3 and #7 */ + private const KNOWN_GAPS = array( + 'lineage2_defighter', + 'lineage2_demystic', + 'lineage2_dfighter', + 'lineage2_efighter', + 'lineage2_emystic', + 'lineage2_hfighter', + 'lineage2_hmystic', + 'lineage2_kfsoldier', + 'lineage2_kmsoldier', + 'lineage2_ofighter', + 'lineage2_omystic', + ); + + /** 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 — see #7, 110 roster portraits unsourced + */ + public function test_roster_classes_cover_every_seeded_class(): void + { + $this->markTestSkipped('ALL — see #7, 110 roster portraits unsourced'); + $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(110, $this->seeded_imagenames()); + } +}