Unlit `Textured` materials loaded as `MeshBasicMaterial`, so they stayed bright while the lights receded during a camouflage switch (E 100 legendary camouflage).
- `Textured` materials now export as standard materials with the base color texture as emissive and a black base color, so they look the same as before.
- The Tankopedia model fades their emissive intensity with the lighting transition.
- Additive blending applies to these parts again, since they are standard materials.
- Tankopedia state uses `loaders: { model, skin, attachment }` with `requested` and `loaded`
- useSkin swaps the camouflage halfway through the lighting transition for both cached and downloaded skins
- Remove the leftover "Unreleased" camouflage name handling
Fixes a batch of legendary camouflage problems in the Tankopedia viewer, adds three missing unique camouflages and lets attachments be shown together with a camouflage.
## Camouflage parts (state switchers)
- Camouflages now choose which tank pieces show the same way the game does. Each switcher's load-time scenario script runs at export time (checks for attached camouflage items, or compares the camouflage preset), along with its state-switch actions. This replaces the old guess from part names (new `core/blitzkit/switcherStates.ts`).
- Fixes missing roof MGs, fenders and fuel tanks (IS-3 Bear, Obj. 140 Terror, IS-7, IS-4, FV215b 183, AMX 50 B, KV-1S) and default parts mixed into camouflages (Pershing Speedfire, Forest Witch Fullmetal, Waffenträger Goldstreifen).
- Switchers inside camouflage parts now show one state instead of all of them stacked (Obj. Kust double net, Goldstreifen stowage, Tiger II cat).
- States are matched by original node names, because the GLTF loader renames duplicates.
- Camouflage part meshes with no LODs (`lodIndex -1`) are kept, which restores the Tiger II cat body and missing pieces on about 11 other camouflages.
## Materials
- `AlphaBlend` materials export as transparent. `AlphaBlendAdditive` materials export as additive and are applied in `jsxTree`, and unlit `Textured` materials export as unlit (IS-8 Raroh flames, dinosaur holograms, and glass and screens on a few base tanks).
- Camouflage part materials that share a name with the tank's material no longer replace the tank's camouflage material (Bisonte C45 Unbreakable).
## Names
- With a legendary camouflage selected, the title shows the tank's in-battle name ("GuP Type 95 Ha-Go", "KV-1 Warrior", "Shinobi").
- Camouflages without a name in the game files yet show "Unreleased" in orange instead of their raw string key, and no longer crash the tank names list.
## Attachments
- Attachments (`kind: customization`) are listed in their own group under the camouflages and can be turned on together with a camouflage. They load the same way as camouflages and fade with the same transition.
## Other
- Liberté, You Rock! and Ozzie (single-tank camouflages that only set a preset through `override`) are now available.
- Mastery gun mark meshes (`custom_gun_marks_*`) are no longer drawn (Helsing, HWT20 FalconFire and two camouflage guns).
## Not included
- Pixel Wave, Digital Trace and Forest Shadow are tiled pattern camouflages, which the viewer can't render yet.
- FV301 Corbie's drone position and the dinosaur heads' glow depend on camouflage animations and emissive materials, which are separate PRs.
Fixes#417Fixes#419Fixes#421
Adds track repair time to the Tankopedia survivability stats, matching the value the game lists.
- Added a required `repair_time` to track definitions, filled from each chassis's hysteresis health divided by its health regen per second
- Applied the toolbox, the repair crew skill and the protective kit as multiplicative repair speed bonuses
- Added a Track repair time row under fire chance; it appears once the definitions are regenerated on the next build
Fixes#308
Lets people on limited data stop the Tankopedia from downloading 3D models automatically.
- Added an Automatically load 3D models switch to the settings page, on by default
- When off, the tank page hero shows a Load 3D model button where the loading bar sits
- Held back every model download on the tank page until requested, including the dynamic armor switcher and the gun flexibility visualizer
- Wrapped the tank sandbox in its own suspense boundary so the loading bar shows real progress instead of the whole page waiting on the model
- Measured loading progress from when each loading bar appears so it always starts empty
- Added the English strings; other languages fall back until translated
Fixes#311
Prepares BlitzKit for tier XI tanks ahead of their release. Tier X was hardcoded as the maximum in the tank search filters, the guesser and the tech tree, and had no roman numeral beyond X.
- Added `XI` to `TIER_ROMAN_NUMERALS`
- Added an awaitable tier list derived from the highest tier in the tank definitions, so nothing changes until tier XI tanks exist
- Used it for the tank search tier filters and the guesser tier picker and defaults
- Made the tech tree check for successors instead of assuming tier X is the last tier
Fixes#302
Contact shadows under the tank disappeared when the camera orbited low enough to look up at it. The shadow plane and the ground disc are both transparent and centred on the origin, so three.js sorts them by camera depth. Looking upward flips that order, and because the shadow doesn't write depth, the ground disc was drawn over it.
- Gave `ContactShadows` a `renderOrder` of 1 so it always renders after the ground disc
Fixes#299
On some browsers a thin sliver of the Insanity banner showed through along the rounded edges of the equalizer toggle, most visibly on the right where the gradient is opaque. The banner and the gradient were painted on two separately clipped elements, so their anti-aliased edges didn't line up.
- Moved the gradient onto the outer box as the top layer of its `backgroundImage`, above the banner
- Removed the separate gradient layer from the inner flex
Fixes#297
Dragging a tank's track scrolled its roughness/metalness texture at twice the speed of the base color and normal textures, so highlights drifted off the track links.
**Cause**
- `translateTexture` added the scroll offset to `roughnessMap` and `metalnessMap` separately
- glTF has a single metallic-roughness texture, and the loader assigns that same texture object to both slots, so it received the offset twice
**Changes**
- Collect the track's maps in a `Set` before scrolling so a texture shared between slots is offset exactly once
The static armor panel hid itself with a fixed `right: -6rem`. In languages with longer labels (e.g. Spanish) the panel is wider than that, so part of it stayed visible at the edge.
Anchor the panel at `right: 0` and slide it out with a transform based on its own width (`translateX(calc(100% + var(--space-4)))`) when static armor isn't active, so it always clears the edge regardless of label length.
The reload visualizer and the Firepower reload rows showed two decimals.
Add an exported `RELOAD_PRECISION` (1) in the reload visualizer and use it for the visualizer's times and placeholders and for the Firepower reload rows (per-shell reloads, reload and intra-clip).
The hero loading bar was offset below the title by half the font size plus a fixed 16px. The title is centered as a line box, so its descenders and heavy caps reach lower than half the font size, and the fixed gap didn't grow with the title.
Offset the bar by 0.75 of the font size instead, so the gap scales with the title.
The gun flexibility visualizer at the bottom of the Tankopedia Firepower section computed depression/elevation purely from the raw gun `pitch.min`/`pitch.max` values, ignoring `initial_turret_rotation.pitch` (the tank's baked-in turret mount tilt). Every other place that reports gun depression/elevation already accounts for this offset; the visualizer was the outlier.
On tanks with a level turret mount the offset is zero, so it was invisible almost everywhere. Minotauro's turret sits pitched 3° forward, which made the visualizer's min/max/pitch stats and wedge shape disagree with the Firepower stats directly above it (e.g. 7°/-18° instead of 10°/15°).
**Changes:**
- `GunFlexibilityVisualizer/index.tsx`: fold the turret's initial pitch offset into `minPitch`, `maxPitch` and the displayed `pitch` stat, and into the drawn limit shape, the pitch dot and the pointer mapping, so the dashed neutrality circle sits at the displayed 0°.
- `GunFlexibilityVisualizer/components/CanvasContent.tsx`: nest the gun mesh inside a turret group carrying the same static tilt as its base rotation, so the mini 3D preview's live drag-pitch composes on top of it, mirroring `useTankTransform.ts`.
## Summary
- Add a Spall Liner toggle button to the Tankopedia model viewer, placed alongside the existing Calibrated Shells / Enhanced Armor buttons. It's only shown for tanks that can actually equip Spall Liner.
- The toggle reads/writes the same `Duel.protagonist.provisions` array as the Provisions tab, so both stay in sync automatically.
- If a provision slot is free, clicking equips Spall Liner immediately. If all slots are full, a popover shows icons with a shuffle arrow so you can pick which active provision to swap out in one step.
- Buttons use the same medium rounding as the consumable buttons on the tank page.
- When Spall Liner is equipped, the Dynamic Armor 3D viewer's click-damage tooltip now applies the same 20% HE damage reduction Spall Liner grants in-game.
- Refactored the hardcoded `-0.2` HE damage coefficient in `tankCharacteristics.ts` into a shared constant so the Compare tool and the 3D viewer use a single source of truth.
## Scope
This only affects the single-tank Tankopedia page. The Firepower stats table (the viewed tank's own outgoing gun damage) is intentionally untouched, since a defensive damage reduction would be semantically backwards there. The Dynamic Armor viewer's click tooltip is the only place a "damage taken by this tank" number exists, so that's where the reduction is wired in.