Zombies/SpecialEnemies.cs

Static helper class that lists special enemy IDs, their asset paths, descriptions, and helpers to query variants and categorize them as specials or bosses. Provides constants for each special id, arrays of names, methods PathFor, DescriptionFor, VariantFor, IsKnown, IsSpecial, IsBoss, and computed lists for BossNames/NonBossNames and fallbacks.

File Access
using Sandbox;
using System.Linq;

namespace NZombies;

/// <summary>
/// SPECIALS — the roster a special spawner can be pointed at.
///
/// A special is any enemy that is NOT the walker: it arrives on its own rounds,
/// from its own spawners, instead of joining the normal wave. Today that is one
/// hellhound; the roster exists so the second one is a line of data rather than
/// a second spawner tool.
///
/// ⛔ A SWITCH, NOT A `static readonly Dictionary`. Static initialisers do NOT
/// re-run on hotload, so a dictionary built in its own initialiser can never be
/// corrected in a live session — the seventh occurrence of that trap is written
/// up in INSTRUCTIONS.md pattern 1. A switch is evaluated per call and always
/// reflects the source.
/// </summary>
public static class SpecialEnemies
{
	/// <summary>The id stored in a spawner and in the save file.
	///
	/// ⚠️ THIS STRING IS PERSISTED. Renaming one silently orphans every spawner
	/// in every saved config that referenced it, the same way renumbering a
	/// persisted enum would. Add new ids freely; change existing ones never.</summary>
	public const string Hellhound = "hellhound";

	/// <summary>Brutus — the first boss. See `BrutusHelmet` for the fight.</summary>
	public const string Brutus = "brutus";

	/// <summary>
	/// The Napalm Zombie — BO1 Shangri-La's suicide bomber. See `NapalmZombie` for the fight.
	///
	/// ⚠️ A SPECIAL, NOT A BOSS, AND ITS `.zvar` SAYS `IsBoss: false` TO MATCH. Upstream calls it
	/// `IsMiniBoss`, which this project has no third tier for — and the tier that fits is the one
	/// that arrives during an ordinary round rather than replacing it, because that is how the
	/// enemy is used.
	/// </summary>
	public const string Napalm = "napalm";

	/// <summary>
	/// The Shrieker — BO1 Shangri-La's screamer. See `ShriekerZombie` for the fight.
	///
	/// ⚠️ A SPECIAL FOR THE SAME REASON THE NAPALM IS, and upstream marks it `IsMiniBoss` too. It
	/// arrives during a round rather than replacing one, which is the only distinction this
	/// project draws.
	/// </summary>
	public const string Shrieker = "shrieker";

	/// <summary>
	/// The Pest — CoD WWII's sprinter, upstream's `nz_zombie_special_sprinter`.
	///
	/// ⚠️ THE ONLY ONE OF THESE WITH NO COMPONENT OF ITS OWN, and deliberately. Brutus has a
	/// helmet, the Napalm Zombie erupts, the Shrieker screams; the Pest just runs at 202 u/s with
	/// a tenth of a boss's health. Everything it is lives in `pest.zvar`, which is the shape a
	/// special SHOULD be when its idea is simple enough — adding an empty component to make the
	/// set look uniform would be a file nobody can delete later.
	/// </summary>
	public const string Pest = "pest";

	/// <summary>
	/// Oberon — CS:Online's giant, ported from `npc_drg_csnz_oberon`.
	/// </summary>
	///
	/// ⚠️ A BOSS AND HIS `.zvar` SAYS SO. He arrives with his own 76-bone skeleton and twenty of
	/// his own clips rather than the walker set, which is why every sequence named in the variant
	/// is an `zbs_*` / `scene_*` name and none of them fall back to `WalkerGroundSpeeds`.
	///
	/// ⚠️ TIER ONE ONLY SO FAR: he walks, runs, attacks and dies on `Standard` behaviour. The knife
	/// phase (`scene_knife` into the `knife_*` clip set), the bomb and the ground-hole attacks are
	/// in the model and not yet in the game — they need a component, the way Brutus needed one for
	/// his helmet.
	public const string Oberon = "oberon";

	/// <summary>
	/// Shrek — nZombies Rezzurrection's joke boss (`nz_zombie_boss_shrek`), for whimsical maps such as Defocus (2026-10-01).
	///
	/// ⚠️ BRUTUS'S BODY: `shrek.vmdl` is Brutus's skeleton, clips and hitboxes with the SuperSlam Shrek mesh bound to it,
	/// and `shrek.zvar` copies Brutus's tiers. What the Lua adds on top (the summon) is `ShrekBoss`.
	/// ⚠️ NO HELMET: the helmet is keyed on "brutus" in the model's path, and his is `models/zombies/shrek/`.
	/// </summary>
	public const string Shrek = "shrek";

	/// <summary>
	/// The Helldonkey — Rezzurrection's `nz_zombie_special_donkey`: a hellhound in the Shrek 3 Donkey's body, with the
	/// hound's skeleton, clips, tiers and sounds, as in GMod. Shrek summons two at a time, and a map can also send them as
	/// its special round.
	/// </summary>
	public const string Helldonkey = "helldonkey";

	/// <summary>
	/// The Margwa — BO3's three-headed boss (GMod's `nz_zombie_boss_margwa`, 2026-10-06), "All Elementals": each one rolls
	/// Revelations, Fire or Shadow, as upstream's `GenesisElementals(math.random(3))` does. See `MargwaBoss` for the fight.
	/// ⚠️ THE FOUR BELOW ARE THE SAME BODY IN ONE SKIN EACH (`margwa.vmdl`'s `skinN` groups), as upstream's four variant files
	/// are one line of `SetSkin` each. Which maps send which is the user's call, once every chosen enemy is ported.
	/// </summary>
	public const string Margwa = "margwa";

	/// <summary>The Shadows of Evil Margwa: skin 0, plain — mouths and swipes, no element.</summary>
	public const string MargwaZod = "margwa_zod";

	/// <summary>The Revelations Margwa: skin 1, plain.</summary>
	public const string MargwaGenesis = "margwa_genesis";

	/// <summary>The Fire Margwa: skin 2 — throws a fire wave that rolls at you.</summary>
	public const string MargwaFire = "margwa_fire";

	/// <summary>The Shadow Margwa: skin 3 — opens a portal and sends four homing skulls.</summary>
	public const string MargwaShadow = "margwa_shadow";

	/// <summary>
	/// Avogadro — Alpha Omega's electric man (GMod's `nz_zombie_boss_avogadro`, 2026-10-06). See `AvogadroBoss` for the fight:
	/// bullets barely hurt him, a knife stuns him; he dashes, throws bolts of lightning and blasts anyone close.
	/// </summary>
	public const string Avogadro = "avogadro";

	/// <summary>
	/// The Astronaut — Moon's spaceman (GMod's `nz_zombie_boss_astro`, 2026-10-06). See `AstronautBoss`: he grabs and headbutts
	/// (never a kill), sprints after three misses, and dies in a blast that takes the zombies round him.
	/// </summary>
	public const string Astronaut = "astronaut";

	/// <summary>
	/// The Director — George Romero, Call of the Dead's boss (GMod's `nz_zombie_boss_director`, 2026-10-06). See `DirectorBoss`:
	/// barely hurt by anything, he rages when shot (a sprint for 45 s), screams buffs onto the zombies round him, and his killer
	/// gets a free perk.
	/// </summary>
	public const string Director = "director";

	/// <summary>
	/// The Panzer Soldat — Origins' mech (GMod's `nz_zombie_boss_panzer`, 2026-10-06). See `PanzerBoss`: armour that must be
	/// shot off (faceplate, power core), a flamethrower, and a claw that drags you in or a taser.
	/// </summary>
	public const string Panzer = "panzer";

	/// <summary>
	/// Brenner — CoD WWII's flamethrower brute (GMod's `nz_zombie_boss_fireman`, 2026-10-07). See `BrennerBoss`: he walks near and runs far, flames you at 145-335, enrages at half health, staggers now and then, and shrugs off all but bullets and blades.
	/// </summary>
	public const string Brenner = "brenner";

	/// <summary>
	/// The Panzerhund — Wolfenstein's mechanical dog (GMod's `nz_zombie_boss_panzerhund`, 2026-10-07), one of two bodies rolled per dog (a fast one and a faster one). See `PanzerhundBoss`: a flame lunge every 4-6 s within 400, a bite, and a blast when it dies.
	/// </summary>
	public const string Panzerhund = "panzerhund";

	/// <summary>
	/// The Sawrunner — Cry of Fear's chainsaw man (GMod's `nz_zombie_boss_sawrunner`, 2026-10-07). See `SawrunnerBoss`: he walks until he sees you, then sprints with a heavier saw for 10 s; x0.5 from everything; his death gives every player a perk.
	/// </summary>
	public const string Sawrunner = "sawrunner";

	/// <summary>
	/// Zaballa the Deceiver — MWZ's masked demon (GMod's `nz_zombie_boss_trickster`, 2026-10-07). See `ZaballaBoss`: he vanishes into a portal, drops three mines at your feet and steps out behind you; three masks break at 75%, 25% and death; a pulse round him at close range; x0.75 from everything.
	/// </summary>
	public const string Zaballa = "zaballa";

	/// <summary>
	/// The Mangler — BO6's arm-cannon brute, the HVT (GMod's `nz_zombie_boss_raz_t10`, 2026-10-07). See `ManglerBoss`: a homing cannon shot every 3-9 s at 150-750 that dazes and throws you; shoot his cannon arm while he aims to spoil it (he enrages and sprints) and, in time, tear it off; a helmet and a chest plate to shoot off (x0.015 while they hold, x0.15 on the body); a blast when he dies.
	/// </summary>
	public const string Mangler = "mangler";

	/// <summary>
	/// The Tesla Zombie — a zombie in an HEV suit (GMod's `nz_zombie_boss_hev`, 2026-10-07). See `TeslaBoss`: he sprints, swings, and screams — every player in his sight within 175 dazed, every walker round him stunned and sprinting for good.
	/// </summary>
	public const string Tesla = "tesla";

	/// <summary>
	/// The Panzermorder — CoD WWII's brute with a flower of meat for a chest (GMod's `nz_zombie_boss_meatflower`, the boss round's "Panzermorder", 2026-10-07). See `PanzermorderBoss`: four health bars — each one emptied knocks it down, and a hit while it lies there spends one of its three lives (none, and it gets up whole); a roar that dazes, a ground slam, a sprint on its last bar; a perk for everyone when it bursts. ⚠️ The id is the asset's, `meatflower`.
	/// </summary>
	public const string Meatflower = "meatflower";

	/// <summary>
	/// The Meúchler — CoD WWII's assassin (GMod's `nz_zombie_boss_assassin`, 2026-10-07), one of two bodies rolled per spawn (the plain and the elite, upstream's 50/50). See `MeuchlerBoss`: he crawls into the ground and out of the spawn nearest you when you are far or out of his sight, sprints once angry (faster at half health), a knife staggers him, and bullets barely hurt him.
	/// </summary>
	public const string Meuchler = "meuchler";

	/// <summary>
	/// The Megaton — BOCW's radioactive brute (GMod's `nz_zombie_boss_steiner`, 2026-10-07). See `MegatonBoss`: three acid shots at 250-750, a gas bomb at 350-750, a sprint for good at half health, x0.15 from everything — and when he dies he splits into the Megaton Blaster and the Megaton Bomber.
	/// </summary>
	public const string Megaton = "megaton";

	/// <summary>The Megaton Blaster — his right half (`nz_zombie_boss_steiner_right`): the acid shots alone, x0.2. The split makes one; a boss round may send one alone, and it tears itself free all the same.</summary>
	public const string MegatonBlaster = "megaton_blaster";

	/// <summary>The Megaton Bomber — his left half (`nz_zombie_boss_steiner_left`): the gas bomb alone, x0.2.</summary>
	public const string MegatonBomber = "megaton_bomber";

	/// <summary>
	/// The Thrasher — BO3 Zetsubou No Shima's spore-sacked brute (GMod's `nz_zombie_boss_thrasher`, 2026-10-07). See `ThrasherBoss`: three spore sacs are his weak points (x0.85, x0.25 anywhere else) and pop at 65% and 35%; he enrages, burrows up behind you from 750 away, and eats zombies to grow his sacs back (eating a downed player is in, but off by default — `nz_thrasher_set eat 1`).
	/// </summary>
	public const string Thrasher = "thrasher";

	/// <summary>
	/// The Mimic — Black Ops 6's shape-shifter (GMod's `nz_zombie_boss_mimic`, 2026-10-07). See `MimicBoss`: its tentacle catches you within 215, reels you to its mouth and bites (knife it twice to get out; it takes nothing while it holds you); it lobs electric spit at 200-750 that dazes; it enrages below half health; x0.4 on the body, x0.75 on the head.
	/// </summary>
	public const string Mimic = "mimic";

	/// <summary>
	/// The Krasny Soldat — Black Ops Cold War's pyro mech (GMod's `nz_zombie_boss_krasny`, 2026-10-07). See `KrasnyBoss`: shoot his faceplate (it enrages him) and power core off; a flamethrower at 115-300, a fire bomb lobbed at 400-1250 that leaves the floor burning, and a jetpack lunge at anyone 500 or more away.
	/// </summary>
	public const string Krasny = "krasny";

	/// <summary>
	/// The Sizzler — CoD WWII's fire runner (GMod's `nz_zombie_special_siz`, 2026-10-07). A special, not a boss: see `SizzlerZombie` — it walks until it sees you within 950 (or 2-7 s pass), then ignites and sprints for good; it explodes when it dies, lit or not.
	/// </summary>
	public const string Sizzler = "sizzler";

	/// <summary>
	/// The Wüstling — CoD WWII's brute (GMod's `nz_zombie_special_follower`, 2026-10-07). A special, not a boss: see `WustlingZombie` — hurt him and he may charge (a roar, a sprint, a slam that hits hard); x0.25 from everything but the weak spot on his upper back.
	/// </summary>
	public const string Wustling = "wustling";

	/// <summary>
	/// The Bomber Zombie — CoD WWII's bomb carrier (GMod's `nz_zombie_special_bomba`, 2026-10-07). A special, not a boss: see `BomberZombie` — shoot the bomb on its back and it and the zombies round it go up; shoot the harness and the bomb falls off, live; within 350 it lights the fuse and sprints, within 150 it blows itself up. 1 in 300 is a treasure bomber.
	/// </summary>
	public const string Bomber = "bomber";

	/// <summary>Every special, in menu order. Also the picker's options.</summary>
	public static string[] Names => new[] { Hellhound, Brutus, Napalm, Shrieker, Pest, Oberon, Shrek, Helldonkey,
		Margwa, MargwaZod, MargwaGenesis, MargwaFire, MargwaShadow, Avogadro, Astronaut, Director, Panzer, Brenner, Panzerhund, Sawrunner,
		Zaballa, Mangler, Tesla, Meatflower, Meuchler, Megaton, MegatonBlaster, MegatonBomber, Thrasher, Mimic, Krasny, Sizzler, Wustling, Bomber };

	/// <summary>Asset path for an id, or null if it is not one of ours.</summary>
	public static string PathFor( string name ) => name switch
	{
		Hellhound => "zombies/hellhound.zvar",
		Brutus => "zombies/brutus.zvar",
		Napalm => "zombies/napalm.zvar",
		Shrieker => "zombies/shrieker.zvar",
		Pest => "zombies/pest.zvar",
		Oberon => "zombies/oberon.zvar",
		Shrek => "zombies/shrek.zvar",
		Helldonkey => "zombies/helldonkey.zvar",
		Margwa => "zombies/margwa.zvar",
		MargwaZod => "zombies/margwa_zod.zvar",
		MargwaGenesis => "zombies/margwa_genesis.zvar",
		MargwaFire => "zombies/margwa_fire.zvar",
		MargwaShadow => "zombies/margwa_shadow.zvar",
		Avogadro => "zombies/avogadro.zvar",
		Astronaut => "zombies/astronaut.zvar",
		Director => "zombies/director.zvar",
		Panzer => "zombies/panzer.zvar",
		Brenner => "zombies/brenner.zvar",
		Panzerhund => "zombies/panzerhund.zvar",
		Sawrunner => "zombies/sawrunner.zvar",
		Zaballa => "zombies/zaballa.zvar",
		Mangler => "zombies/mangler.zvar",
		Tesla => "zombies/tesla.zvar",
		Meatflower => "zombies/meatflower.zvar",
		Meuchler => "zombies/meuchler.zvar",
		Megaton => "zombies/megaton.zvar",
		MegatonBlaster => "zombies/megaton_blaster.zvar",
		MegatonBomber => "zombies/megaton_bomber.zvar",
		Thrasher => "zombies/thrasher.zvar",
		Mimic => "zombies/mimic.zvar",
		Krasny => "zombies/krasny.zvar",
		Sizzler => "zombies/sizzler.zvar",
		Wustling => "zombies/wustling.zvar",
		Bomber => "zombies/bomber.zvar",
		_ => null,
	};

	/// <summary>Human-facing one-liner, for the settings panel hint.</summary>
	public static string DescriptionFor( string name ) => name switch
	{
		Hellhound => "fast, ignores barricades",
		Brutus => "boss — huge health, helmet must be broken before headshots land",
		Napalm => "suicide bomber — blows up at close range and leaves the ground burning",
		Shrieker => "screamer — blinds you from range, and its death kills every zombie near it",
		Pest => "sprinter — fast and fragile, and you hear the flies before you see it",
		Oberon => "boss — twice your height, faster than he looks, and he does not use doors",
		Shrek => "boss — Brutus's strength in a swamp ogre, and now and then he sings two Helldonkeys up beside him",
		Helldonkey => "a hellhound shaped like a donkey — fast, ignores barricades",
		Margwa => "boss — three heads; only an open mouth can be hurt. Rolls Revelations, Fire or Shadow",
		MargwaZod => "boss — the Shadows of Evil Margwa: three heads, only an open mouth can be hurt",
		MargwaGenesis => "boss — the Revelations Margwa: three heads, only an open mouth can be hurt",
		MargwaFire => "boss — the Fire Margwa: three heads, and a fire wave that rolls at you",
		MargwaShadow => "boss — the Shadow Margwa: three heads, and four homing skulls out of a portal",
		Avogadro => "boss — the electric man: bullets barely hurt him, a knife stuns him; he dashes, throws lightning and blasts you back",
		Astronaut => "boss — grabs and headbutts you (never a kill), sprints after three misses, and blows up the zombies round him when he dies",
		Director => "boss — George Romero: barely hurt by anything, rages and sprints when shot, buffs the horde; his killer gets a free perk",
		Panzer => "boss — the Panzer Soldat: shoot his faceplate and power core off; a flamethrower, and a claw that drags you in or a taser",
		Brenner => "boss — the flamethrower brute: walks near, runs far, flames you at mid range, enrages at half health; x0.25 from all but bullets and blades",
		Panzerhund => "boss — the mechanical dog: sprints at you, lunges breathing fire every few seconds, and blows up when it dies",
		Sawrunner => "boss — the chainsaw man: walks until he sees you, then sprints with a heavier saw; half damage from everything; a perk for everyone when he dies",
		Zaballa => "boss — the masked demon: vanishes, drops three mines at your feet and steps out behind you; a pulse when you get close; three masks to break",
		Mangler => "boss — the arm-cannon brute: a homing shot that dazes and throws you; shoot his cannon arm as he aims to spoil it, then tear it off; a helmet and a chest plate to break; x0.15 on the body",
		Tesla => "boss — the Tesla Zombie: a sprinter in an HEV suit whose scream dazes you and stuns the zombies round him, who then sprint",
		Meatflower => "boss — the Panzermorder: four health bars; emptying one knocks it down, and only a hit while it lies there spends one of its three lives (else it gets up whole); a dazing roar, a ground slam, a sprint on its last bar",
		Meuchler => "boss — the assassin: crawls into the ground and out of the spawn nearest you, sprints once angry; bullets barely hurt him, a knife staggers him",
		Megaton => "boss — the radioactive brute: three acid shots from range, a gas bomb, a sprint at half health; kill him and he splits in two",
		MegatonBlaster => "boss — the Megaton's right half: acid shots from range, and a sprint when you are close",
		MegatonBomber => "boss — the Megaton's left half: gas bombs from range, and a sprint when you are close",
		Thrasher => "boss — shoot his glowing spore sacs (they pop at 65% and 35%); he enrages, burrows up behind you, and eats zombies to grow his sacs back",
		Mimic => "boss — the shape-shifter: its tentacle drags you to its mouth for a bite (knife it twice to break free; it can't be hurt while it holds you), it spits electricity from range, and it enrages at half health",
		Krasny => "boss — the pyro mech: shoot his faceplate and power core off; a flamethrower, a fire bomb that leaves the floor burning, and a jetpack lunge from range",
		Sizzler => "fire runner — walks until it spots you, then catches fire and sprints at you; it blows up when it dies, so kill it at range",
		Wustling => "brute — shoot him and he may charge: a roar, a sprint, a slam that hits hard; x0.25 from everything but the weak spot on his upper back (x3)",
		Bomber => "bomber — a bomb on its back: shoot it to blow it and the zombies round it up, or shoot the harness to knock it off; within 350 it lights the fuse and sprints, within 150 it blows itself up",
		_ => "unknown special",
	};

	/// <summary>Load a special's variant, or null.
	///
	/// ⚠️ Not cached. ResourceLibrary already caches the asset, and a cache here
	/// would be one more static holding a stale value across a hotload for no
	/// gain — see the note at the top of this file.</summary>
	public static ZombieVariant VariantFor( string name )
	{
		var path = PathFor( name );
		if ( string.IsNullOrWhiteSpace( path ) ) return null;

		return ResourceLibrary.Get<ZombieVariant>( path );
	}

	/// <summary>True when this id is one we can actually spawn.</summary>
	public static bool IsKnown( string name ) => PathFor( name ) is not null;

	/// <summary>
	/// Is this variant one of the specials, rather than the walker?
	///
	/// ⚠️ MATCHED ON ResourceName, not on ZombieBehaviour. Behaviour is an
	/// ATTACK-STYLE enum — Standard vs LeapAttack — and six of the leapers are
	/// ordinary walkers, so testing it would classify them as specials and pay
	/// special-rate drops for a normal kill.
	///
	/// ⚠️ Drop rates are the only caller today. If anything gameplay-critical ever
	/// depends on this, give ZombieVariant an explicit flag instead: deriving
	/// "specialness" from an asset filename is fine for a loot roll and too fragile
	/// for a rule.
	/// </summary>
	public static bool IsSpecial( ZombieVariant variant )
		=> variant is not null
			&& Names.Contains( variant.ResourceName, System.StringComparer.OrdinalIgnoreCase );

	/// <summary>
	/// Is this variant a BOSS, which is a stricter question than <see cref="IsSpecial"/>.
	///
	/// ⛔ THE NOTE ON `IsSpecial` ASKED FOR THIS AND BRUTUS IS WHY. It says drop rates are its
	/// only caller and that "if anything gameplay-critical ever depends on this, give ZombieVariant
	/// an explicit flag instead - deriving specialness from an asset filename is fine for a loot
	/// roll and too fragile for a rule". Death Perception M1 is a x3 damage rule, so the flag went
	/// on the variant and this reads it.
	///
	/// ⚠️ ADDING BRUTUS TO `Names` ALSO MADE HIM A SPECIAL FOR DROPS, which is intended — a boss
	/// should roll the better plate and salvage chances. But the two questions stay separate: the
	/// hellhound is a special and not a boss.
	/// </summary>
	public static bool IsBoss( ZombieVariant variant ) => variant?.IsBoss ?? false;

	/// <summary>
	/// The ids that are bosses, and the ids that are not.
	///
	/// ⛔ THE TWO PICKERS MUST NOT SHARE A ROSTER, AND REGISTERING BRUTUS EXPOSED THAT. Adding him
	/// to <see cref="Names"/> made him selectable as a SPECIAL - so a dog round could have rolled a
	/// boss, and the special-spawn dropdown offered one. Neither is wanted: a boss round and a
	/// special round are different round types with different spawn lists.
	///
	/// ⚠️ LOADING EACH VARIANT TO ASK IS THE POINT, not a cost to avoid. `IsBoss` lives on the
	/// asset because a filename is "too fragile for a rule" - this file's own words - so the answer
	/// has to come from the asset. `ResourceLibrary` caches them, and both lists are read by menus
	/// and round setup rather than per frame.
	/// </summary>
	public static string[] BossNames
		=> Names.Where( n => IsBoss( VariantFor( n ) ) ).ToArray();

	/// <summary>Everything that is NOT a boss — what a special round may roll.</summary>
	public static string[] NonBossNames
		=> Names.Where( n => !IsBoss( VariantFor( n ) ) ).ToArray();

	/// <summary>The boss to fall back to when a spawner names something that is not one.</summary>
	public static string BossFallback => BossNames.FirstOrDefault() ?? Brutus;

	/// <summary>Is this id a boss, by name.</summary>
	public static bool IsBossName( string name ) => IsBoss( VariantFor( name ) );

	/// <summary>The id to fall back to when a spawner names something unknown —
	/// a config saved against a special that has since been removed should still
	/// spawn SOMETHING rather than silently contributing nothing to its round.</summary>
	public static string Fallback => Names.FirstOrDefault() ?? Hellhound;
}