From 6ffd3a0ebd04df3c3e2ac3306f077388fc98a469 Mon Sep 17 00:00:00 2001 From: "@permadeath.com" Date: Wed, 19 Aug 2026 21:35:19 -0400 Subject: [PATCH] docs(features): four feature ideas from a player Heat as one feature per breakpoint rather than a curve, melee as a cliff that constrains what may be fired, side arcs crossed with per-location damage, and TMM as a ratio against the accuracy it costs. Each names a shape the basis cannot express, not a quantity it does not measure. --- plan/features.md | 56 +++++++++++++++++++++++++++++++++++++++++++++ tests/test_train.py | 2 ++ 2 files changed, 58 insertions(+) diff --git a/plan/features.md b/plan/features.md index 82c659c..a066ba9 100644 --- a/plan/features.md +++ b/plan/features.md @@ -62,3 +62,59 @@ mystery. Two features are deliberately left out of a movement vector: `heat_incurred` and `ammo_spent`. A move commits to neither, and a column that is the same saturated number on every candidate teaches a fit nothing. + +## Named ideas, from a player + +These came from jmm and are worth more than the average backlog item: each one +names a shape the basis currently cannot express, not just a quantity it does +not measure. Verify every threshold against MegaMek before implementing - the +numbers below are a player's recollection and MegaMek is the authority on what +this simulator does. + +### Heat wants a feature per breakpoint, not a curve + +Heat does nothing gradually. It crosses a line and something specific breaks: +movement, then aim, then shutdown, then ammo. TSM turns *on* at a line too, and +that one is a benefit. Verified in MegaMek: `Mek.hasActiveTSM` tests heat >= 9. + +Neither a linear nor an exponential term can express "17 is barely worse than +16, and 19 is a different game". One bounded feature per breakpoint can, and +each is separately explainable to a player - which is the whole point of the +basis. + +Breakpoints to confirm against `Mek` and the heat effect tables: movement +penalty, to-hit penalty, shutdown roll, ammo explosion roll, TSM activation. +Read them out of MegaMek rather than transcribing from a rulebook. + +### Melee is a cliff, and it constrains what you may fire + +Ending a move adjacent, in a hex you can actually attack from, is worth a great +deal of damage. Ending one hex short leaves all of it on the table. A distance +feature cannot say that; a `melee_reach` feature can. + +Three complications, each arguably its own feature: + +- Firing an arm weapon blocks that arm's punch; firing a leg weapon blocks the + kick. So a firing decision spends a melee option, and the bot has to be able + to notice it is doing that. +- Closing to melee usually gives the enemy the same option against us. +- So "run in" and "hold and shoot" are a real fork, and the basis should let a + weight decide it rather than a rule. + +### Side arcs are not the rear arc + +Flanking is currently framed as the rear arc. A side arc is a different thing +and interacts with damage already taken: if their left leg armour is gone, +reaching their *left* side is worth far more than their right. That needs the +per-location armour the observation now carries, crossed with which arc a +candidate hex falls in. + +### TMM is a ratio, not a quantity + +Movement buys a target movement modifier and costs accuracy. Players do not ask +"how much TMM did I gain", they ask whether it was worth it - jumping for +1 TMM +while taking +3 to your own hits is a bad trade, and a feature that only counts +TMM gained will happily make it. + +So: a TMM-per-point-of-self-penalty feature rather than a TMM feature. Possibly +also a term for TMM lost to turning, which would discourage running in circles. diff --git a/tests/test_train.py b/tests/test_train.py index 9c1ed2c..44df017 100644 --- a/tests/test_train.py +++ b/tests/test_train.py @@ -489,6 +489,8 @@ class TestTemporalLabel(unittest.TestCase): def test_no_round_log_means_no_temporal_label(self): old = {"players": [{"name": "North", "bvStart": 100, "bvRemaining": 40}]} self.assertIsNone(label_at(old, "North", 1)) + + class TestImitationCorpus(unittest.TestCase): """Rows reconstructed from another player's moves. -- 2.51.2