Make Player.grenades a pocket with a slot per kind instead of one integer, so a second kind of grenade is data rather than a second mechanism.
world.ts:428 is export const MAX_GRENADES = 1, buy (world.ts:459) does player.grenades += 1 for anything whose kind is "grenade", and canBuyItem refuses a second with player.grenades < MAX_GRENADES. Slot 4 draws he and only he. Every grenade this game will ever have shares that one integer, which is why #37 (smoke) has been unbuildable since #36 landed. This issue is only the pocket: no new grenade, and nothing a player sees beyond being able to carry one of each once there is more than one kind.
Done looks like:
[ ] The pocket is per kind — one of each, with the total ceiling stated as a constant beside it and not spread through canBuyItem.
[ ] Slot 4 has a written rule for which kind it draws when the pocket holds two, and how you reach the other (pressing 4 again cycles is the cheapest; say it in the comment, prove it in a test). With one kind carried, slot 4 behaves exactly as today.
[ ] buy, canBuyItem, startRound's re-arm, the throw in grenades.ts and the drop on death all go through one helper each; no call site counts grenades by hand.
[ ] hud.ts shows the pocket with no layout shift when it is empty, and the buy panel's HE button still greys with the same reason text from buy.ts.
[ ] observe.ts's 32 inputs are untouched — the shipped policy's numbers must keep meaning what they meant when it was trained. If an observation changes, say so in the pull and re-measure with scripts/measure.ts.
[ ] Tests in world.test.ts that fail without it: a second HE is refused; the pocket survives a round boundary; slot 4 on an empty pocket keeps the gun in your hands.
Files:src/client/world.ts, src/client/buy.ts, src/client/grenades.ts, src/client/hud.ts and their tests.
Risks: it edits the middle of world.ts, so it conflicts with anything else there — do not run it beside pickup or economy work. The policy's observation vector is the thing to leave alone.
Crew: scout — it is rules, plain data in and out, with tests.
Make
Player.grenadesa pocket with a slot per kind instead of one integer, so a second kind of grenade is data rather than a second mechanism.world.ts:428isexport const MAX_GRENADES = 1,buy(world.ts:459) doesplayer.grenades += 1for anything whosekindis"grenade", andcanBuyItemrefuses a second withplayer.grenades < MAX_GRENADES. Slot 4 drawsheand onlyhe. Every grenade this game will ever have shares that one integer, which is why #37 (smoke) has been unbuildable since #36 landed. This issue is only the pocket: no new grenade, and nothing a player sees beyond being able to carry one of each once there is more than one kind.Done looks like:
canBuyItem.buy,canBuyItem,startRound's re-arm, the throw ingrenades.tsand the drop on death all go through one helper each; no call site counts grenades by hand.hud.tsshows the pocket with no layout shift when it is empty, and the buy panel's HE button still greys with the same reason text frombuy.ts.observe.ts's 32 inputs are untouched — the shipped policy's numbers must keep meaning what they meant when it was trained. If an observation changes, say so in the pull and re-measure withscripts/measure.ts.world.test.tsthat fail without it: a second HE is refused; the pocket survives a round boundary; slot 4 on an empty pocket keeps the gun in your hands.Files:
src/client/world.ts,src/client/buy.ts,src/client/grenades.ts,src/client/hud.tsand their tests.Risks: it edits the middle of
world.ts, so it conflicts with anything else there — do not run it beside pickup or economy work. The policy's observation vector is the thing to leave alone.Crew: scout — it is rules, plain data in and out, with tests.