Common Pitfalls - Critical Mistakes to Avoid
This document outlines critical mistakes that have been made in the past and how to avoid them. Read this before making changes to material/shader handling.
🚨 Material & Shader Pitfalls
1. Generic Color Parameter Misuse
MISTAKE: Applying generic "Color" parameters as base texture tints.
Why it's wrong: Many League shaders use "Color" for VFX effects (bloom, scrolling patterns, emissive), NOT for base texture tinting. Applying it as baseColorFactor will incorrectly darken/tint textures.
Example of the mistake:
// ❌ WRONG - Too generic, matches VFX colors
ColorTintBase = TryGetColorTint("Color") // This will tint Soraka's body red!Correct approach:
// ✅ CORRECT - Only use explicit shader profiles
if (ColorTintBase == null && !string.IsNullOrEmpty(profile.ColorTintParam))
{
ColorTintBase = TryGetColorTint(profile.ColorTintParam);
}
// DefaultProfile.ColorTintParam should be NULL/emptyFiles affected:
packages/extractor/LeagueConvert/IO/Skin/StaticMaterial.cs-ResolveForShader()packages/extractor/LeagueConvert/IO/Skin/StaticMaterial.cs-DefaultProfile
Key rule: Only shaders with explicit profiles that define ColorTintParam should have tinting applied. Never use generic fallbacks like "Color", "TintColor", or "Tint_Color" without a shader profile.
2. Shared Texture Instance Modification
MISTAKE: Modifying texture.repeat or texture.offset on shared texture instances.
Why it's wrong: Multiple materials can share the same texture instance. Modifying repeat cascades across all materials using that texture, making flipbooks progressively smaller.
Example of the mistake:
// ❌ WRONG - Modifies shared texture instance
material.map.repeat.set(newRepeatX, newRepeatY);
// If 5 materials share this texture, repeat gets divided 5 times!Correct approach:
// ✅ CORRECT - Clone texture before modifying
const clonedMap = material.map.clone();
material.map = clonedMap;
clonedMap.repeat.set(newRepeatX, newRepeatY);Files affected:
packages/web/src/engine/index.ts-handleFlipbook()
Key rule: Always clone textures before modifying repeat or offset properties.
3. Procedural Shader Texture Handling
MISTAKE: Skipping material creation or using fallback textures for procedural shaders.
Why it's wrong: Procedural shaders (like Shaders/SkinnedMesh/Glass) don't have diffuse textures. They generate visuals using color parameters. If you skip material creation or use a fallback, the mesh will be black/uncolored.
Example of the mistake:
// ❌ WRONG - Skips material if no texture found
if (magickImage == null) {
continue; // Material never created!
}Correct approach:
// ✅ CORRECT - Generate 1x1 placeholder from color params
if (magickImage == null && staticMaterialForTexture != null &&
staticMaterialForTexture.IsProceduralShader())
{
var profile = staticMaterialForTexture.GetShaderProfile();
float[] colorValue = null;
if (!string.IsNullOrEmpty(profile.ColorTintParam))
{
staticMaterialForTexture.RawParams.TryGetValue(
profile.ColorTintParam.ToLowerInvariant(), out colorValue);
}
byte r = colorValue != null && colorValue.Length >= 3
? (byte)(Math.Clamp(colorValue[0], 0f, 1f) * 255) : (byte)255;
// ... create 1x1 MagickImage from RGB
magickImage = new MagickImage(MagickColor.FromRgb(r, g, b), 1, 1);
}Files affected:
packages/extractor/LeagueConvert/IO/Skin/Extensions/SkinExtensions.cs- Material creationpackages/extractor/LeagueConvert/IO/Skin/Skin.cs-FindTexture()
Key rule: Always create materials, even for procedural shaders. Generate a 1x1 placeholder texture from shader color parameters.
4. Bloom Parameter Name Variations
MISTAKE: Only checking one parameter name for bloom intensity.
Why it's wrong: Different shaders use different parameter names (Bloom_Strength, BloomIntensity, Emissive_Strength). If you only check one, materials that should have bloom won't get it, causing incorrect color tint application.
Example of the mistake:
// ❌ WRONG - Only checks one name
BloomStrength = TryGetFloat("Bloom_Strength") ?? 0;
// Soraka uses "BloomIntensity" (no underscore) - gets 0, then Color gets applied as tint!Correct approach:
// ✅ CORRECT - Check multiple parameter name variations
if (BloomStrength == 0)
{
BloomStrength = TryGetFloat(profile.BloomIntensityParam)
?? TryGetFloat(DefaultProfile.BloomIntensityParam)
?? TryGetFloat("BloomIntensity") // Fallback for variations
?? TryGetFloat("Emissive_Strength")
?? 0;
}Files affected:
packages/extractor/LeagueConvert/IO/Skin/StaticMaterial.cs-ResolveForShader()
Key rule: Always check multiple parameter name variations for bloom/emissive intensity. Missing bloom detection causes incorrect color tinting.
5. Glass Material Transparency
MISTAKE: Assuming alphaMode: BLEND is enough for glass transparency.
Why it's wrong: Glass materials need both alphaMode: BLEND AND baseColorFactor alpha set from Alpha_Bias parameter. Without the alpha in baseColorFactor, the material won't be transparent even with BLEND mode.
Example of the mistake:
// ❌ WRONG - Only sets alphaMode
material.AlphaMode = nameof(AlphaMode.BLEND);
// Material still opaque!Correct approach:
// ✅ CORRECT - Set both alphaMode and baseColorFactor alpha
material.AlphaMode = nameof(AlphaMode.BLEND);
if (extras.GlassParams.AlphaBias.HasValue)
{
float opacity = Math.Max(0f, Math.Min(1f, 1.0f + extras.GlassParams.AlphaBias.Value));
pbrMetallicRoughness.baseColorFactor = new float[] { 1f, 1f, 1f, opacity };
}Files affected:
packages/extractor/LeagueConvert/IO/Skin/Extensions/SkinExtensions.cs- Glass material setup
Key rule: For transparent materials, always set both alphaMode AND baseColorFactor alpha component.
6. Flipbook Speed Parameter Extraction
MISTAKE: Not extracting FlipbookSpeed parameter for auto-animated flipbooks.
Why it's wrong: Some flipbooks (like Annie Skin31) are time-based auto-animated, not animation-synced. Without FlipbookSpeed, they won't animate on the frontend.
Correct approach:
// ✅ CORRECT - Extract flipbook speed
if (FlipbookSpeed == null && FlipbookSize != null)
{
FlipbookSpeed = TryGetFloat(profile.FlipbookSpeedParam)
?? TryGetFloat("flipbookspeed")
?? TryGetFloat("flipbookanimspeed");
}Files affected:
packages/extractor/LeagueConvert/IO/Skin/StaticMaterial.cs-ResolveForShader()packages/extractor/SimpleGltf/Json/Material.cs-MaterialExtraspackages/web/src/engine/index.ts-updateAutoFlipbooks()
Key rule: Always extract FlipbookSpeed when FlipbookSize is present. Frontend needs it for time-based animations.
🔍 Debugging Tips
How to verify color tint fix:
# Extract a skin that previously had incorrect tinting
dotnet run --project LeagueConvert.CommandLine -- convert-wad Soraka -o /tmp/test --skins 16
# Check baseColorFactor in GLB
python3 << 'EOF'
import json, struct
with open('/tmp/test/soraka/16/model.glb', 'rb') as f:
f.read(12) # Skip header
chunk_length = struct.unpack('<I', f.read(4))[0]
f.read(4) # Skip chunk type
gltf = json.loads(f.read(chunk_length).decode('utf-8'))
for m in gltf.get('materials', []):
pbr = m.get('pbrMetallicRoughness', {})
base_color = pbr.get('baseColorFactor', 'NOT SET')
print(f"{m.get('name')}: {base_color}")
EOFHow to verify glass material:
# Extract a skin with glass (Renata skin0)
dotnet run --project LeagueConvert.CommandLine -- convert-wad Renata -o /tmp/test --skins 0
# Check glass params
python3 << 'EOF'
import json, struct
with open('/tmp/test/renata/0/model.glb', 'rb') as f:
f.read(12)
chunk_length = struct.unpack('<I', f.read(4))[0]
f.read(4)
gltf = json.loads(f.read(chunk_length).decode('utf-8'))
for m in gltf.get('materials', []):
extras = m.get('extras', {})
if extras.get('isGlass'):
print(f"{m.get('name')}: {extras.get('glassParams')}")
EOF🚨 Extractor-Specific Pitfalls
Do not "fix" a texture by comparing it to the live game
MISTAKE: seeing that the game renders a champion with X_Base_TX_CM while we export X_Skin08_TX_CM, and switching the extractor to match the game.
Why it's wrong: during TFT's move to Unreal (Set 18 onward) the legacy .skn in the WAD and the mesh the game actually renders are no longer the same asset. They have different UV layouts. The texture the game uses can be flatly wrong on our mesh — Set 18 Xayah renders correctly with the declared skinMeshProperties.texture (_Skin08_) and turns to garbage if you force the in-game _Base_ onto it.
The only test that means anything is whether our model looks right in the viewer. Filenames matching the game prove nothing, and RMSE against a CDragon PNG only tells you which file you got, not whether it belongs on these UVs.
Related: SetDefaultTexture in Skin.cs prefers the explicit texture field over the skin-level Material diffuse, because TFT skins point that Material at their TierUpgrade star-tier overlay. Sivir is the one Set 18 exception — her declared texture is a recall backdrop — and she is patched per-model in model-fixes-extras.yml rather than by changing the rule.
7. Stale PBE Cache
MISTAKE: Using cached WAD files for PBE without clearing cache.
Why it's wrong: PBE (Public Beta Environment) changes frequently. Cached WADs may contain outdated skin data, leading to incorrect extractions or missing new content.
Example of the mistake:
// ❌ WRONG - Uses stale PBE cache
if (File.Exists(cachedPath))
{
return cachedPath; // May be outdated for PBE!
}Correct approach:
// ✅ CORRECT - Clear PBE cache before downloading
if (patchline.Equals("pbe", StringComparison.OrdinalIgnoreCase))
{
if (File.Exists(cachedPath))
{
logger.Information("Clearing cached PBE WAD: {Path}", cachedPath);
File.Delete(cachedPath);
}
}Files affected:
packages/extractor/LeagueConvert.CommandLine/WadDownloader.cs-DownloadWadAsync()
Key rule: Always clear cache for PBE downloads. For live servers, cache is acceptable.
8. Missing Hash Tables
MISTAKE: Not handling hash table loading failures gracefully.
Why it's wrong: Hash tables are required to resolve hashed file paths in WAD files. Without them, the extractor cannot find skin data.
Example of the mistake:
// ❌ WRONG - Crashes if hash tables not loaded
var path = HashTables.Game[hash]; // KeyNotFoundException!Correct approach:
// ✅ CORRECT - Check if hash tables are loaded
if (HashTables.Game == null || !HashTables.Game.ContainsKey(hash))
{
logger.Error("Hash table not loaded or hash not found: {Hash}", hash);
return null;
}
var path = HashTables.Game[hash];Files affected:
packages/extractor/LeagueConvert/IO/HashTables/HashTables.cspackages/extractor/LeagueConvert/IO/WadFile/StringWad.cs
Key rule: Always verify hash tables are loaded before use. Provide fallback loading mechanisms.
9. Incorrect Path Resolution
MISTAKE: Assuming input is always a file path, not a champion name.
Why it's wrong: Users may provide champion names (e.g., "Zoe") expecting the extractor to find or download the WAD automatically.
Example of the mistake:
// ❌ WRONG - Fails if input is champion name
var wad = new StringWad(input); // FileNotFoundException if "Zoe" providedCorrect approach:
// ✅ CORRECT - Resolve input first
string? resolvedPath = null;
if (LeaguePathResolver.IsFilePath(input))
{
resolvedPath = input;
}
else
{
resolvedPath = await LeaguePathResolver.ResolveInputAsync(
input, dataFinalFolder, patchline, allowDownload, logger);
if (resolvedPath == null)
return; // Error already logged
}
var wad = new StringWad(resolvedPath);Files affected:
packages/extractor/LeagueConvert.CommandLine/Program.cs-GetConvertWadCommand()packages/extractor/LeagueConvert.CommandLine/LeaguePathResolver.cs
Key rule: Always check if input is a file path or champion name. Resolve champion names to WAD paths before use.
10. Python Path Detection Issues
MISTAKE: Hardcoding Python path or not handling multiple Python versions.
Why it's wrong: Different systems have Python in different locations. CDTB may be installed for a specific Python version, and using the wrong one causes import errors.
Example of the mistake:
// ❌ WRONG - May use wrong Python version
var pythonPath = "python3"; // May not have CDTB installedCorrect approach:
// ✅ CORRECT - Try multiple paths, prefer specific versions
var paths = new[]
{
"/opt/homebrew/bin/python3.10", // Specific version
"/opt/homebrew/bin/python3.11",
"/opt/homebrew/bin/python3.12",
"/opt/homebrew/bin/python3", // Generic fallback
"python3"
};
foreach (var path in paths)
{
if (IsValidPython(path))
return path;
}Files affected:
packages/extractor/LeagueConvert.CommandLine/WadDownloader.cs-GetPythonPath()
Key rule: Try specific Python versions first (they're more likely to have CDTB), then fall back to generic python3.
📝 Summary Checklist
Before making changes to material/shader handling, verify:
- [ ] Are you using generic parameter names without shader profiles? → DON'T
- [ ] Are you modifying shared texture instances? → Clone first
- [ ] Are you handling procedural shaders correctly? → Generate placeholder
- [ ] Are you checking multiple bloom parameter name variations? → Check all
- [ ] Are you setting both
alphaModeANDbaseColorFactoralpha for transparency? → Set both - [ ] Are you extracting
FlipbookSpeedfor auto-animated flipbooks? → Extract it
Before making changes to the extractor, verify:
- [ ] Are you clearing PBE cache before downloading? → Clear it
- [ ] Are hash tables loaded before use? → Check first
- [ ] Are you handling both file paths and champion names? → Resolve input
- [ ] Are you trying multiple Python paths? → Try specific versions first