Capture current mission, AI, map and administration development

Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
This commit is contained in:
2026-10-13 22:52:53 +02:00
parent a951a96edc
commit 6a667d8deb
8104 changed files with 2124945 additions and 2061 deletions
@@ -0,0 +1,37 @@
{
"scope": "Only ERR_HQ.c and new ERR_EngineerMinePosts.c; controller and tests separate peer patches.",
"behavior": "Original physical squad can make a second finite engineer attempt at a genuinely newer delivered, globally separated post, subject to4 lifetime attempts per exact original record/source and32 mission attempts.",
"ledger_lifetime": "HQ-owned and never reset by ERR_HQUnit registration, ReleaseAssignment or release. New HQ instance starts a new round. Strong original record/task/assignment identities and scalar snapshots survive mutable task/report values; weak group identity cannot create new quota via a reused source.",
"ordering": [
"Current mission/server and old-job Tick first.",
"Existing unit task/order/combat/physical arrival checks.",
"Canonical Force registry record and StatusSampler ownership/strength binding.",
"All retained jobs BlocksNextPost(record): active or unsettled removal blocks same original force.",
"Copy detached prior selected sites; Consume validates/snapshots exact chronology and quota before one site query.",
"Refused query still consumes attempt. Selected site retained before Begin, including refusal.",
"Begin receives only old sites; shared exact-item tombstone array remains unchanged."
],
"limits": [
"Conservative >160m separation applies to every consumed mission post, even selection/no-stock refusal. This is new bounded policy, not claimed original parity.",
"Four is lifetime per original force; owned predecessor reset its four-per-deployment counter on a new order.",
"No same-post retries, expiry extension, new supplies/forces, defences widened, or native route guarantee.",
"Existing32 jobs and2 active jobs,128/4096/65536 selector work caps unchanged. Extra ledger checks scan at most32 entries.",
"Existing controller.Stop and late callback behavior is handled by companion patch, not duplicated in HQ."
],
"validation": {
"apply_check": "passed against target working tree",
"actual_method_tests": "delegated to /root/player_admission_review; pending at source freeze",
"native": "not launched",
"full_suite": "not run",
"target_writes": false
},
"test_expectations": [
"Initial consumed attempt then a genuinely newer post can Begin once; baseline lifetime latch suppresses it.",
"Repeated task/assignment/message/revision or non-new queued/observed times refuse.",
"Four/32 caps include no-site/no-item refusals; unregister/re-register cannot reset original record/source quota.",
"Source reused by another record, samegroup under anotherrecord, or record changing source/group/strength refuses.",
"Exact160m and below refuses, >160 passes; no target-live-position query.",
"Unresolved old removal blocks new post; prior site copied before own selectedpoint and retained even Begin refuses.",
"Nonfinite/negative/future time, expired task or nonfinite position refuses without consuming."
]
}
@@ -0,0 +1,150 @@
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c
@@ -21,7 +21,6 @@
ref ERR_HQTask Task;
ref ERR_HQTask PendingTask;
ref ERR_EngineerMine EngineerMine;
- bool EngineerMineAttempted;
ref ERR_HQSearchReport PendingSweep;
ref ERR_HQStatusReport PendingStatus;
ref ERR_HQStatusSampler StatusSampler = new ERR_HQStatusSampler();
@@ -133,6 +132,7 @@
protected ref array<ref ERR_HQHuntWindow> m_Hunts = {};
protected ref array<IEntity> m_EngineerMineAttempts = {};
protected ref array<ref ERR_EngineerMine> m_EngineerMineJobs = {};
+ protected ref ERR_EngineerMinePosts m_EngineerMinePosts = new ERR_EngineerMinePosts();
protected ref array<ref ERR_HQBelief> m_PlanBeliefs = {};
protected ref map<string, int> m_HomeAvailable = new map<string, int>();
protected ref ERR_HQNetwork m_Network = new ERR_HQNetwork();
@@ -1219,7 +1219,7 @@
if (unit.Assignment) ReserveTask(unit.Task, reserved, first, second);
if (unit.PendingOrder && (!unit.Assignment || unit.PendingTask != unit.Task)) ReserveTask(unit.PendingTask, reserved, first, second);
}
- // A delivered road post may use one original carried mine. This is local
+ // Each distinct delivered road post may use one original carried mine. This is local
// execution of its existing HQ order; no separate response or new force.
protected void MaintainEngineerMines(float now)
{
@@ -1232,19 +1232,31 @@
}
foreach (ERR_HQUnit unit : m_Units)
{
- if (active >= 2 || m_EngineerMineJobs.Count() >= 32 || m_EngineerMineAttempts.Count() >= 256) break;
- if (unit.EngineerMineAttempted || !unit.Task || !unit.Assignment || !unit.Task.RoadIntercept) continue;
+ if (active >= 2 || m_EngineerMineJobs.Count() >= 32 || m_EngineerMinePosts.Count() >= 32 || m_EngineerMineAttempts.Count() >= 256) break;
+ if (!unit.Task || !unit.Assignment || !unit.Task.RoadIntercept) continue;
if (unit.PendingTask || unit.PendingOrder || unit.Downlink || now >= unit.Task.ExpiresAt) continue;
if (!unit.Eligible(m_Mode) || Fighting(unit)) continue;
- if (vector.DistanceXZ(unit.Group.GetOrigin(), unit.Task.Destination) > 20) continue;
- // One bounded site query/attempt per original squad, including a refusal.
- // Retained exact-item quarantine also prevents another squad retrying it.
- unit.EngineerMineAttempted = true;
+ vector groupPosition = unit.Group.GetOrigin();
+ float arrivalDistance = vector.DistanceXZ(groupPosition, unit.Task.Destination);
+ if (!ERR_ForceRules.FiniteVector(groupPosition) || !ERR_ForceRules.Finite(arrivalDistance) || arrivalDistance > 20) continue;
+ if (!m_Mode.Forces() || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)) continue;
+ ERR_ForceRecord record = m_Mode.Forces().FindGroup(unit.Group);
+ if (!record || record.Group != unit.Group || record.InitialStrength != unit.StatusSampler.InitialStrength()) continue;
+ bool blocked;
+ foreach (ERR_EngineerMine previous : m_EngineerMineJobs)
+ if (previous.BlocksNextPost(record)) { blocked = true; break; }
+ if (blocked) continue;
+ // Snapshot old sites before adding this attempt/site. Refusals consume quota;
+ // registering the original group again cannot reset the mission ledger.
+ array<vector> priorSites = {}; m_EngineerMinePosts.PriorSites(priorSites);
+ int attempt = m_EngineerMinePosts.Consume(record, unit.ForceSourceId, unit.Task, unit.Assignment, now);
+ if (attempt < 0) continue;
vector position;
if (!ERR_EngineerMineSite.Select(unit.Task.Destination, position)) break;
+ if (!m_EngineerMinePosts.RecordSite(attempt, position)) break;
unit.EngineerMine = new ERR_EngineerMine(m_EngineerMineAttempts);
m_EngineerMineJobs.Insert(unit.EngineerMine);
- if (unit.EngineerMine.Begin(m_Mode, unit, unit.Task, position, now)) active++;
+ if (unit.EngineerMine.Begin(m_Mode, unit, unit.Task, position, now, priorSites)) active++;
else unit.EngineerMine.Stop();
break; // One new site query/admission per HQ tick, including refusals.
}
--- /dev/null
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_EngineerMinePosts.c
@@ -0,0 +1,81 @@
+// Mission-owned finite attempt history. Registration and assignment release
+// never reset it. Values are copied before native work; no live enemy queries.
+class ERR_EngineerMinePost
+{
+ ref ERR_ForceRecord Record;
+ SCR_AIGroup Group;
+ string Source;
+ int Strength;
+ ref ERR_HQTask Task;
+ ref ERR_HQReport Assignment;
+ int MessageId;
+ int BeliefId;
+ int Revision;
+ float QueuedAt;
+ float ObservedAt;
+ vector Post;
+ bool HasSite;
+ vector Site;
+}
+class ERR_EngineerMinePosts
+{
+ protected ref array<ref ERR_EngineerMinePost> m_Posts = {};
+ int Count() { return m_Posts.Count(); }
+ bool Eligible(ERR_ForceRecord record, string source, ERR_HQTask task, ERR_HQReport assignment, float now)
+ {
+ if (!record || !record.Group || !record.Physical || record.Eliminated || source.IsEmpty() || record.SourceId != source) return false;
+ if (record.InitialStrength < 1 || record.InitialStrength > 32 || !task || !assignment || m_Posts.Count() >= 32) return false;
+ if (!task.RoadIntercept || task.ReleaseOnly || task.Investigation() || !task.SecuritySite.IsEmpty()) return false;
+ if (task.BeliefId <= 0 || task.BeliefRevision <= 0 || assignment.MessageId <= 0) return false;
+ if (!ERR_ForceRules.Finite(now) || !ERR_ForceRules.Finite(task.QueuedAt) || !ERR_ForceRules.Finite(task.ExpiresAt)
+ || !ERR_ForceRules.Finite(assignment.ObservedAt) || assignment.ObservedAt < 0 || task.QueuedAt < assignment.ObservedAt
+ || task.QueuedAt > now || now >= task.ExpiresAt) return false;
+ if (!ERR_ForceRules.FiniteVector(task.Destination) || !ERR_ForceRules.FiniteVector(assignment.Position)) return false;
+ int used;
+ foreach (ERR_EngineerMinePost previous : m_Posts)
+ {
+ if (!previous) return false;
+ float separation = vector.DistanceXZ(task.Destination, previous.Post);
+ if (!ERR_ForceRules.Finite(separation) || separation <= 160) return false;
+ if (previous.Task == task || previous.Assignment == assignment) return false;
+ // Same source or physical group under another record cannot acquire quota.
+ if (previous.Record != record)
+ {
+ if (previous.Source == source || previous.Group == record.Group) return false;
+ continue;
+ }
+ if (previous.Source != source || previous.Group != record.Group || previous.Strength != record.InitialStrength) return false;
+ used++;
+ if (used >= 4 || task.QueuedAt <= previous.QueuedAt || assignment.ObservedAt <= previous.ObservedAt
+ || assignment.MessageId == previous.MessageId) return false;
+ if (task.BeliefId == previous.BeliefId && task.BeliefRevision <= previous.Revision) return false;
+ }
+ return true;
+ }
+ // Returns immutable entry index or -1; a refused site/item still consumes it.
+ int Consume(ERR_ForceRecord record, string source, ERR_HQTask task, ERR_HQReport assignment, float now)
+ {
+ if (!Eligible(record, source, task, assignment, now)) return -1;
+ ERR_EngineerMinePost entry = new ERR_EngineerMinePost();
+ entry.Record = record; entry.Group = record.Group; entry.Source = source; entry.Strength = record.InitialStrength;
+ entry.Task = task; entry.Assignment = assignment; entry.MessageId = assignment.MessageId;
+ entry.BeliefId = task.BeliefId; entry.Revision = task.BeliefRevision;
+ entry.QueuedAt = task.QueuedAt; entry.ObservedAt = assignment.ObservedAt; entry.Post = task.Destination;
+ m_Posts.Insert(entry);
+ return m_Posts.Count() - 1;
+ }
+ bool RecordSite(int index, vector site)
+ {
+ if (index < 0 || index >= m_Posts.Count() || !ERR_ForceRules.FiniteVector(site)) return false;
+ ERR_EngineerMinePost entry = m_Posts[index];
+ if (!entry || entry.HasSite || !ERR_EngineerMineSite.InBand(entry.Post, site)) return false;
+ entry.Site = site; entry.HasSite = true;
+ return true;
+ }
+ void PriorSites(out array<vector> sites)
+ {
+ sites.Clear();
+ foreach (ERR_EngineerMinePost entry : m_Posts)
+ if (entry.HasSite) sites.Insert(entry.Site);
+ }
+}
@@ -0,0 +1,15 @@
{
"files": {
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c": {
"before_working_sha256": "5d66c53baf8762e76ae59e28721edaa348014af6d988d42891d9a84a6cf44969",
"before_index_sha256": "1f129b979abcb86ee10622e2866aa987807f070f2ab2d818af015e12a8bcfd8c",
"proposed_sha256": "d873b5b689e918f8c907918d673304a184ac2e4a4cb6face527b37a67821ee9f"
},
"addons/core/Scripts/Game/ERR/10_AI/ERR_EngineerMinePosts.c": {
"before_working_sha256": null,
"before_index_sha256": null,
"proposed_sha256": "ab0171a510d46fad6df5b477ae27cb03016b894700e4111e9a5b99d8061fe3a6"
}
},
"patch_sha256": "ca04c1efc00a7b2aedbe92e627faaaa61921f0c778551793129515c041226082"
}