Skip to content

HD2Runtime v0.31.2 — No More Stutter, Four Statuses per Row, Ammunition Limits, the Game's Own Names and Support Weapon Payloads

Choose a tag to compare

@SkyeShade SkyeShade released this 11 Oct 23:49
· 35 commits to master since this release

HD2Runtime 0.31.2 — no more stutter, four statuses per row, ammunition limits, the game's own names and support weapon payloads

0.31.2 is a hotfix release built from player reports: custom stratagems no longer stutter every 5 seconds, beam
conversion values can be changed again and reverted, a damage row takes up to four statuses (the GL-15 Evictor's gas
and gas confusion), ammunition maxima follow the game's network limits (the TD-220 Bastion coax), the RL-77's bomblet
explosion is found under both rockets, the game's own names and custom stratagem texts work in Chinese and every other
game language, Acid Storm can be attached to weapons (asked for the TX-41 Sterilizer), the Sterilizer's hellpod is
editable, the LAS-99 Quasar's lockout after an overheat is editable, and support weapons take impact and expiry
payloads.

Each item says whether it was seen working in game ("live") or is covered by the offline tests and the snapshot
validators only. Two fixes are live, reported working in game on a development build (without a log): the custom
stratagem stutter and the beam conversion re-edit. Everything else is offline only, and its new fields need
allow_unverified_effect. Nothing in this release was tested in multiplayer.

Custom stratagems: no stutter every 5 seconds (live)

  • A player reported "a pulse, a quick stutter" with custom stratagem mods. The F10 Performance tab showed the custom
    stratagems at 0.65 ms on average with a peak of 53.8 ms: one long update about every 5 seconds.
  • Cause: aboard the ship, the Runtime checks the custom stratagems' carriers again every 5 seconds: the ship allocation
    and the custom panel's availability view (M.REVALIDATE_EVERY). Each check ran one carrier discovery per registered
    custom stratagem. Each discovery read every catalogued stratagem again: its row, its ownership, its look and its code,
    about 1,700 reads. Live, each read is one ReadProcessMemory. With many custom stratagems, one update made tens of
    thousands of reads.
  • Now (the same reads, made fewer times):
    • an allocation runs one discovery for all the definitions that ask the same one (the same token, exclusions and Eagle
      mode), not one per definition (runtime/carrier_allocator.lua);
    • what a discovery reads of each catalogued stratagem is read once per update and shared by every discovery in that
      update. It is read again when any Runtime write happens in that update (runtime/stratagem_slot_conversion.lua);
    • a stratagem type scan that finds nothing is read once per update, not once per id (runtime/stratagem_loadout.lua).
  • The results are unchanged: the same carriers, verdicts, availability and ordering, and the same multiplayer rules. No
    guard changed.
  • Offline, aboard the ship over a simulated minute (tests/test_custom_stratagem_performance.py), the worst update
    went:
    • with the three example custom stratagems: from 93,275 reads (about 120 ms) to 590 reads (about 3 ms);
    • with fifteen: from 465,467 reads (about 570 ms) to 590 reads.
      It no longer grows with the number of custom stratagems. The test fails if it does again.
  • Live: tested in game on a development build of this fix and reported working (no log or numbers sent). A mission
    and several players were not reported separately (schemas/live_evidence.json
    custom_stratagem_revalidation_stutter_0_31_2).

Beam conversion: a field changes again, and back (live)

  • A player report (HD2R Editor, APW-1): beam.fire_rate 300 -> 60 applied, then every later change was refused until
    a restart, the reset too: CONFLICT: the APW-1 Anti-Materiel Rifle's beam.fire_rate is 60, neither the expected 300 nor the requested 300 (another operation set it).
  • Cause: an option-bound ensure (the editor holds each field with one, its value bound to a script value) may move the
    value it applied itself on: every typed domain accepts those bytes (change.owned, core/ownership.lua). The beam
    conversion domain checked only expect and value, so the ensure's own 60 read as another operation's. Its restore
    (the ensure's enabled option off) failed the same way.
  • The beam conversion settings and per-weapon rows now accept the ensure's own previous value too
    (domains/beam_conversion_writes.lua). Another mod's operation, and a writer outside HD2Runtime, are still
    refused with CONFLICT.
  • A request naming beam.fire_rate alone no longer refits a pulse that was set explicitly: only the Trident's 0.15 s
    or the pulse fitted to the current rate is refitted. Before, the rate's and the pulse's ensures overwrote each other
    at every steady-state check.
  • Live: tested in game on a development build with the HD2R Editor on the APW-1: repeated edits and the revert were
    reported working (no log sent). Only this re-edit and revert behaviour is live. The gameplay effect of each beam value,
    other weapons, another mod's refusal and multiplayer are not: every beam conversion write still needs
    allow_component_swap and allow_unverified_effect (schemas/live_evidence.json
    beam_conversion_same_mod_reedit_0_31_2).
  • See docs/beam-conversion.md ("Rules", "The pulse length limits the rate").

Status slots 1 to 4 on every damage row (offline only)

  • Every DamageInfo row now offers all of its empty status slots, up to slot 4, each with its strength
    (damage.status_<k>_type / _strength, explosion.damage.status_<k>_*). Before, only the first empty slot was
    offered, so a row with no status could gain only one: the GL-15 Evictor's blast could not take both gas and
    gas_confusion. This applies to player, support and mounted weapons and to throwable explosions.
  • The slots stay packed from slot 1, now judged on the row as the whole write leaves it (core/status_slots.lua):
    slot k takes a status only when slots 1 to k-1 hold one, live (vanilla, or set by an earlier operation) or set by
    the same transaction or another operation of the same plan phase, in any order. Slot 2 alone on an empty row is
    refused (STATUS_SLOT_GAP), and so is clearing a slot while a later one holds a status. Each slot's own bytes are
    still checked against their exact reviewed value.
  • The new slots after a row's first empty one always need allow_unverified_effect, also on the live-proven
    projectile direct-hit rows: a second attached status on one row has not been live-tested.
  • See docs/status-effects.md ("API", "Guards").

Ammunition maxima: 2047 rounds, 31 spare magazines (offline only)

  • A player reported a TD-220 Bastion coax capacity of 2500 that showed about 2100 rounds for its owner and 500 for a
    friend. The live round count is an 11-bit network field (0 to 2047) and the spare-magazine count a 5-bit one (0 to
    31). The game clamps larger values at the next shot, so teammates see a different count.
  • weapon.capacity / magazine.capacity / attachment.magazine_capacity now take at most 2047. The magazine counts
    (starting_magazines, magazines_from_supply, spare_magazines, and the attachment ones) take at most 31. This
    covers player, support and mounted weapons and magazine attachments. Smaller existing maxima are kept. The refusal
    names the network field.
  • Older mods keep working. A mod that declares an SDK older than 0.31.2 had no maximum: its larger value is applied
    as the maximum and logged as a legacy operation. It is not refused. A mod that declares 0.31.2, or whose SDK cannot
    be read, is refused. See docs/legacy-sdk-compatibility.md ("Maxima added in 0.31.2").
  • The TD-220 Bastion coax and the Exosuit arms own no reload component. Their spare-magazine fields are flagged
    reloadComponent.present = false as expected to have no effect. They are not blocked, and this is not proven from
    game code.
  • See docs/vehicle-weapons.md ("Ammunition limits").

RL-77 bomblet explosion (offline only)

  • A player looked for the RL-77 Airburst's bomblet explosion (500 damage per bomblet) under its normal rocket. The
    bomblets explode as one row, both those of its normal rocket and those of its cluster round.
    The catalogue named that row only after the cluster round:
    support_weapon/rl77_airburst_rocket_launcher/function_expiry/shrapnel_expiry.
  • It now also has the owner-neutral alias support_weapon/rl77_airburst_rocket_launcher/bomblet. The old id keeps
    working. See docs/explosions.md ("Chains with several sources").
  • The support-weapon catalogue links the wiki branch "RL-77 P1 IE" to that explosion, and "EAT-700 P1 IE" to the
    EAT-700's napalm shrapnel explosion. The link is structural: the burst's shrapnel projectile and that projectile's
    impact explosion. Both are authored through hd2.explosion(name). See docs/support-weapon-api.md.
  • scripts/research_explosion_identities.py now records every named source of a chain explosion as an owner: the
    bomblet row lists both RL-77 bursts in sdk/ExplosionCatalogue.json (owners). Ids, sharing and packages do not
    change.

The game's own names in the game's language (offline only)

  • A translator reported that the object names a tool shows (the Runtime's keys, such as AR-23 Liberator) cannot be
    localized. The game has every one of these names in all 15 languages. hd2.text now reads them from the game, in
    the language it is set to:
    • hd2.text.name(kind, key) and target:display_name() return 'AR-23 解放者' and its parts. Kinds: weapons,
      support weapons, throwables, backpacks, vehicles, boosters and stratagems.
    • hd2.text.language() / hd2.text.available() say whether the texts can be read now;
    • hd2.text.game(id) reads one text by id.
  • Read-only: nothing is written and nothing needs an acknowledgement. A lookup that cannot answer returns nil, a code
    and a reason, and the caller shows the key: before the game registers its text (UNAVAILABLE), for another language
    than the game's (LANGUAGE_NOT_LOADED: only the current one is in memory) or on another build.
  • Names are composed as the armory shows them, <designation> <name>. The keys do not change. Where the game's name
    differs from the key, the game's is shown: CQC-73 Entrenchment Tool shows as CQC-72, the RL-77 backpack as Airburst
    Rocket Backpack, the Super Earth FRV variant as M-102 GUNNER FRV, the GATER rig by its full name. The three
    underbarrels have no game name (NO_NAME).
  • The text ids are internal: they are in the runtime catalogues only, not in the SDK's capability files.
  • A mod that uses hd2.text or display_name needs min_version 0.31.2.
  • See docs/game-text.md.

Custom stratagem texts in every language (offline only)

  • name, name_cased, description and delivery.presentation.name of hd2.custom_stratagem.register (and of a
    custom_stratagems.json project) now also take a table of the game's languages, {default = ..., cn = ..., tc = ...},
    as hd2.resources.text does. The keys are default and the 15 game language codes.
  • traits take any UTF-8 text without control characters. Before, only printable ASCII was accepted, so Chinese was
    refused. Upper case is Unicode-aware (runtime/i18n.lua); CJK characters are kept as they are.
  • The limits are bytes, and the documentation and sdk/CustomStratagemSchema.json now say so (lengthUnit: bytes):
    • 64 for a name (about 21 CJK characters);
    • 400 for a description;
    • 32 for a trait (about 10 CJK characters).
  • The project compiler (hd2.py custom-stratagem) counts bytes too. Before, it counted characters, so a 64-character
    Chinese name passed the compiler and was refused by the Runtime.
  • See docs/custom-stratagem-api.md ("Texts in every language").

Acid Storm on weapons and grenades (offline only)

  • acid_storm is now an attachable status (a player request: the TX-41 Sterilizer applying Acid Storm to enemies).
    It is on the experimental other_system tier, so every write needs allow_unverified_effect. 35 of the 71
    statuses are attachable now (other_system: 16).
  • Correction to the 0.31.1 notes. They said Acid Storm's definition is "a one-second marker with no damage" and
    that "what the storm does comes from the weather system". The effect is in the status itself: while it is applied,
    the game's own handler for this status lowers the armour of every armoured zone of the target by one level (never
    below 0), and restores it when the status ends, about 1 s after the last hit that applied it. The weather only
    applies the status, to enemies as well as Helldivers. It still deals no damage and shows no particles or sound.
    About a dozen enemy types are not susceptible to it and are unaffected.
  • It is attachable as a reviewed, per-status family exception: schemas/status_attachment_policy.json gives it a
    familyException reason, and the generator accepts that key only on the other_system tier, with a non-empty
    reason. Every other weather, terrain, stim and system status stays refused (not attachable).
  • Use strength 1: the status then triggers on the first hit that penetrates the zone. See docs/status-effects.md
    ("Values").
  • No mod is affected: it is a newly allowed value, so no legacy entry is needed.

LAS-99 Quasar: the cooldown after an overheat (offline only)

  • The Quasar's 15 s lockout after an overheat is its own member, WeaponHeat +140: the heat lost per second while the
    weapon is overheated (and while it charges). The game's heat step was traced offline
    (research/overheat-cooling-F5FEE03DCFDB.json): it cools at heat.cool_per_second only while the weapon is neither
    overheated nor charging, and otherwise at +140, when WeaponHeat +144 is 0. The overheat ends at the release
    threshold (+100, 0 on the Quasar): (100 - 0) / 6.66 = 15 s, about 17.5 s hot and 12.5 s cold.
  • New field heat.cool_per_second_overheated on the LAS-99 Quasar Cannon
    (hd2.fields.heat.cool_per_second_overheated; 0.1 to 1000 per second; needs allow_unverified_effect). The
    Quasar is the only weapon whose record has +144 = 0, and it owns that record alone. A write applies on the next
    frame, also to a Quasar in hand.
  • Correction. heat.overheat_cooldown said to edit heat.capacity or heat.cool_per_second. That is wrong:
    heat.cool_per_second never governs the lockout (a user project set it 6.66 -> 10 for this). Its reason now names
    the weapon's own case: the Quasar's lockout formula with its values. On every other heat weapon, +144 is set: an
    overheated weapon does not cool and needs a heatsink change (a reload). The LAS-17 Double-Edge Sickle never
    overheats. The LAS-98's "100 / 5 = 20 s" in docs/support-weapon-api.md was wrong for the same reason.
  • WeaponHeat +144 ("no passive cooling while overheated") and +100 (the release threshold) are explained, not exposed.
  • Live test pending (schemas/live_evidence.json quasar_overheated_cooling_0_31_2), solo:
    heat.cool_per_second_overheated 20 should give about 5 s. As the A/B, heat.cool_per_second 20 alone should
    leave about 15 s.
  • See docs/support-weapon-api.md ("Warm-up and cooldown after overheat").

TX-41 Sterilizer hellpod (offline only)

  • The TX-41's pod had no "Slot n" rows and a locked "Items spawned". Its rack is ordinary (one item, one consumer,
    unshared, no random draw), but its pickup has no resource path, and the pod catalogue offered only pickups with
    one. One missing path made the whole rack read-only, with the misleading reason "no resolvable name".
  • A typed pickup without a path is now offered when it has a catalogue name that no other offered pickup uses, an
    entity row and a vanilla rack that delivers it. Its identity is proven as for every pickup. The generator asserts
    that the TX-41 is the only one. Its rack is writable like any other: hd2.fields.payload.entity on its slots
    (allow_unverified_reference for anything but its own TX-41) and hd2.fields.payload.spawn_count
    (allow_unverified_effect).
  • Its own loadout package (chemgun) is recorded (research/package-residency-F5FEE03DCFDB.json, only this entry
    added), so Runtime loads it before a TX-41 is placed in another pod.
  • The racks that stay read-only say why accurately: the Dark Fluid Backpack's item has neither a path nor a unique
    name. The MS-11 Solo Silo's pod holds the silo and its remote, not pickups; the custom silo API covers it.
  • Knock-on: the TX-41 can now be delivered by a custom stratagem and register a family='weapon' variant (33 hosts
    register instead of 32). Neither is live-tested.
  • See docs/pod-payloads.md ("Read-only racks").

Impact and expiry payloads on support weapons (offline only)

  • A support weapon could not change what its rounds explode into: the APW-1 had no payload row at all, and the AC-8
    showed only its explosion's values. Support weapons now have the primaries' payload field on every projectile row an
    attack fires, per fire mode: hd2.fields.terminal.explosion on
    hd2.support_weapon(name):attack(role):projectile():terminal_action('impact' | 'expiry')
    (terminal.<role>.<phase>.explosion). 51 fields on 23 weapons:
    • primary (the normal round) on every support weapon that fires a projectile row;
    • programmable, the ProgrammableAmmo feed: the AC-8's flak, the GR-8's and the RL-77's cluster round;
    • the Epoch's charge levels (primary, full_charge) and the SG-88's rounds feed (feed_primary);
    • the Spear, Commando and W.A.S.P. missiles: impact only. A missile ends by its own lifetime, and whether its row's
      expiry explosion ever fires is not traced, so no expiry is offered (the target names the reason).
  • Values: terminal:explosion() (the row's own), terminal:no_explosion(), hd2.explosion(name) (any explosion with a
    known package) or another weapon's terminal explosion handle. terminal:current() is the expect. Every write needs
    allow_shared and allow_unverified_effect; a catalogued explosion also allow_unverified_reference.
  • The write uses the row the role's projectile fields already write, re-resolved through the weapon every time (a
    swapped feed refuses). A catalogued explosion's package is loaded first, its row re-proven, and an explosion whose
    shrapnel would release the written row again is refused (RECURSIVE_COMPOSITION): the W.A.S.P. cluster explosion on
    the W.A.S.P.'s own row.
  • Caveats travel with each field (terminal:describe(), payload in the SDK file): who else fires or releases the row,
    the MG-43 / MGX-42 tracer rows a magazine pattern also fires, an expiry on a row with no lifetime, the Epoch's charge
    level.
  • These are shared row writes, as on the primaries. The per-shot payload for one weapon alone
    (hd2.projectiles.payload) stays 0.32.0 work.
  • Proven offline on the retained snapshot: all 51 fields resolve to their reviewed rows and values; the APW-1 impact set
    to a grenade's explosion, the AC-8 flak to an orbital shell's, the RL-77 cluster expiry removed, and the recursion
    refusal. Live test pending (schemas/live_evidence.json support_payload_0_31_2).
  • See docs/explosions.md ("Payloads on support weapons") and docs/support-weapon-api.md.

Payload options: every explosion a payload takes (offline only)

  • An editor listed only the 14 explosions of the primaries as payloads. A payload has taken any catalogued explosion
    with a known package since 0.30.0: 138 of them, from weapons, grenades, orbitals, eagles, Hellbombs, sentries, mines,
    vehicles and more, and now 139 with the EXCESSIVELY LARGE NUKE (below). They are now published for a picker, grouped by source family:
    • hd2.explosions.payload_options(filter): every option with its label, group, damage, radius, statuses, what it
      leaves (a lingering field, fire, shrapnel, an arc), warnings, package and acknowledgements, and value
      (hd2.explosion(name));
    • terminal:payload_options(filter) (primary, secondary and support terminal actions): what one field takes: its own
      explosion, none, every option with the value to write and the acknowledgements, and the ones it refuses with the
      reason;
    • hd2.explosions.payload_groups(), hd2.explosions.payload_warnings(); terminal:current() on primaries too;
    • sdk/PayloadOptions.json (contract hd2runtime.payload_options.v1, no raw ids): the options, the groups and every
      payload field (185: 134 primary and secondary, 51 support) with its acknowledgements and refusals.
  • Groups: weapons 18, support weapons 31, grenades and throwables 23, orbitals 13, eagles 12, Hellbombs 2, sentries
    and emplacements 7, mines 4, vehicles and exosuits 11, backpacks 1, enemies 7, other 10.
  • Warnings inform and never refuse: very_large (18, the Hellbombs' 10000 damage among them), lingering_field (12:
    the game holds at most 4096 status fields and does not check the count), fire, shrapnel, arc, no_damage,
    objective_packages (1).
  • Choosing an option loads its package before the write (waiting_for_assets); a mission effects option needs none. A
    package that is not resident within 90 s, or cannot be requested, rejects the operation (ASSET_UNAVAILABLE) and the
    row keeps its value.
  • Generated by scripts/generate_payload_options.py (domains/payload_options.lua, sdk/PayloadOptions.json). See
    docs/explosions.md ("Payload options").

EXCESSIVELY LARGE NUKE as a payload (offline only)

  • The MS-N223 Shredder Silo's nuclear blast is now a payload option: EXCESSIVELY LARGE NUKE in
    hd2.explosions.payload_options() (group other, warnings very_large and objective_packages, a note). It is
    the Cyborg Production Unit's explosion, the native catalogued row the silo requests, unmodified: 10000 damage, AP 10,
    a 50 m inner and a 100 m outer radius. Its value is hd2.explosion('entity/cyborg_production_unit/ability').
  • Before, the catalogue refused it as a payload: it knew only the objective package that holds its effect, not the
    one that holds its sound. The catalogue entry now lists both (package.keys; sdk/ExplosionCatalogue.json
    package.objective, packages = 2). A weapon payload, an attack output slot and a custom projectile row load
    both before the write (core/assets.lua explosion_dependencies), about 300 MB, which can take up to 90 s.
  • Why it is offered: both packages are in this build's bundle database, and the silo loads the same two packages and
    requests the same explosion in a mission (seen live, custom_stratagem_silo). The write only references the row,
    re-proven live first; it releases no shrapnel. As a projectile's impact it is not live-tested: it needs
    allow_unverified_reference and allow_unverified_effect, as every catalogued payload.
  • Anything within 100 m of the impact is caught, the shooter too. See docs/explosions.md ("EXCESSIVELY LARGE
    NUKE").

Projectile swaps on the LAS-99 Quasar and the RS-422 Railgun (offline only)

  • Two more support weapons are projectile hosts: hd2.support_weapon('LAS-99 Quasar Cannon'):projectile_source() and
    the RS-422 Railgun's are writable (hd2.fields.attack.projectile). 10 support weapons swap now instead of 8.
  • They were refused only because they are not magazine-fed. That rule keeps every host to the structure of the two
    live controls: for another structure, something other than ProjectileWeapon +0 might select the fired projectile.
    An offline trace of the game's fire path now shows that nothing else selects it for these two
    (research/fired-type-callers-F5FEE03DCFDB.json). The Quasar's heat levels and the Railgun's charge levels hold no
    projectile, so their fire passes none. The resolver then takes ProjectileWeapon +0: for a heat weapon directly, and
    for the Railgun by the magazine path of the live-proven hosts. Each owns its record alone. The reasoning is in
    docs/attack-outputs.md ("Hosts outside the magazine-fed structure").
  • Guard. No live control of these structures exists yet. So every donor on them needs
    allow_unverified_reference as well as allow_unverified_effect, even one of the host's own class
    (HOST_STRUCTURE_UNVERIFIED). Restoring the weapon's own projectile needs nothing. Every other host guard applies.
  • Not relaxed: the M-1000 Maxigun and the GL-28. The trace found that a linked-ammunition weapon fires the type of
    its linked ammunition source (its backpack) whenever one is linked. What that source holds is not traced, so they
    stay read-only, now with that reason.
  • Live test pending (schemas/live_evidence.json support_projectile_traced_hosts_0_31_2), solo: the Quasar fires
    EAT-17 rockets and the Railgun APW-1 rounds after a fresh call-in.

Stats of the projectile an output fires (read-only)

  • For an editor's "Fires: " view after a projectile swap: hd2.attack_output(id):stats() returns the vanilla
    stats of the projectile that output fires, from the game data. Nothing is read live and nothing is written.
    • flight: velocity, mass, drag, gravity, lifetime, pellet_count, diameter, lifetime_variance,
      penetration_slowdown;
    • direct_damage: standard_damage, durable_damage, ap_direct ... ap_extreme, demolition, stagger,
      push_force, statuses;
    • impact / expiry: the hd2.explosion catalogue name, label, radii, shrapnel count, arc and damage.
    • Also output, owner, compatibilityClass and consumers (how many entities fire that row).
  • hd2.attack_output(id):owner_projectile() returns the owner weapon's own editable projectile handle when the output
    is its primary attack. Edits there are shared with the owner (allow_shared).
  • Generated offline: scripts/research_projectile_stats.py -> research/projectile-stats-F5FEE03DCFDB.json, checked
    against the projectile component catalogue and the explosion catalogue research. Then
    scripts/generate_projectile_stats.py -> domains/projectile_stats.lua. Live values: hd2.inspect.
  • See docs/attack-outputs.md ("Stats of an output projectile") and sdk/docs/api.md.

F10 Performance tab: lighter while it is shown (offline only)

  • With the Performance tab open, its own drawing showed as "overlay settings" at about 0.8 ms a frame. Its numbers were
    already computed once per graph bin (0.25 s), but every frame formatted, translated and fitted the texts again and
    recomputed every rectangle.
  • What the tab draws is now built once per bin (and per window size, scale and language) and replayed in the other
    frames: the same calls in the same order (runtime/performance_panel.lua).
  • Offline, with the real overlay builder, the tab's drawing went from about 0.48 to 0.32 ms a frame, and the whole
    panel frame from 0.76 to 0.56 ms. Most of what remains is the overlay's own work for the graph's 360 rectangles.

Compatibility

  • Mods built for 0.31.1 work unchanged. A mod that declares an SDK older than 0.31.2 and sets an ammunition value
    above the new maxima keeps working: the value is clamped and logged as a legacy operation (see "Ammunition maxima").
  • The startup line reads HD2Runtime 0.31.2 initialized (API 1).
  • A mod that uses hd2.text or target:display_name() needs min_version 0.31.2. The other new fields and APIs
    (status slots after a row's first empty one, heat.cool_per_second_overheated, the support weapon payloads, the
    payload options, hd2.attack_output(id):stats(), the Quasar and Railgun swaps, the TX-41 rack, acid_storm, the
    bomblet alias, custom stratagem language tables) are unknown on 0.31.1: declare 0.31.2 for them too.