From b950ac13181401bc993928f54370ebaf2073c573 Mon Sep 17 00:00:00 2001 From: GonnaHetzMe Date: Sun, 12 Apr 2026 22:17:55 +0200 Subject: [PATCH 01/17] fix: sc2 alphatest threshold --- .../src/core/blitz/extractModel/index.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/scripts/src/core/blitz/extractModel/index.ts b/packages/scripts/src/core/blitz/extractModel/index.ts index 846dd80c9..a2e9da9f8 100644 --- a/packages/scripts/src/core/blitz/extractModel/index.ts +++ b/packages/scripts/src/core/blitz/extractModel/index.ts @@ -75,6 +75,19 @@ export async function extractModel(vfs: AbstractVFS, path: string) { .setImage(baseColor), ); + if ( + node.enabledPresets?.AlphaTest && + node.properties.alphatestThreshold + ) { + const view = new DataView(node.properties.alphatestThreshold); + const alphaCutoff = + view.byteLength < 4 + ? 0.5 + : view.getFloat32(view.byteLength - 4, true); + + material.setAlphaMode("MASK").setAlphaCutoff(alphaCutoff); + } + if (node.configCount) { for ( let configIndex = 0; @@ -91,8 +104,10 @@ export async function extractModel(vfs: AbstractVFS, path: string) { } const view = new DataView(archive.properties.alphatestThreshold); - const _ = view.getUint8(0); // only god knows what these flags are - const alphaCutoff = view.getFloat32(1, true); + const alphaCutoff = + view.byteLength < 4 + ? 0.5 + : view.getFloat32(view.byteLength - 4, true); material.setAlphaMode("MASK").setAlphaCutoff(alphaCutoff); } From 4d13803b0c2a94f08c98c93b4fbd043a402e7452 Mon Sep 17 00:00:00 2001 From: GonnaHetzMe Date: Sun, 12 Apr 2026 22:35:55 +0200 Subject: [PATCH 02/17] feat: culling --- .../src/core/blitz/extractModel/index.ts | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/scripts/src/core/blitz/extractModel/index.ts b/packages/scripts/src/core/blitz/extractModel/index.ts index a2e9da9f8..5f835b7da 100644 --- a/packages/scripts/src/core/blitz/extractModel/index.ts +++ b/packages/scripts/src/core/blitz/extractModel/index.ts @@ -75,9 +75,26 @@ export async function extractModel(vfs: AbstractVFS, path: string) { .setImage(baseColor), ); + const defaultConfigArchive = + typeof node.configCount === "number" + ? (times( + node.configCount, + (index) => node[`configArchive_${index}`], + ).find((archive) => archive.configName === "Default") ?? + node.configArchive_0) + : undefined; + const customCullMode = + node.customCullMode ?? defaultConfigArchive?.customCullMode; + + if (customCullMode === 0) { + material.setDoubleSided(true); + } else if (customCullMode !== undefined) { + material.setDoubleSided(false); + } + if ( node.enabledPresets?.AlphaTest && - node.properties.alphatestThreshold + node.properties?.alphatestThreshold ) { const view = new DataView(node.properties.alphatestThreshold); const alphaCutoff = @@ -98,7 +115,8 @@ export async function extractModel(vfs: AbstractVFS, path: string) { if ( archive.configName !== "Default" || - !archive.enabledPresets?.AlphaTest + !archive.enabledPresets?.AlphaTest || + !archive.properties?.alphatestThreshold ) { continue; } From 4205b167692bff9b1666d9954ca489674473a11d Mon Sep 17 00:00:00 2001 From: GonnaHetzMe Date: Mon, 13 Apr 2026 02:00:12 +0200 Subject: [PATCH 03/17] fix: correct RGB565 PVR decoding --- packages/core/src/streams/pvr.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/core/src/streams/pvr.ts b/packages/core/src/streams/pvr.ts index 979b9c396..8703f5cd7 100644 --- a/packages/core/src/streams/pvr.ts +++ b/packages/core/src/streams/pvr.ts @@ -1,5 +1,5 @@ -import { clamp, isEqual, times } from 'lodash-es'; -import { ReadStream } from './buffer'; +import { clamp, isEqual, times } from "lodash-es"; +import { ReadStream } from "./buffer"; export enum PvrFlags { NoFlag = 0, @@ -96,7 +96,7 @@ export class PvrReadStream extends ReadStream { const header = this.header(); this.metadata(); - if (typeof header.pixelFormat === 'number') { + if (typeof header.pixelFormat === "number") { switch (header.pixelFormat) { default: throw new TypeError( @@ -142,12 +142,12 @@ export class PvrReadStream extends ReadStream { times(pixelCount, (index) => { const buffer = this.consumeUint8Array(2); - const bufferIndex = index * 4; + const bufferIndex = index * 3; const r = ((buffer[1] & 0b11111000) >>> 3) / 31; const g = - ((buffer[1] & 0b111) << 3) | - (((buffer[0] & 0b11100000) >>> 5) / 31); - const b = buffer[0] & 0b11111; + (((buffer[1] & 0b111) << 3) | ((buffer[0] & 0b11100000) >>> 5)) / + 63; + const b = (buffer[0] & 0b11111) / 31; data[bufferIndex] = Math.round(clamp(r * 255, 0, 255)); data[bufferIndex + 1] = Math.round(clamp(g * 255, 0, 255)); @@ -186,7 +186,7 @@ export class PvrReadStream extends ReadStream { metadataSize: this.uint32(), }; - if (header.version !== 'PVR\x03') throw new TypeError('Endian mismatch'); + if (header.version !== "PVR\x03") throw new TypeError("Endian mismatch"); return header; } @@ -205,19 +205,19 @@ export class PvrReadStream extends ReadStream { resolveBitRatePixelFormat( pixelFormat: Exclude, number>, ) { - if (pixelFormat.order === 'rgba') { + if (pixelFormat.order === "rgba") { if (isEqual(pixelFormat.bitRates, [4, 4, 4, 4])) { return ResolvedBitRatePixelFormat.R4G4B4A4; } else throw new TypeError( - `Unhandled rgba bit rate ${pixelFormat.bitRates.join(', ')}`, + `Unhandled rgba bit rate ${pixelFormat.bitRates.join(", ")}`, ); - } else if (pixelFormat.order === 'rgb\0') { + } else if (pixelFormat.order === "rgb\0") { if (isEqual(pixelFormat.bitRates, [5, 6, 5, 0])) { return ResolvedBitRatePixelFormat.R5G6B5A0; } else throw new TypeError( - `Unhandled rgb bit rate ${pixelFormat.bitRates.join(', ')}`, + `Unhandled rgb bit rate ${pixelFormat.bitRates.join(", ")}`, ); } else { throw new TypeError(`Unhandled pixel order ${pixelFormat.order}`); From aec30e1944273fc6e81489ad2f249ff3bbbdecec Mon Sep 17 00:00:00 2001 From: GonnaHetzMe Date: Mon, 13 Apr 2026 02:13:53 +0200 Subject: [PATCH 04/17] chore: bump github actions versions --- .github/workflows/build-bot.yml | 2 +- .github/workflows/website.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-bot.yml b/.github/workflows/build-bot.yml index 14c9cb343..501cd703f 100644 --- a/.github/workflows/build-bot.yml +++ b/.github/workflows/build-bot.yml @@ -28,7 +28,7 @@ jobs: run: bun run build:bot - name: Archive build - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: bot path: dist/bot diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 623200286..1c739206c 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -46,14 +46,14 @@ jobs: - uses: arduino/setup-protoc@v3 with: repo-token: ${{ secrets.GH_TOKEN }} - - uses: actions/configure-pages@v5 + - uses: actions/configure-pages@v6 - run: bun run setup - name: Build website run: bun run build:website - name: Upload artifact - uses: actions/upload-pages-artifact@v4 + uses: actions/upload-pages-artifact@v5 with: path: dist/website @@ -69,4 +69,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 From 3948af9e39c26f93b63350654d837a2e12968757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 14 Apr 2026 11:31:16 -0500 Subject: [PATCH 05/17] fix: implement types and simplify code --- packages/core/src/streams/sc2.ts | 5 +++- .../src/core/blitz/extractModel/index.ts | 29 ++++++++++++------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/packages/core/src/streams/sc2.ts b/packages/core/src/streams/sc2.ts index eb5ebccd7..8b4837bab 100644 --- a/packages/core/src/streams/sc2.ts +++ b/packages/core/src/streams/sc2.ts @@ -15,6 +15,9 @@ type DataNode = { qualityGroup?: string; fxName?: string; textures?: Textures; + customCullMode?: number; + enabledPresets?: EnabledPresets; + properties?: Properties; } & ( | ({ configCount: number; @@ -24,7 +27,7 @@ type DataNode = { } ); -interface ConfigArchive { +export interface ConfigArchive { configName: string; fxName: string; properties: Properties; diff --git a/packages/scripts/src/core/blitz/extractModel/index.ts b/packages/scripts/src/core/blitz/extractModel/index.ts index 5f835b7da..3139a8608 100644 --- a/packages/scripts/src/core/blitz/extractModel/index.ts +++ b/packages/scripts/src/core/blitz/extractModel/index.ts @@ -1,5 +1,6 @@ import { bufferToBigInt, + ConfigArchive, Hierarchy, Sc2ReadStream, ScgReadStream, @@ -75,14 +76,20 @@ export async function extractModel(vfs: AbstractVFS, path: string) { .setImage(baseColor), ); - const defaultConfigArchive = - typeof node.configCount === "number" - ? (times( - node.configCount, - (index) => node[`configArchive_${index}`], - ).find((archive) => archive.configName === "Default") ?? - node.configArchive_0) - : undefined; + let defaultConfigArchive: ConfigArchive | undefined = undefined; + + if (node.configCount) { + const names = times( + node.configCount, + (index) => node[`configArchive_${index}`], + ); + + defaultConfigArchive = names.find( + (archive) => archive.configName === "Default", + ); + defaultConfigArchive ??= node.configArchive_0; + } + const customCullMode = node.customCullMode ?? defaultConfigArchive?.customCullMode; @@ -204,8 +211,8 @@ export async function extractModel(vfs: AbstractVFS, path: string) { case "TransformComponent": { const localTranslation = component["tc.localTranslation"]; - // The game resets top-level node translation to [0, 0, 0] on load. - // Child nodes must keep their authored local offsets. + // The game resets top-level node translation to [0, 0, 0] on load. + // Child nodes must keep their authored local offsets. node.setTranslation( parent instanceof Scene ? [0, 0, 0] : localTranslation, ); @@ -317,4 +324,4 @@ export async function extractModel(vfs: AbstractVFS, path: string) { await document.transform(prune({ keepAttributes: true }), dedup()); return document; -} \ No newline at end of file +} From 5fa4d3b24263421d2de2a5434f011cb6464417fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:17:40 -0500 Subject: [PATCH 06/17] remove aeson --- .../components/Equalizer.tsx | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equalizer.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equalizer.tsx index f49200157..1ec26568a 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equalizer.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Equalizer.tsx @@ -70,37 +70,6 @@ export function Equalizer() { - - - - - - Brought to you by{" "} - {" "} - WoTB News - - - - - - - This feature was brought to you early, ahead of the release of the - game mode, thanks to{" "} - - WoTB News - - . - - - ); } From c353119962c31e9f78dcbc670a61378e420b4048 Mon Sep 17 00:00:00 2001 From: GonnaHetzMe Date: Wed, 29 Apr 2026 21:19:43 +0200 Subject: [PATCH 07/17] fix: use RIFF sprite bounds for tank icon extraction --- packages/scripts/src/buildAssets/tankIcons.ts | 54 ++++++++++++++++--- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/packages/scripts/src/buildAssets/tankIcons.ts b/packages/scripts/src/buildAssets/tankIcons.ts index 3a0fbfc22..fb74388d9 100644 --- a/packages/scripts/src/buildAssets/tankIcons.ts +++ b/packages/scripts/src/buildAssets/tankIcons.ts @@ -1,6 +1,7 @@ import { NATION_IDS } from "@blitzkit/core"; import sharp from "sharp"; import type { Vector3Tuple } from "three"; +import { parsePackedSpriteRect } from "../core/blitz/parsePackedSpriteRect"; import { AssetUploader } from "../core/github/assetUploader"; import { vfs } from "./constants"; import type { VehicleDefinitionList } from "./definitions"; @@ -37,6 +38,45 @@ export interface TankParameters { }; } +async function extractPackedTankIcon(packedBuffer: Buffer, context: string) { + const texture = sharp(packedBuffer); + const spriteRect = parsePackedSpriteRect(packedBuffer); + + if (!spriteRect) { + throw new Error(`Missing RIFF sprite bounds for ${context}`); + } + + const [left, top, width, height] = spriteRect; + const bounds = [left, top, width, height]; + + if (!bounds.every(Number.isInteger)) { + throw new Error( + `Invalid RIFF sprite bounds for ${context}: ${bounds.join(" ")}`, + ); + } + + const metadata = await texture.metadata(); + + if (metadata.width === undefined || metadata.height === undefined) { + throw new Error(`Failed to read image dimensions for ${context}`); + } + + if ( + left < 0 || + top < 0 || + width <= 0 || + height <= 0 || + left + width > metadata.width || + top + height > metadata.height + ) { + throw new Error( + `Out-of-bounds RIFF sprite for ${context}: ${bounds.join(" ")} in ${metadata.width}x${metadata.height}`, + ); + } + + return await texture.extract({ left, top, width, height }).toBuffer(); +} + export async function tankIcons() { console.log("Building tank icons..."); @@ -67,12 +107,14 @@ export async function tankIcons() { const bigPath = `Data/${parameters.resourcesPath.bigIconPath .replace(/~res:\//, "") .replace(/\..+/, "")}.packed.webp`; - const big = await sharp(await vfs.file(bigPath)) - .trim() - .toBuffer(); - const small = await sharp(await vfs.file(smallPath)) - .trim() - .toBuffer(); + const big = await extractPackedTankIcon( + await vfs.file(bigPath), + `tank ${tankKey} big`, + ); + const small = await extractPackedTankIcon( + await vfs.file(smallPath), + `tank ${tankKey} small`, + ); if (big) { await uploader.add({ From 76e748f0ed9f7966a43aa6a2de5fb5e5a73be7e9 Mon Sep 17 00:00:00 2001 From: GonnaHetzMe Date: Wed, 29 Apr 2026 23:03:59 +0200 Subject: [PATCH 08/17] feat: add localized tooltips to tankopedia modules tree --- .../CharacteristicsSection/components/Modules.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx index 937c216d3..81ed16338 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx @@ -18,6 +18,7 @@ import { Heading, IconButton, Text, + Tooltip, } from "@radix-ui/themes"; import { awaitableTankDefinitions } from "../../../../core/awaitables/tankDefinitions"; import { useLocale } from "../../../../hooks/useLocale"; @@ -55,12 +56,14 @@ function ModuleButton({ selected, unlock, top, + tooltip, onClick, }: { unlock: Unlock; tier: number; selected: boolean; top: boolean; + tooltip?: string; onClick: () => void; }) { const isTank = unlock.type === ModuleType.MODULE_TYPE_VEHICLE; @@ -134,7 +137,7 @@ function ModuleButton({ ); - return isTank ? ( + const node = isTank ? ( {node}; } const tankDefinitions = await awaitableTankDefinitions; @@ -167,7 +174,7 @@ export function Modules() { const topGun = topTurret.guns.at(-1)!; const topEngine = tank.engines.at(-1)!; const topTrack = tank.tracks.at(-1)!; - const { strings } = useLocale(); + const { strings, unwrap } = useLocale(); function setByUnlock(unlock: Unlock) { Duel.mutate((draft) => { @@ -249,6 +256,8 @@ export function Modules() { if (module === undefined) return null; + const tooltip = module.name ? unwrap(module.name) : undefined; + return ( Date: Thu, 30 Apr 2026 21:58:39 +0200 Subject: [PATCH 09/17] Update packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: TrèsAbhi <43380238+tresabhi@users.noreply.github.com> --- .../Tankopedia/CharacteristicsSection/components/Modules.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx index 81ed16338..a290cf245 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx @@ -256,7 +256,7 @@ export function Modules() { if (module === undefined) return null; - const tooltip = module.name ? unwrap(module.name) : undefined; + const tooltip = unwrap(module.name!); return ( Date: Sun, 10 May 2026 21:55:33 -0500 Subject: [PATCH 10/17] impossible inspect element --- packages/website/src/components/Guesser.tsx | 24 +++++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/website/src/components/Guesser.tsx b/packages/website/src/components/Guesser.tsx index 62ff27553..292025229 100644 --- a/packages/website/src/components/Guesser.tsx +++ b/packages/website/src/components/Guesser.tsx @@ -58,12 +58,14 @@ export function Guesser() { const [results, setResults] = useState(null); const [selected, setSelected] = useState(null); const tiers = Guess.use((state) => state.tiers); + const status = useRef(null); const requestSearch = useCallback(() => { setSelected(null); setSearching(true); search(); }, []); + const search = useCallback( debounce(() => { if (!input.current) return; @@ -92,6 +94,20 @@ export function Guesser() { if (guessState !== GuessState.NotGuessed) setSelected(null); }, [guessState]); + useEffect(() => { + const interval = setInterval(() => { + if (!status.current) return; + + status.current.innerText = literals(strings.website.tools.guess.stats, { + correct: correctGuesses, + total: totalGuesses, + streak: streak, + }); + }, 1000 / 60); + + return () => clearInterval(interval); + }, [correctGuesses, totalGuesses, streak]); + return ( - - {literals(strings.website.tools.guess.stats, { - correct: correctGuesses, - total: totalGuesses, - streak: streak, - })} - + From 042a1afa74209c2abcdb87066b61e8eaad6144b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 11 May 2026 16:18:54 -0500 Subject: [PATCH 11/17] remove cache from useModel --- packages/website/src/hooks/useModel.ts | 30 ++++++++------------------ 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/packages/website/src/hooks/useModel.ts b/packages/website/src/hooks/useModel.ts index 8f6d8a411..83bfc7cfa 100644 --- a/packages/website/src/hooks/useModel.ts +++ b/packages/website/src/hooks/useModel.ts @@ -3,28 +3,16 @@ import { type ObjectMap, useLoader } from "@react-three/fiber"; import { Mesh, MeshStandardMaterial } from "three"; import { type GLTF, GLTFLoader } from "three-stdlib"; -const cache: Record< - number, - { - gltf: GLTF & ObjectMap; - hasPbr: boolean; - } -> = {}; - export function useModel(id: number) { const gltf = useLoader(GLTFLoader, asset(`3d/tanks/models/${id}.glb`)); - if (!cache[id]) { - cache[id] = { - gltf, - hasPbr: Object.values(gltf.nodes).some( - (node) => - node instanceof Mesh && - node.material instanceof MeshStandardMaterial && - node.material.roughnessMap !== null - ), - }; - } - - return cache[id]; + return { + gltf, + hasPbr: Object.values(gltf.nodes).some( + (node) => + node instanceof Mesh && + node.material instanceof MeshStandardMaterial && + node.material.roughnessMap !== null + ), + }; } From 0b2e322112effa3ec9b215556524e07b3c551121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 11 May 2026 17:14:22 -0500 Subject: [PATCH 12/17] disbale low tiers --- packages/website/src/components/Guesser.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/src/components/Guesser.tsx b/packages/website/src/components/Guesser.tsx index 292025229..4f6f6e51a 100644 --- a/packages/website/src/components/Guesser.tsx +++ b/packages/website/src/components/Guesser.tsx @@ -322,7 +322,7 @@ export function Guesser() { ? GuessState.Correct : GuessState.Incorrect; draft.totalGuesses++; - draft.correctGuesses += correct ? 1 : 0; + draft.correctGuesses += correct && draft.tiers.length === 10 ? 1 : 0; draft.streak = correct ? draft.streak + 1 : 0; }); } else { From 4f42a31f53d4637532a85e2c0e2422dceab70538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 11 May 2026 17:52:23 -0500 Subject: [PATCH 13/17] Reduce interval timing from 60fps to 200ms --- packages/website/src/components/Guesser.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/src/components/Guesser.tsx b/packages/website/src/components/Guesser.tsx index 4f6f6e51a..27c7915f0 100644 --- a/packages/website/src/components/Guesser.tsx +++ b/packages/website/src/components/Guesser.tsx @@ -103,7 +103,7 @@ export function Guesser() { total: totalGuesses, streak: streak, }); - }, 1000 / 60); + }, 1000 / 200); return () => clearInterval(interval); }, [correctGuesses, totalGuesses, streak]); From 100c7391fbb81c79d897375bb4d13c4908b2912b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 12 May 2026 17:54:50 -0500 Subject: [PATCH 14/17] dispose models on unload --- packages/website/src/hooks/useArmor.ts | 34 ++++++------ packages/website/src/hooks/useDispose.ts | 67 ++++++++++++++++++++++++ packages/website/src/hooks/useModel.ts | 12 +++-- 3 files changed, 90 insertions(+), 23 deletions(-) create mode 100644 packages/website/src/hooks/useDispose.ts diff --git a/packages/website/src/hooks/useArmor.ts b/packages/website/src/hooks/useArmor.ts index e1a5f05a6..d697bd0ba 100644 --- a/packages/website/src/hooks/useArmor.ts +++ b/packages/website/src/hooks/useArmor.ts @@ -1,23 +1,19 @@ -import { asset } from '@blitzkit/core'; -import { type ObjectMap, useLoader } from '@react-three/fiber'; -import { type GLTF, GLTFLoader } from 'three-stdlib'; - -const cache: Record< - number, - { gltf: GLTF & ObjectMap; hasDynamicArmor: boolean } -> = {}; +import { asset } from "@blitzkit/core"; +import { useLoader } from "@react-three/fiber"; +import { GLTFLoader } from "three-stdlib"; +import { useDispose } from "./useDispose"; export function useArmor(id: number) { - const gltf = useLoader(GLTFLoader, asset(`3d/tanks/armor/${id}.glb`)); + const path = asset(`3d/tanks/armor/${id}.glb`); + const gltf = useLoader(GLTFLoader, path); - if (!cache[id]) { - cache[id] = { - gltf, - hasDynamicArmor: Object.values(gltf.nodes).some( - (node) => - node.name.includes('state_00') || node.name.includes('state_01'), - ), - }; - } - return cache[id]; + useDispose(gltf, path); + + return { + gltf, + hasDynamicArmor: Object.values(gltf.nodes).some( + (node) => + node.name.includes("state_00") || node.name.includes("state_01"), + ), + }; } diff --git a/packages/website/src/hooks/useDispose.ts b/packages/website/src/hooks/useDispose.ts new file mode 100644 index 000000000..dffe2dc81 --- /dev/null +++ b/packages/website/src/hooks/useDispose.ts @@ -0,0 +1,67 @@ +import { useLoader } from "@react-three/fiber"; +import { useEffect } from "react"; +import { Cache } from "three"; +import { GLTFLoader } from "three-stdlib"; + +export function useDispose(gltf: any, path: string) { + useEffect(() => { + return () => { + const disposedGeometries = new Set(); + const disposedMaterials = new Set(); + const disposedTextures = new Set(); + + gltf.scene.traverse((obj: any) => { + if (obj.geometry) { + const geometry = obj.geometry; + + if (!disposedGeometries.has(geometry.uuid)) { + disposedGeometries.add(geometry.uuid); + geometry.dispose(); + } + } + + if (obj.material) { + const materials = Array.isArray(obj.material) + ? obj.material + : [obj.material]; + + for (const material of materials) { + if (!disposedMaterials.has(material.uuid)) { + disposedMaterials.add(material.uuid); + + for (const key in material) { + const value = material[key]; + + if ( + value && + value.isTexture && + !disposedTextures.has(value.uuid) + ) { + disposedTextures.add(value.uuid); + } + } + + for (const key in material) { + const value = material[key]; + + if (value && value.isTexture) { + value.source.data.close(); + value.dispose(); + } + } + + material.dispose(); + } + } + } + + if (obj.skeleton) { + obj.skeleton.dispose?.(); + } + }); + + useLoader.clear(GLTFLoader, path); + Cache.remove(path); + }; + }, [gltf, path]); +} diff --git a/packages/website/src/hooks/useModel.ts b/packages/website/src/hooks/useModel.ts index 83bfc7cfa..c8d532bd5 100644 --- a/packages/website/src/hooks/useModel.ts +++ b/packages/website/src/hooks/useModel.ts @@ -1,10 +1,14 @@ import { asset } from "@blitzkit/core"; -import { type ObjectMap, useLoader } from "@react-three/fiber"; +import { useLoader } from "@react-three/fiber"; import { Mesh, MeshStandardMaterial } from "three"; -import { type GLTF, GLTFLoader } from "three-stdlib"; +import { GLTFLoader } from "three-stdlib"; +import { useDispose } from "./useDispose"; export function useModel(id: number) { - const gltf = useLoader(GLTFLoader, asset(`3d/tanks/models/${id}.glb`)); + const path = asset(`3d/tanks/models/${id}.glb`); + const gltf = useLoader(GLTFLoader, path); + + useDispose(gltf, path); return { gltf, @@ -12,7 +16,7 @@ export function useModel(id: number) { (node) => node instanceof Mesh && node.material instanceof MeshStandardMaterial && - node.material.roughnessMap !== null + node.material.roughnessMap !== null, ), }; } From 2f745a16d11141dac87210c73f208ba2e05405de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 12 May 2026 18:08:02 -0500 Subject: [PATCH 15/17] clean up --- packages/website/src/hooks/useDispose.ts | 85 ++++++++++-------------- 1 file changed, 34 insertions(+), 51 deletions(-) diff --git a/packages/website/src/hooks/useDispose.ts b/packages/website/src/hooks/useDispose.ts index dffe2dc81..2ff4e3685 100644 --- a/packages/website/src/hooks/useDispose.ts +++ b/packages/website/src/hooks/useDispose.ts @@ -1,62 +1,45 @@ -import { useLoader } from "@react-three/fiber"; +import { useLoader, type ObjectMap } from "@react-three/fiber"; import { useEffect } from "react"; -import { Cache } from "three"; -import { GLTFLoader } from "three-stdlib"; +import { Cache, Group, Mesh, MeshStandardMaterial, Object3D } from "three"; +import { GLTFLoader, type GLTF } from "three-stdlib"; -export function useDispose(gltf: any, path: string) { +export function useDispose(gltf: GLTF & ObjectMap, path: string) { useEffect(() => { return () => { - const disposedGeometries = new Set(); - const disposedMaterials = new Set(); - const disposedTextures = new Set(); + gltf.scene.traverse((object) => { + if (object instanceof Mesh) { + if (object.material instanceof MeshStandardMaterial) { + const maps = [ + object.material.map, + object.material.aoMap, + object.material.envMap, + object.material.bumpMap, + object.material.alphaMap, + object.material.lightMap, + object.material.normalMap, + object.material.emissiveMap, + object.material.roughnessMap, + object.material.metalnessMap, + object.material.displacementMap, + ]; - gltf.scene.traverse((obj: any) => { - if (obj.geometry) { - const geometry = obj.geometry; + for (const map of maps) { + if (!map) continue; - if (!disposedGeometries.has(geometry.uuid)) { - disposedGeometries.add(geometry.uuid); - geometry.dispose(); - } - } - - if (obj.material) { - const materials = Array.isArray(obj.material) - ? obj.material - : [obj.material]; - - for (const material of materials) { - if (!disposedMaterials.has(material.uuid)) { - disposedMaterials.add(material.uuid); - - for (const key in material) { - const value = material[key]; - - if ( - value && - value.isTexture && - !disposedTextures.has(value.uuid) - ) { - disposedTextures.add(value.uuid); - } - } - - for (const key in material) { - const value = material[key]; - - if (value && value.isTexture) { - value.source.data.close(); - value.dispose(); - } - } - - material.dispose(); + map.source.data.close(); + map.dispose(); } - } - } - if (obj.skeleton) { - obj.skeleton.dispose?.(); + object.material.dispose(); + } else { + throw new TypeError( + `Unexpected material type: ${object.material.type}`, + ); + } + } else if (object instanceof Group || object instanceof Object3D) { + // traverse will get children + } else { + throw new TypeError(`Unexpected object type: ${object}`); } }); From 082872287682c214cb78bb032a53aebcefecade4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Fri, 15 May 2026 03:47:40 +0000 Subject: [PATCH 16/17] logs --- docs/changelogs/1.30.3.md | 18 ++++++++++++++++++ package.json | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 docs/changelogs/1.30.3.md diff --git a/docs/changelogs/1.30.3.md b/docs/changelogs/1.30.3.md new file mode 100644 index 000000000..69137dcac --- /dev/null +++ b/docs/changelogs/1.30.3.md @@ -0,0 +1,18 @@ +# BlitzKit 1.30.3 + +Thank you to [GonnaHetzMe](https://github.com/karelpak1) for your contributions this update. + +## Features + +- [GonnaHetzMe] Added tooltips to the module selector in Tankopedia + +## Fixes + +- Added aggressive re-rendering for the Guess mini-game's score readout to make inspect-element harder +- Removed tank visual and collision model caching +- Guesser once again breaks streaks if not all tiers are selected +- BlitzKit now properly unloads tank models from memory when visually hidden in Guesser +- [GonnaHetzMe] Tanks with complex transparency like the Herdbreaker once again handle opacity correctly +- [GonnaHetzMe] Pieces of tank models that have transparency now handle culling properly (they're visible from both sides) +- [GonnaHetzMe] Tanks with "cardboard" textures have been fixed, thanks to better PVR file parsing +- [GonnaHetzMe] Tank icons are now scaled correctly so small tanks are now properly zoomed out in contrast to bigger ones diff --git a/package.json b/package.json index ed634eb22..3fa646324 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blitzkit", - "version": "1.30.2", + "version": "1.30.3", "license": "BlitzKit License", "private": true, "scripts": { From a9a870c3e6b8b931be96ee0872b45eb77233f1a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Fri, 22 May 2026 14:52:47 -0500 Subject: [PATCH 17/17] New translations en.json (Russian) (#245) [ci skip] --- packages/i18n/strings/ru.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/i18n/strings/ru.json b/packages/i18n/strings/ru.json index 85014a4fa..5cc4a70a3 100644 --- a/packages/i18n/strings/ru.json +++ b/packages/i18n/strings/ru.json @@ -1018,8 +1018,8 @@ "title": "Характеристики", "compare": { "label": "Сравнивать с", - "class": "Уровень Тип", - "tier": "Все уровни", + "class": "{tier} {class}", + "tier": "Все {tier} уровня", "all": "Все танки" }, "values": {