From 9a2ec8860f3204cd2727624bfb1f4adcd58164dd Mon Sep 17 00:00:00 2001 From: "@permadeath.com" Date: Wed, 19 Aug 2026 21:49:41 -0400 Subject: [PATCH] docs(features): check off the positional group, name what the other three wait on `distance_to_focus`, `psr_risk` and `heat_cost` are not LOS problems. Each waits on something specific - a focus target on the request, the path's own piloting modifiers, and a step generator that offers a run or a jump - so `los`'s exit criterion is met and says so. --- plan/features.md | 34 ++++++++++++++++++++++------------ plan/los.md | 12 +++++++----- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/plan/features.md b/plan/features.md index a066ba9..4d24fd4 100644 --- a/plan/features.md +++ b/plan/features.md @@ -26,10 +26,23 @@ whether it can be used for learning. - [x] Feature trait with a declared normalisation type; make an unnormalised feature impossible rather than discouraged -- [ ] Positional: `range_band_fit`, `exposure`, `cover_quality`, `elevation_gain`, - `tmm_gained`, `distance_to_focus`, `cohesion`, `rear_arc_gain`, `psr_risk`, - `heat_cost`, `los_out`, `los_in` - every one of them needs line of sight - or a hex evaluation built on it, so they wait on `los` +- [x] Positional, the nine that line of sight answers: `exposure`, `los_in`, + `los_out`, `cover_quality`, `range_band_fit`, `elevation_gain`, + `tmm_gained`, `cohesion`, `rear_arc_gain`. `features::positional`, all + `bounded`, all measured from one `Posture::survey` per candidate so a + line is traced once however many features read it +- [ ] Positional, the three still open, and what each waits for: + - `distance_to_focus` waits on the force's focus target reaching the unit + level. A unit proposes before its force has chosen whom to concentrate + on, so there is nothing to measure a distance to; the fix is a field on + `Request::Propose`, not a rule the unit can guess + - `psr_risk` waits on the path carrying its own piloting modifiers. Only + the mover knows what a path crosses, and a version that guessed from the + end hex would be wrong in exactly the cases it exists for + - `heat_cost` waits on the step generator offering a run or a jump. Every + candidate today walks, so the column would be the same number on all of + them - the reason `heat_incurred` is already kept out of a movement + vector - [x] Firing: `expected_damage`, `p_kill`, `p_psr_threshold`, `heat_incurred`, `ammo_spent`, `overkill`, `weapon_concentration` - [x] Target: `target_health`, `target_breach`, `target_threat`, `target_skill` @@ -46,18 +59,15 @@ whether it can be used for learning. - [ ] Interaction features, budgeted and named: `weapon_concentration x target_breach` first - now measurable against a basis the bot scores with -The positional group waits on `los`. - ## What the migration cost Movement candidates are measured against a *projected* volley from the hex they end in - range bracket and gunnery, no to-hit number, because the rules compute -none for a shot nobody can take yet. That half is honest. The other half is not -there at all: nothing measures what a hex costs us. `exposure`, `cover_quality` -and `tmm_gained` are the positional group, so until `los` lands the bot scores -a move on what it could shoot and not on what could shoot it, and it closes -harder than it should. That is a known regression with a named fix, not a -mystery. +none for a shot nobody can take yet. That half is honest. The other half arrived with +`los`: `exposure` prices the share of the enemy's firepower that has a line to +a hex, `cover_quality` prices what the ground in between does to their aim, and +`los_in` prices being seen at all. Before them the bot scored a move on what it +could shoot and not on what could shoot it, and closed harder than it should. 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 diff --git a/plan/los.md b/plan/los.md index 834d69c..9f6c00a 100644 --- a/plan/los.md +++ b/plan/los.md @@ -40,12 +40,14 @@ queries a round at 8v8, versus microseconds for everything else measured. **95-107 us mean** over 2383 calls against **0.7-1.5 us** for ours - see `docs/PERFORMANCE.md` -Still open, and what each waits for: +Done, and what is still open: -- [ ] Every positional feature uses it. The basis in [features](features.md) - names them - `cover_quality`, `arc_exposure`, `crossfire`, `break_los`, - `los_out`, `los_in` - and its positional group is the half still waiting - on this. Wiring them up is that epic's work, not this one's +- [x] Every positional feature uses it. `features::positional` measures nine + from one `Posture::survey` per candidate, and the survey is the only + caller of `LosCache::get` - two traces per enemy per candidate, whatever + the feature count. The three positional features still unwritten are + waiting on things that are not line of sight; see + [features](features.md) - [ ] Terrain changes - fire, smoke, cleared woods - are not sent after the board, so a long match drifts correctly-implemented and wrong. Waits on [protocol](protocol.md) -- 2.51.2