$patches */ function textureRecord(string $name, bool $masked, int $width, int $height, array $patches): string { $data = pack('a8VvvVv', $name, $masked ? 1 : 0, $width, $height, 0, count($patches)); foreach ($patches as $patch) { $data .= s16($patch['originX']); $data .= s16($patch['originY']); $data .= pack('v', $patch['patch']); $data .= pack('v', $patch['stepDir']); $data .= pack('v', $patch['colorMap']); } return $data; } /** * @param string[] $records */ function textureXData(array $records): string { $count = count($records); $offset = 4 + ($count * 4); $offsets = []; $body = ''; foreach ($records as $record) { $offsets[] = $offset; $body .= $record; $offset += strlen($record); } $data = pack('V', $count); foreach ($offsets as $itemOffset) { $data .= pack('V', $itemOffset); } return $data . $body; } /** * @param string[] $names */ function pnamesData(array $names): string { $data = pack('V', count($names)); foreach ($names as $name) { $data .= pack('a8', $name); } return $data; } function appendLump(File $wad, string $name, string $data): void { $index = $wad->lumps->count(); $wad->lumps->push(new Lump( offset: 0, size: strlen($data), name: strtoupper($name), data: $data, index: $index, )); } test('resolves textures using PNAMES and patch lumps', function (): void { $wad = new File(); appendLump($wad, 'PNAMES', pnamesData(['PATCHA', 'PATCHB'])); appendLump($wad, 'TEXTURE1', textureXData([ textureRecord('TEXA', false, 64, 64, [ ['originX' => 0, 'originY' => 0, 'patch' => 0, 'stepDir' => 0, 'colorMap' => 0], ]), textureRecord('TEXB', false, 32, 16, [ ['originX' => 8, 'originY' => -2, 'patch' => 0, 'stepDir' => 0, 'colorMap' => 0], ]), ])); appendLump($wad, 'TEXTURE2', textureXData([ textureRecord('TEXA', true, 128, 32, [ ['originX' => 4, 'originY' => 5, 'patch' => 1, 'stepDir' => 0, 'colorMap' => 0], ['originX' => 0, 'originY' => 0, 'patch' => 5, 'stepDir' => 0, 'colorMap' => 0], ]), ])); appendLump($wad, 'PATCHA', ''); $resolved = $wad->resolveTextures(); expect($resolved)->toHaveCount(2); expect($resolved)->toHaveKey('TEXA'); expect($resolved)->toHaveKey('TEXB'); $texA = $resolved['TEXA']; expect($texA->masked)->toBeTrue(); expect($texA->width)->toBe(128); expect($texA->height)->toBe(32); expect($texA->patches)->toHaveCount(2); expect($texA->patches[0]->patchName)->toBe('PATCHB'); expect($texA->patches[0]->lumpIndex)->toBeNull(); expect($texA->patches[0]->isResolved())->toBeFalse(); expect($texA->patches[1]->patchName)->toBeNull(); $texB = TextureResolver::forWad($wad)->resolveByName('texb'); expect($texB)->not->toBeNull(); expect($texB->patches[0]->patchName)->toBe('PATCHA'); expect($texB->patches[0]->lumpIndex)->toBe(3); expect($texB->patches[0]->isResolved())->toBeTrue(); }); test('throws when PNAMES lump is missing', function (): void { $wad = new File(); appendLump($wad, 'TEXTURE1', textureXData([ textureRecord('TEXA', false, 16, 16, [ ['originX' => 0, 'originY' => 0, 'patch' => 0, 'stepDir' => 0, 'colorMap' => 0], ]), ])); expect(fn () => $wad->resolveTextures())->toThrow(RuntimeException::class); });