#!/usr/bin/env python3
"""Build 58k_replace58.js: the kills marked REPLACE NEEDED on every weapon page (the rating bar is disabled for them).
Input: _tools/coord/maptie_audit.jsonl (node _tools/maptie_audit.cjs w1,w2,... out.jsonl). Rule (the user, 27 Oct ~22:00):
generic elements (walls etc.) that most maps have are fine; a kill that can play on only a FEW maps (<= 8 of the 17 dry maps,
for missing map elements; ROOM/FOOTING staging and the flooded-sunken DRY FLOOR exception ignored) is REPLACE NEEDED.
Kills already rebuilt in their slot are left out; slots being rebuilt carry their state."""
import json, collections, os
HERE = os.path.dirname(os.path.abspath(__file__)); ROOT = os.path.dirname(HERE)
SPACE = {'ROOM', 'FOOTING', 'SPACE'}; WATEREXC = {'DRY FLOOR', 'NO UNDERWATER'}; LIMIT = 8
REBUILDING = {}
# slots whose NEW kill is already live on the page (rough or final): no longer marked, so the user can watch and rate the new kill
REPLACED_LIVE = {
  'pump.glass-jaw': 'CLEAR THE AIR',
  'flamer.brazen-bull': 'PULLED',
  'scythe.threshing-floor': 'CROSSGUARD',
  'ar.skin-cartoon': 'SMEAR FRAME',
  'knife.splice': 'STACKED',
  'ice.wipe': 'BACK TURNED',
  'minigun.hold-still': 'OUT OF TRUE',
  'laser.mid-the-glint': 'DEAD STOP',
  'glue.immovable-object': 'PULLED DOWN',
  'flamer.poached': 'SAFETY ON',
  'rpg.the-tail': 'ONE ARM',
  'pump.his-heart': 'FLOOR LEVEL',
  'ar.searchlight': 'SKIP FIRE',
  'rpg.in-the-works': 'UP AND OVER',
  'minigun.the-gap': 'NOT DOWN YET',
  'glue.bungee': 'SLIPSTREAM', 'flamer.molten-rain': 'SPARE',
  'minigun.locked': 'CARAPACE',
  'pump.pull': 'OFF THE LINE',
  'rpg.half-mast': 'THE QUIVER', 'flamer.skin-classic': 'FIGHT FIRE WITH FIRE', 'ar.rewind': 'DRY FIRE', 'minigun.skin-neon': 'THE GRID',
  'flamer.flash-in-the-pan': 'PILOT LIGHT',
  'minigun.skin-camo': 'SPOKES', 'handcannon.mid-swallowed': 'SHOULDER THROW',
  'smg.house': 'OPEN HOUSE', 'revolver.drip': 'DRIP', 'minigun.red-hot': 'RED HOT',
  'pistol.mid-black-round': 'UNDERNEATH', 'pistol.mid-x-ring': 'PASSING SHOT', 'pistol.mid-in-amber': 'LOW ROAD',
  'pistol.mid-live-wire': 'SLIDE LOCK', 'pistol.skin-ice': 'THAW', 'pistol.skin-taped': 'TAPED',
  'golden.eureka': 'GOLD THREAD', 'smg.mid-tin-star': 'HANDSPRING', 'smg.mid-the-take': 'THE CATCH',
  'minigun.brrrt': 'HOMECOMING', 'golden.never-runs-dry': 'LIGHTHOUSE', 'smg.skin-cartoon': 'SPEED LINES',
  'smg.mid-trousers': 'THE TURN', 'smg.mid-trampoline': 'SKID ROW', 'hammer.skipping': 'DEAD WEIGHT',
  'revolver.optogram': 'THE LATCH', 'hammer.high-striker': 'SCRAP',
  'pistol.overflow': 'DEAD RUN', 'minigun.window-dressing': 'SIX',
  'hammer.mid-ketchup': 'RAM', 'hammer.mole-hill': 'LOW BLOW', 'hammer.cpr': 'TOO HEAVY', 'hammer.hanger-on': 'THE NAIL', 'hammer.tent-peg': 'IRON AND GOLD',
  'golden.the-mimic': 'THE MIMIC', 'fists.re-entry': 'AIR JUGGLE', 'pistol.going-down': 'BOOKENDS', 'revolver.bandana': 'TAILED', 'fists.seven-points': 'BELL TO BELL', 'minigun.quiet': 'BETWEEN ROUNDS',
  'pistol.down-the-barrel': 'STILL COMING', 'pistol.the-pendulum': 'NO MORE TRICKS', 'golden.the-swap': 'HOT BARREL', 'knife.telefrag': 'SUPLEX', 'beam.skin-neon': 'THE SIGN YARD', 'revolver.mid-film-burn': 'SLIP', 'revolver.mid-quicksilver': 'BORDER SHIFT', 'fists.slam-dunk': 'POWERBOMB', 'golden.ribbon-cutting': 'KNIFE THROWER', 'revolver.skin-camo': 'LOW CRAWL', 'revolver.skin-neon': 'STROBE', 'sniper.the-long-way-in': 'THE BOLT', 'knife.flayed': 'BREAKOUT', 'golden.skin-camo': 'ACTIVE CAMO', 'golden.mid-the-looter': 'ALONG THE ARM', 'golden.mid-the-tray': 'THE STOOP', 'golden.mid-the-bite-test': 'HAYMAKER',
  'knife.skin-taped': 'FIELD DRESSING', 'knife.mid-right-through': 'OUT AND IN', 'knife.mid-pockets': 'LAUNCHED', 'glue.hang-time': 'STOP MOTION', 'laser.metronome': 'SEVEN A SECOND', 'handcannon.left-behind': 'TOP BREAK',
  'fists.mid-blown-pupil': 'THE PLUM', 'knife.skin-gold': 'LINES OF GOLD', 'knife.crossed-out': 'UNDER THE GUN', 'handcannon.burial-at-sea': 'AIR MAIL', 'handcannon.made-to-measure': 'RECOIL SPIN', 'minigun.the-press': 'THE LEAD', 'knife.pickpocket': 'THE PRICE', 'knife.bear-hug': 'SPLIT SECOND', 'laser.lightning-rod': 'TOURNIQUET',
  'beam.portal': 'HOT SWAP', 'beam.skin-cartoon': 'BACKSWING', 'beam.half-a-second': 'LOADED', 'beam.skin-camo': 'VENT', 'beam.morse': 'SPLITTER', 'beam.mid-tan-lines': 'SAME LINE', 'beam.mid-afterimage': 'FIRST BLOOD', 'beam.mid-bleached': 'SKID', 'glue.concrete-shoes': 'BACKFIRE',
  'fists.mid-laid-down': 'THE BACKFIST',
  'laser.scarecrow': 'GOLD IS A MIRROR',
  'laser.strike': 'RAILGUN',
  'knife.skin-cartoon': 'FOLLOW-THROUGH',
  'sniper.mascot': 'THE GLINT',
  'speedsabre.skin-gold': 'GILT EDGE',  # PAGE-SKIN 1 Nov
  'glue.skin-gold': 'AMBER',  # PAGE-SKIN 1 Nov
  'handcannon.skin-ice': 'CURLING STONE',  # PAGE-SKIN-GOLD 1 Nov
  'sawedoff.skin-ice': 'ROOTED',  # PAGE-SKIN-GOLD 1 Nov
  'shrink.skin-gold': 'THE LOCKET',  # PAGE-SKIN-GOLD 1 Nov
  'tkglove.skin-ice': 'THE WRING',  # PAGE-SKIN-GOLD 1 Nov
  'speedsabre.skin-ice': 'LAST BREATH',  # PAGE-SKIN-GOLD 1 Nov
  'ice.skin-gold': 'ON THE ROCKS',  # PAGE-SKIN-GOLD 1 Nov
  'ice.skin-ice': 'SEED CRYSTAL',  # PAGE-SKIN-ICE 1 Nov
  'shrink.skin-ice': 'SQUARE CUBE',  # PAGE-SKIN-ICE 1 Nov
  'gravflip.skin-ice': 'HAIL',  # PAGE-SKIN-ICE 1 Nov
  'spear.skin-gold': 'THE STANDARD',  # PAGE-SKIN-GOLD 1 Nov
}
# kills the USER rated REPLACE (their slot gets a new kill); they show the same disabled mark
USER_REPLACE = {
  "fists.seven-points": ("fists", "SEVEN POINTS"),
  "minigun.mid-matchwood": ("minigun", "ROLL BACK"),
  "minigun.quiet": ("minigun", "QUIET"),
  "minigun.the-press": ("minigun", "THE HOLD"),
  "minigun.locked": ("minigun", "LOCKED"),
  "minigun.hold-still": ("minigun", "HOLD STILL"),
  "minigun.the-cone": ("minigun", "THE LAWN"),
  "minigun.tatters": ("minigun", "DEAD RINGER"),
  "minigun.skin-classic": ("minigun", "ZIGZAG"),
  "minigun.skin-neon": ("minigun", "FOUR FOR A DOLLAR"),
  "minigun.skin-camo": ("minigun", "THE BLUR"),
  'golden.the-mimic': ('golden', 'THE MIMIC'), 'golden.the-swap': ('golden', 'THE SWAP'), 'golden.ribbon-cutting': ('golden', 'RIBBON CUTTING'),
  'golden.skin-camo': ('golden', 'NEEDLE IN A HAYSTACK'), 'golden.mid-the-looter': ('golden', 'THE LOOTER'), 'golden.mid-the-tray': ('golden', 'THE TRAY'),
  'golden.mid-the-bite-test': ('golden', 'THE BITE TEST'), 'minigun.window-dressing': ('minigun', 'WINDOW DRESSING'),
  'smg.skin-cartoon': ('smg', 'UH-OH'), 'smg.mid-trampoline': ('smg', 'TRAMPOLINE'), 'smg.mid-tin-star': ('smg', 'TIN STAR'),
  'smg.mid-the-take': ('smg', 'THE TAKE'), 'smg.mid-trousers': ('smg', 'TROUSERS'),
  'pistol.overflow': ('pistol', 'OVERFLOW'), 'pistol.down-the-barrel': ('pistol', 'DOWN THE BARREL'), 'pistol.going-down': ('pistol', 'GOING DOWN'),
  'pistol.skin-taped': ('pistol', 'THUP'), 'pistol.mid-black-round': ('pistol', 'THE BLACK ROUND'), 'pistol.mid-x-ring': ('pistol', 'X RING'),
  'revolver.optogram': ('revolver', 'OPTOGRAM'), 'revolver.bandana': ('revolver', 'BANDANA'),
  'revolver.skin-camo': ('revolver', 'DAZZLE'), 'revolver.mid-film-burn': ('revolver', 'FILM BURN'), 'revolver.mid-quicksilver': ('revolver', 'QUICKSILVER'), 'revolver.skin-neon': ('revolver', 'MARQUEE'),
}
out = {}
for line in open(os.path.join(HERE, 'coord', 'maptie_audit.jsonl')):
    r = json.loads(line)
    if 'err' in r: continue
    el = {}; 
    for s in r['skips']:
        m, _, lab = s.partition(':'); L = [x.strip() for x in lab.split('+')] if lab else ['?']
        if all(x in SPACE or x in WATEREXC for x in L): continue
        for x in L:
            if x not in SPACE and x not in WATEREXC: el.setdefault(x, []).append(m)
    maps = set(m for v in el.values() for m in v); play = 17 - len(maps)
    if play > LIMIT: continue
    # UNDERWATER EXCEPTION: kills made for the underwater map (The Deep) are its only kills; they stay (the user's exception)
    if el and any('UNDERWATER' in k for k in el) and all('UNDERWATER' in k or k in ('LIQUID', 'DEEP WATER') for k in el): continue
    need = ', '.join(sorted(el, key=lambda k: -len(el[k]))[:3])
    out[r['key']] = {'w': r['w'], 'name': r['name'], 'play': play, 'need': need,
                     'state': REBUILDING.get(r['key'], 'waiting: a new cinematic kill will take this slot')}
# kills the COORDINATOR rated REPLACE in autonomous mode (predicted from the user's taste, _tools/coord/USER_TASTE.md)
COORD_REPLACE = {
  "tpdagger.back-of-beyond": ("tpdagger", "THE BACK OF BEYOND"),
  "tpdagger.inside-the-house": ("tpdagger", "INSIDE THE HOUSE"),
  "tpdagger.guess-who": ("tpdagger", "GUESS WHO"),
  "tpdagger.wait-him-out": ("tpdagger", "WAIT HIM OUT"),
  "tpdagger.look-out": ("tpdagger", "THE LOOK-OUT"),
  "tpdagger.the-cord": ("tpdagger", "THE CORD"),
  "tpdagger.skin-cartoon": ("tpdagger", "UNZIPPED"),
  "speedsabre.skin-ice": ("speedsabre", "CURLING"),
  "speedsabre.skin-gold": ("speedsabre", "THE GILDED TOMB (DEATH REALM)"),
  "speedsabre.mid-tickled": ("speedsabre", "TICKLED"),
  "spear.maypole": ("spear", "MAYPOLE"),
  "spear.the-door": ("spear", "THE DOOR"),
  "spear.led-by-the-nose": ("spear", "LED BY THE NOSE"),
  "spear.swept-under-the-rug": ("spear", "SWEPT UNDER THE RUG"),
  "spear.spokes": ("spear", "SPOKES"),
  "spear.skin-classic": ("spear", "THE SENTRY"),
  "spear.skin-neon": ("spear", "SINGIN' IN THE RAIN"),
  "spear.mid-trapdoor": ("spear", "TRAPDOOR"),
  "spear.mid-the-tap": ("spear", "THE TAP"),
  "burst.see-no-evil": ("burst", "SEE NO EVIL"),
  "burst.glass-eye": ("burst", "GLASS EYE"),
  "burst.punch-and-judy": ("burst", "PUNCH AND JUDY"),
  "burst.three-in-a-row": ("burst", "THREE IN A ROW"),
  "burst.polly": ("burst", "POLLY"),
  "burst.party-popper": ("burst", "PARTY POPPER"),
  "burst.sleeves": ("burst", "SLEEVES"),
  "burst.panorama": ("burst", "PANORAMA"),
  "burst.awning": ("burst", "AWNING"),
  "burst.side-on": ("burst", "SIDE-ON"),
  "cluster.hexagon": ("cluster", "HEXAGON"),
  "cluster.foxhole": ("cluster", "FOXHOLE"),
  "cluster.clean-sheet": ("cluster", "CLEAN SHEET"),
  "cluster.mid-tail-chase": ("cluster", "TAIL CHASE"),
  "cluster.mid-ping-pong": ("cluster", "PING PONG"),
  "cluster.mid-drop-the-beat": ("cluster", "DROP THE BEAT"),
  "cluster.skin-classic": ("cluster", "SIT ON IT"),
  "cluster.skin-cartoon": ("cluster", "THE MIME"),
  "cluster.brolly": ("cluster", "BROLLY"),
  "cluster.joyride": ("cluster", "JOYRIDE"),
  "cluster.bunting": ("cluster", "BUNTING"),
  "cluster.sixies": ("cluster", "SIXIES"),
  "cluster.cannonball": ("cluster", "CANNONBALL"),
  "cluster.bouncy-castle": ("cluster", "BOUNCY CASTLE"),
  "cluster.the-hamster-wheel": ("cluster", "THE HAMSTER WHEEL"),
  "cluster.duck-and-cover": ("cluster", "DUCK AND COVER"),
  "cluster.empty-bin": ("cluster", "EMPTY BIN"),
  "tkglove.seance": ("tkglove", "THE S\u00c9ANCE"),
  "tkglove.orrery": ("tkglove", "THE ORRERY"),
  "tkglove.ping": ("tkglove", "PING"),
  "tkglove.up": ("tkglove", "UP"),
  "tkglove.inside-out": ("tkglove", "INSIDE OUT"),
  "tkglove.davy-jones": ("tkglove", "DAVY JONES' LOCKER"),
  "tkglove.wastepaper": ("tkglove", "WASTEPAPER"),
  "tkglove.skin-cartoon": ("tkglove", "BALLOON ANIMAL"),
  "tkglove.skin-taped": ("tkglove", "SPLINT"),
  "tkglove.skin-ice": ("tkglove", "CORE SAMPLE"),
  "tkglove.skin-neon": ("tkglove", "LAVA LAMP"),
  "tkglove.mid-breathless": ("tkglove", "BREATHLESS"),
  "tkglove.mid-toothpaste": ("tkglove", "TOOTHPASTE"),
  "tkglove.mid-the-fly": ("tkglove", "THE FLY"),
  "portalgun.excuse-me": ("portalgun", "EXCUSE ME"),
  "portalgun.the-doorway-effect": ("portalgun", "THE DOORWAY EFFECT"),
  "knife.vanishing-act": ("knife", "VANISHING ACT"),
  "knife.splice": ("knife", "SPLICE"),
  "knife.chandelier": ("knife", "CHANDELIER"),
  "gravflip.over-the-top": ("gravflip", "OVER THE TOP"),
  "gravflip.up-the-chimney": ("gravflip", "UP THE CHIMNEY"),
  "gravflip.fall-guy": ("gravflip", "FALL GUY"),
  "gravflip.skylight": ("gravflip", "SKYLIGHT"),
  "gravflip.ceiling-fan": ("gravflip", "THE CEILING FAN"),
  "gravflip.hornets-nest": ("gravflip", "THE HORNETS' NEST"),
  "gravflip.last-air": ("gravflip", "THE LAST AIR"),
  "gravflip.skin-classic": ("gravflip", "HUMPTY DUMPTY"),
  "gravflip.skin-cartoon": ("gravflip", "HIT THE ROOF"),
  "gravflip.skin-camo": ("gravflip", "COVERED"),
  "gravflip.skin-ice": ("gravflip", "SUPERCOOLED"),
  "gravflip.skin-neon": ("gravflip", "CLOSED CIRCUIT"),
  "pan.grease-fire": ("pan", "GREASE FIRE"),
  "pan.cold-sweat": ("pan", "COLD SWEAT"),
  "pan.senseless": ("pan", "SENSELESS"),
  "pan.panic": ("pan", "PANIC"),
  "pan.skin-classic": ("pan", "LATE HOME"),
  "shrink.snap": ("shrink", "SNAP"),
  "shrink.booted": ("shrink", "BOOTED"),
  "shrink.noon": ("shrink", "NOON"),
  "shrink.smoothie": ("shrink", "SMOOTHIE"),
  "shrink.the-parlour": ("shrink", "THE PARLOUR"),
  "shrink.paper-boat": ("shrink", "PAPER BOAT"),
  "shrink.the-cat": ("shrink", "THE CAT"),
  "shrink.the-nest": ("shrink", "THE NEST"),
  "shrink.army-ants": ("shrink", "ARMY ANTS"),
  "shrink.the-dust": ("shrink", "THE DUST"),
  "shrink.anemone": ("shrink", "ANEMONE"),
  "shrink.mid-socket": ("shrink", "THE SOCKET"),
  "shrink.mid-stubbed-out": ("shrink", "STUBBED OUT"),
  "shrink.mid-skirting": ("shrink", "THE SKIRTING"),
  "shrink.mid-scraped": ("shrink", "SCRAPED"),
  "shrink.skin-taped": ("shrink", "HALF MEASURES"),
  "shrink.skin-camo": ("shrink", "THE KNOT"),
  "shrink.skin-cartoon": ("shrink", "IRIS OUT"),
  "shrink.skin-ice": ("shrink", "THE LAST MATCH"),
  "shrink.skin-neon": ("shrink", "THE ZAPPER"),
  "shrink.skin-gold": ("shrink", "THE SETTING"),
  "flamer.jerky": ("flamer", "JERKY"),
  "flamer.poached": ("flamer", "POACHED"),
  "flamer.wicker-giant": ("flamer", "THE WICKER GIANT"),
  "flamer.brazen-bull": ("flamer", "THE BRAZEN BULL"),
  "flamer.viking-funeral": ("flamer", "VIKING FUNERAL"),
  "flamer.molten-rain": ("flamer", "MOLTEN RAIN"),
  "flamer.mirage": ("flamer", "MIRAGE"),
  "flamer.terracotta": ("flamer", "TERRACOTTA"),
  "flamer.toasted": ("flamer", "TOASTED"),
  "flamer.the-urn": ("flamer", "THE URN"),
  "flamer.wet-shot": ("flamer", "WET SHOT"),
  "flamer.afterburner": ("flamer", "AFTERBURNER"),
  "flamer.flash-in-the-pan": ("flamer", "FLASH IN THE PAN"),
  "flamer.skin-classic": ("flamer", "WELL DONE"),
  "pump.dollhouse": ("pump", "DOLLHOUSE"),
  "knife.bear-hug": ("knife", "BEAR HUG"),
  "knife.crossed-out": ("knife", "CROSSED OUT"),
  "knife.telefrag": ("knife", "TELEFRAG"),
  "knife.pickpocket": ("knife", "PICKPOCKET"),
  "knife.flayed": ("knife", "FLAYED"),
  "knife.skin-taped": ("knife", "BUTTERFINGERS"),
  "knife.skin-cartoon": ("knife", "FLIP BOOK"),
  "knife.skin-gold": ("knife", "THE AUCTION"),
  "knife.mid-right-through": ("knife", "RIGHT THROUGH"),
  "knife.mid-pockets": ("knife", "POCKETS"),
  "bouncer.mid-lumps": ("bouncer", "LUMPS"),
  "bouncer.the-piston": ("bouncer", "THE PISTON"),
  "bouncer.mid-firefly": ("bouncer", "FIREFLY"),
  "bouncer.mid-the-tumbler": ("bouncer", "THE TUMBLER"),
  "sniper.the-long-way-in": ("sniper", "THE LONG WAY IN"),
  "sniper.the-bait": ("sniper", "THE BAIT"),
  "sniper.mascot": ("sniper", "MASCOT"),
  "sniper.bone-flowers": ("sniper", "BONE FLOWERS"),
  "sniper.the-last-look": ("sniper", "THE LAST LOOK"),
  "sniper.eight-bells": ("sniper", "EIGHT BELLS"),
  "sniper.hold-your-breath": ("sniper", "HOLD YOUR BREATH"),
  "sniper.the-cloud": ("sniper", "THE CLOUD"),
  "sniper.sling": ("sniper", "SLING"),
  "sniper.rehearsal": ("sniper", "REHEARSAL"),
  "sniper.the-brass": ("sniper", "THE BRASS"),
  "sniper.the-bag": ("sniper", "THE BAG"),
  "sniper.the-watched": ("sniper", "THE WATCHED"),
  "pump.glass-jaw": ("pump", "GLASS JAW"),
  "pump.his-heart": ("pump", "HIS HEART"),
  "pump.mid-curtain-call": ("pump", "CURTAIN CALL"),
  "pump.mid-four-eyes": ("pump", "FOUR EYES"),
  "pump.mid-potato": ("pump", "POTATO"),
  "pump.mid-stuck-the-landing": ("pump", "STUCK THE LANDING"),
  "ar.rewind": ("ar", "TAKE THREE"),
  "ar.searchlight": ("ar", "SEARCHLIGHT"),
  "ar.skin-cartoon": ("ar", "PAINTED TUNNEL"),
  "ar.mid-stand-off": ("ar", "STAND-OFF"),
  "ar.mid-t-pose": ("ar", "T-POSE"),
  "ar.mid-overdose": ("ar", "OVERDOSE"),
  "ar.mid-newspaper": ("ar", "NEWSPAPER"),
  "scythe.threshing-floor": ("scythe", "THE THRESHING FLOOR"),
  "scythe.ossuary": ("scythe", "THE OSSUARY"),
  "scythe.bag-of-bones": ("scythe", "BAG OF BONES"),
  "scythe.rock-a-bye": ("scythe", "ROCK-A-BYE"),
  "scythe.bale": ("scythe", "THE BALE"),
  "scythe.skin-cartoon": ("scythe", "HARP"),
  "scythe.mid-nevermore": ("scythe", "NEVERMORE"),
  "scythe.puffer": ("scythe", "PUFFER"),
  "sawedoff.the-barber": ("sawedoff", "THE BARBER"),
  "sawedoff.shotgun-wedding": ("sawedoff", "SHOTGUN WEDDING"),
  "sawedoff.last-chord": ("sawedoff", "LAST CHORD"),
  "sawedoff.the-mask": ("sawedoff", "THE MASK"),
  "sawedoff.hanging-on": ("sawedoff", "HANGING ON"),
  "sawedoff.skin-classic": ("sawedoff", "GET OFF MY LAWN"),
  "sawedoff.skin-ice": ("sawedoff", "BLACK ICE"),
  "sawedoff.skin-neon": ("sawedoff", "LAST SONG"),
  "thammer.galvanised": ("thammer", "GALVANISED"),
  "thammer.lichtenberg": ("thammer", "LICHTENBERG"),
  "thammer.ball-lightning": ("thammer", "BALL LIGHTNING"),
  "thammer.forge": ("thammer", "FORGE"),
  "thammer.storm-chaser": ("thammer", "STORM CHASER"),
  "thammer.forecast": ("thammer", "FORECAST"),
  "thammer.world-record": ("thammer", "WORLD RECORD"),
  "thammer.skin-cartoon": ("thammer", "CRISPY"),
  "thammer.skin-camo": ("thammer", "DANGER CLOSE"),
  "ice.on-the-rocks": ("ice", "ON THE ROCKS"),
  "ice.lawn-dart": ("ice", "LAWN DART"),
  "ice.triple-dog-dare": ("ice", "TRIPLE DOG DARE"),
  "ice.wipe": ("ice", "WIPE"),
  "ice.whiteout": ("ice", "WHITEOUT"),
  "ice.skin-classic": ("ice", "JACK FROST"),
  "ice.skin-camo": ("ice", "BIRD STRIKE"),
  "ice.skin-neon": ("ice", "LIT"),
  "ice.skin-gold": ("ice", "GOLDEN HOUR"),
  "ice.skin-ice": ("ice", "PERMAFROST"),
  "gl.tablecloth": ("gl", "TABLECLOTH"),
  "gl.the-tent": ("gl", "THE TENT"),
  "gl.fetch": ("gl", "FETCH"),
  "gl.snooze": ("gl", "SNOOZE"),
  "gl.cook-out": ("gl", "THE COOK-OUT"),
  "gl.over-the-wall": ("gl", "OVER THE WALL"),
  "gl.the-long-way": ("gl", "THE LONG WAY"),
  "gl.touchdown": ("gl", "TOUCHDOWN"),
  "gl.treasure": ("gl", "TREASURE"),
  "rpg.headlights": ("rpg", "HEADLIGHTS"),
  "rpg.brace-position": ("rpg", "BRACE POSITION"),
  "rpg.skin-camo": ("rpg", "THE SKY"),
  "rpg.skin-neon": ("rpg", "SIGN OFF"),
  "rpg.skin-classic": ("rpg", "THE FIRST ROCKET"),
  "rpg.mid-outrun": ("rpg", "OUTRUN"),
  "rpg.mid-speck": ("rpg", "SPECK"),
  "rpg.say-cheese": ("rpg", "SAY CHEESE"),
  "rpg.skin-cartoon": ("rpg", "SEEING STARS"),
  "rpg.mid-fins": ("rpg", "FINS"),
  "rpg.half-mast": ("rpg", "HALF-MAST"),
  "rpg.in-the-works": ("rpg", "IN THE WORKS"),
  "rpg.last-floor": ("rpg", "LAST FLOOR"),
  "rpg.mid-from-above": ("rpg", "FROM ABOVE"),
  "rpg.mid-skipping-stone": ("rpg", "SKIPPING STONE"),
  "rpg.mid-the-crate": ("rpg", "THE CRATE"),
  "rpg.the-safest-place": ("rpg", "THE SAFEST PLACE"),
  "rpg.the-tail": ("rpg", "THE TAIL"),
  "rpg.tilt": ("rpg", "TILT"),
  "glue.the-glue-factory": ("glue", "THE GLUE FACTORY"),
  "glue.eyes-wide-open": ("glue", "EYES WIDE OPEN"),
  "glue.sitting-duck": ("glue", "SITTING DUCK"),
  "glue.mid-model-kit": ("glue", "MODEL KIT"),
  "glue.mid-prints": ("glue", "PRINTS"),
  "glue.mid-shelf-life": ("glue", "SHELF LIFE"),
  "glue.mid-sole-mate": ("glue", "SOLE MATE"),
  "laser.four-corners": ("laser", "FOUR CORNERS"),
  "laser.skin-cartoon": ("laser", "ACCORDION"),
  "laser.mid-hiccups": ("laser", "HICCUPS"),
  "laser.mid-the-glint": ("laser", "THE GLINT"),
  "laser.mid-pratfall": ("laser", "PRATFALL"),
  "beam.half-a-second": ("beam", "HALF A SECOND"),
  "beam.morse": ("beam", "MORSE"),
  "beam.portal": ("beam", "PORTAL"),
  "beam.skin-camo": ("beam", "TRENCH COAT"),
  "beam.skin-cartoon": ("beam", "ERASED"),
  "beam.skin-neon": ("beam", "HALO"),
  "beam.mid-tan-lines": ("beam", "TAN LINES"),
  "beam.mid-afterimage": ("beam", "AFTERIMAGE"),
  "beam.mid-bleached": ("beam", "BLEACHED"),
  "hammer.tent-peg": ("hammer", "TENT PEG"),
  "hammer.skipping": ("hammer", "SKIPPING"),
  "hammer.high-striker": ("hammer", "HIGH STRIKER"),
  "hammer.hanger-on": ("hammer", "HANGER-ON"),
  "hammer.mole-hill": ("hammer", "MOLE HILL"),
  "hammer.cpr": ("hammer", "CPR"),
  "hammer.mid-ketchup": ("hammer", "KETCHUP"),
  "fists.re-entry": ("fists", "RE-ENTRY"),
  "fists.slam-dunk": ("fists", "SLAM DUNK"),
  "fists.mid-blown-pupil": ("fists", "BLOWN PUPIL"),
  "fists.mid-laid-down": ("fists", "LAID DOWN"),
  "handcannon.left-behind": ('handcannon', "LEFT-BEHIND"),
  "handcannon.burial-at-sea": ('handcannon', "BURIAL-AT-SEA"),
  "handcannon.made-to-measure": ('handcannon', "MADE-TO-MEASURE"),
  "handcannon.bullet-catch": ('handcannon', "BULLET-CATCH"),
  "handcannon.mid-staring-contest": ('handcannon', "MID-STARING-CONTEST"),
  "handcannon.mid-swallowed": ('handcannon', "MID-SWALLOWED"),
  "handcannon.mid-burp": ('handcannon', "MID-BURP"),
}
for k, (w, n) in COORD_REPLACE.items():
    if k not in out: out[k] = {'w': w, 'name': n, 'play': 17, 'need': '', 'user': True, 'state': "the coordinator's call (predicted from your ratings): a new cinematic kill will take this slot"}
for k, (w, n) in USER_REPLACE.items():
    if k not in out: out[k] = {'w': w, 'name': n, 'play': 17, 'need': '', 'user': True, 'state': 'you rated it REPLACE: a new kill will take this slot'}
for k in REPLACED_LIVE: out.pop(k, None)
js = ('/* 58k_replace58.js · GENERATED by _tools/replace58_gen.py (coordinator). Kills that play on only a few maps = REPLACE NEEDED.\n'
      '   The rating bar (58k_rate.js) shows them disabled with the REPLACE NEEDED mark so they are not rated by accident. */\n'
      '(function () { const K58 = window.K58 = window.K58 || {}; K58.REPLACE58 = ' + json.dumps(out, ensure_ascii=False, indent=0, sort_keys=True) + '; K58.REPLACED_LIVE = ' + json.dumps(REPLACED_LIVE, ensure_ascii=False, indent=0, sort_keys=True) + '; })();\n')
open(os.path.join(ROOT, '58k_replace58.js'), 'w').write(js)
c = collections.Counter(v['w'] for v in out.values())
print(len(out), 'kills marked'); print(dict(sorted(c.items())))
