feat(art): Synty weapons for the player and the bots#28
Merged
Conversation
Replaces the generated carbine viewmodel with the licensed Synty rifle, arms the bots, and makes every licensed conversion reproducible from one command. Weapons are assembled rather than converted: Synty ships a preset as a receiver plus separate magazine, sight, trigger, slide and charging handle sharing one origin, so `import_synty_weapon.py` globs the parts, joins and welds them, and skips the bipods and fold-out foregrips that read as damage up close. SOCKET_MUZZLE (CLAUDE.md) is placed by measuring the mesh, because the pack is not consistent about which way a weapon faces. The barrel is the longest axis; which end is the muzzle is decided by the pistol grip, which is the lowest part of a firearm and always sits behind the barrel. The first heuristic — "the muzzle end is thinner" — is true of the rifle and the sniper and false of the SMG, whose folding stock is a thin rod, and put that socket on the stock. Rendering a marker at the socket caught it; the coordinates looked reasonable for all three. assets.test.ts now guards each weapon's socket position. Three fixes the screenshots forced out: - The FBX import transform is baked into the mesh. Left alone it is a 0.01 scale and a -90° X rotation that survives export as a node transform, making every child coordinate centimetres in a rotated basis — the muzzle socket read 72.0 rather than 0.72. - `placeAll` gained a `unique` option. It returns hardware instances by default, and assigning to an InstancedMesh's material is a silent no-op, so every viewmodel material change was landing on nothing. Forcing the weapon bright red changed nothing on screen, which is what identified it. - `synty_weapons` is barely metallic. "Gun metal, so make it metallic" renders it black: a metal is lit by what it reflects, this atlas ships no metallic or roughness map, and the environment contribution is turned down for close-range work. Synty paint metal into the albedo instead. tools/art/import-synty.mjs now records the source file, material slot, decimation ratio and atlas for every licensed asset. Those parameters previously lived only in shell history; the props and vehicles it regenerates come back byte-identical, which is how the missing atlas arguments were found. synty_atlas.webp is re-encoded from source so the driver reproduces it too. Shell budget 7.13 MB against the 9 MB guard. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vu1nz Security Review0 finding(s) in PR #? No security issues found. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces the generated carbine viewmodel with the licensed Synty rifle, arms the bots, and makes every licensed conversion reproducible from one command.
What ships
wep_riflewep_smgwep_snipersynty_weapons.webpTwo silhouettes rather than one, so which side a figure is on is legible before the team tint on its marker resolves. The generated
carbineis still built and still ships as a fallback that does not need the licensed pack.Shell budget 7.13 MB against the 9 MB guard.
Assembled, not converted
Synty ships a weapon modular — the preset is a receiver, and the magazine, sight, trigger, slide and charging handle are separate FBX files sharing its origin.
import_synty_weapon.pyglobs the parts, joins and welds them, and skips bipods and fold-out foregrips, which hang below the barrel and read as damage at viewmodel distance.SOCKET_MUZZLE is measured, not assumed
CLAUDE.md requires one on every weapon, and the pack is not consistent about which way a weapon faces. The barrel is the longest axis; which end is the muzzle is decided by the pistol grip, which is the lowest part of a firearm and always sits behind the barrel.
The first heuristic — "the muzzle end is the thinner end" — holds for the rifle and the sniper and fails on the SMG, whose folding stock is a thin rod. Its socket ended up on the stock, where muzzle flash and tracers would have spawned behind the shooter's shoulder. Rendering a marker at the socket is what caught it; the coordinates alone looked reasonable for all three.
assets.test.tsnow guards each weapon's socket position.Three bugs the screenshots forced out
72.0rather than0.72.placeAllgained auniqueoption. It returns hardware instances by default, and assigning to anInstancedMesh's material is a silent no-op — so every viewmodel material change was landing on nothing. Forcing the weapon bright red changed nothing on screen, which is what finally identified it.synty_weaponsis barely metallic. "Gun metal, so make it metallic" renders it black: a metal is lit almost entirely by what it reflects, this atlas ships no metallic or roughness map, and the environment contribution is turned down for close-range work. Synty paint their metal into the albedo instead.Reproducibility
tools/art/import-synty.mjsrecords the source file, material slot, decimation ratio and atlas for every licensed asset. Those parameters previously lived only in one session's shell history. The props and vehicles it regenerates come back byte-identical — which is how the missing atlas arguments were found.synty_atlas.webpis re-encoded from source so the driver reproduces it too (+39 KB).Not wired into
pnpm assets:build: that runs in CI, and this needs the 406 MB purchased pack.Verification
Typecheck, lint, 202 tests and the full build all pass. The viewmodel and bot weapons were checked by driving the built game in a browser and screenshotting
/play, not by eye over the source.🤖 Generated with Claude Code