Limit repeated AI driver honking; record Workshop 0.0.129
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -0,0 +1 @@
|
||||
15:30:50.026 SCRIPT (E): ERR SMOKE FAIL: horn driver seat
|
||||
@@ -0,0 +1,5 @@
|
||||
15:34:03.323 SCRIPT : ERR HORN RESULT: prefab={AD80FB47D868C775}Prefabs/Vehicles/Wheeled/S105/S105_green.et allowedFrames=31 blockedFrames=878 violations=0
|
||||
15:34:03.323 SCRIPT : ERR SMOKE PASS: native AI driver continuous horn demand bounded with cooldown
|
||||
15:34:03.324 SCRIPT : ERR HORN RESULT: prefab={0B4DEA8078B78A9B}Prefabs/Vehicles/Wheeled/UAZ469/UAZ469_PKM.et allowedFrames=31 blockedFrames=878 violations=0
|
||||
15:34:03.324 SCRIPT : ERR SMOKE PASS: native AI driver continuous horn demand bounded with cooldown
|
||||
15:34:03.324 SCRIPT : ERR SMOKE COMPLETE: native UAZ and civilian AI horn input limits
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Frozen local Workbench ai-driver-horn fixture; no live server or installed asset writes."""
|
||||
from pathlib import Path
|
||||
import hashlib,json,shutil,subprocess,sys,os,time,re
|
||||
ROOT=Path(__file__).resolve().parents[3]
|
||||
sys.path.insert(0,str(ROOT/'tools'))
|
||||
from source_manifest import addon_manifest
|
||||
from headless_game import assert_prefix_idle
|
||||
name=sys.argv[1]
|
||||
if not name.isalnum(): raise ValueError('Use an alphanumeric run suffix')
|
||||
source=Path('/tmp/erri-ai-driver-horn-source-'+name)
|
||||
profile=Path('/tmp/erri-ai-driver-horn-run-'+name)
|
||||
compat=ROOT/'.local/playtest-compat-20260920'
|
||||
assert_prefix_idle(compat)
|
||||
source.mkdir();profile.mkdir()
|
||||
for rel in addon_manifest(ROOT):
|
||||
target=source/rel;target.parent.mkdir(parents=True,exist_ok=True);shutil.copy2(ROOT/rel,target)
|
||||
shutil.copytree(ROOT/'tools',source/'tools',ignore=shutil.ignore_patterns('__pycache__'))
|
||||
(source/'docs').mkdir();shutil.copy2(ROOT/'docs/external-addons.json',source/'docs/external-addons.json')
|
||||
fixture=Path(__file__).with_name('native-fixture.c')
|
||||
target=source/'addons/core/Scripts/Game/ERR/10_AI/ZZZ_HornFixture.c'
|
||||
target.write_text(fixture.read_text())
|
||||
# Opt-in fixture is independent of the historical generic Core smoke.
|
||||
p=source/'addons/core/Scripts/Game/ERR/00_Core/ERR_NativeBaseSmoke.c'
|
||||
p.write_text(p.read_text().replace('array<string> independentFixtures = {','array<string> independentFixtures = {"ai-driver-horn", '))
|
||||
p=source/'tools/headless_workbench.py'
|
||||
p.write_text(p.read_text().replace("choices=['coordinated-ai',", "choices=['ai-driver-horn', 'coordinated-ai',").replace("a.smoke not in {'coordinated-ai',", "a.smoke not in {'ai-driver-horn', 'coordinated-ai',"))
|
||||
# The existing fatal-loss diagnostic is guarded by errSmoke; this Core-only
|
||||
# runner uses errBaseSmoke. Adapt only that test-helper flag in the frozen copy.
|
||||
p=source/'addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerMedical.c'
|
||||
p.write_text(p.read_text().replace('System.GetCLIParam("errSmoke", enabled)', 'System.GetCLIParam("errBaseSmoke", enabled)'))
|
||||
installed=Path.home()/'.local/share/Steam/steamapps/compatdata/1874880/pfx/drive_c/users/steamuser/Documents/My Games/ArmaReforger/addons'
|
||||
(profile/'profile').mkdir()
|
||||
(profile/'profile/EscapistsFactions.json').write_text(json.dumps({'escapists':'ussr','enemy':'us','independent':'fia'})+'\n')
|
||||
(profiles:=profile/'fixture-inputs.json').write_text(json.dumps({'fixture_sha256':hashlib.sha256(fixture.read_bytes()).hexdigest(),'production_sources':addon_manifest(ROOT),'source':str(source),'profile':str(profile)},indent=2)+'\n')
|
||||
command=[sys.executable,str(source/'tools/headless_workbench.py'),'--compat',str(compat),'--profile',str(profile),'--external-addons',str(installed),'--project','core','--smoke','ai-driver-horn','--rounds','1','--timeout','300']
|
||||
result=subprocess.run(command)
|
||||
raise SystemExit(result.returncode)
|
||||
@@ -0,0 +1,101 @@
|
||||
#ifdef WORKBENCH
|
||||
// Inject continuous horn demand into two fixture-owned AI drivers, then observe
|
||||
// the actual production OnApplyControls chain. No human input is generated.
|
||||
class ERR_HornFixtureDriver
|
||||
{
|
||||
IEntity Driver;
|
||||
Vehicle Car;
|
||||
SCR_AIGroup Group;
|
||||
int Started = -1;
|
||||
int Allowed;
|
||||
int Blocked;
|
||||
int Violations;
|
||||
}
|
||||
class ERR_HornFixture
|
||||
{
|
||||
static ref array<ref ERR_HornFixtureDriver> Drivers = {};
|
||||
static ERR_HornFixtureDriver Find(IEntity actor)
|
||||
{
|
||||
foreach (ERR_HornFixtureDriver driver : Drivers) if (driver.Driver == actor) return driver;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
modded class SCR_CharacterControllerComponent
|
||||
{
|
||||
protected override void OnApplyControls(IEntity owner, float timeSlice)
|
||||
{
|
||||
ERR_HornFixtureDriver fixture = ERR_HornFixture.Find(owner);
|
||||
CharacterInputContext input = GetInputContext();
|
||||
bool seated = fixture && CompartmentAccessComponent.GetVehicleIn(owner) == fixture.Car;
|
||||
if (seated && input) input.SetVehicleHorn(1);
|
||||
super.OnApplyControls(owner, timeSlice);
|
||||
if (!seated || !input) return;
|
||||
int now = System.GetTickCount();
|
||||
if (fixture.Started < 0) fixture.Started = now;
|
||||
int elapsed = now - fixture.Started;
|
||||
bool sounding = input.GetVehicleHorn() != 0;
|
||||
if (sounding) fixture.Allowed++; else fixture.Blocked++;
|
||||
if (sounding && elapsed >= 600 && elapsed < 29900) fixture.Violations++;
|
||||
}
|
||||
}
|
||||
modded class ERR_GameMode
|
||||
{
|
||||
protected bool m_ERRHornSpawned;
|
||||
protected int m_ERRHornDeadline;
|
||||
protected override void EOnInit(IEntity owner)
|
||||
{
|
||||
super.EOnInit(owner);
|
||||
if (Replication.IsServer() && !SCR_Global.IsEditMode()) GetGame().GetCallqueue().CallLater(ERR_HornTest, 500, true);
|
||||
}
|
||||
protected void ERR_HornTest()
|
||||
{
|
||||
if (!Active()) return;
|
||||
SCR_PlayerController player = SCR_PlayerController.Cast(GetGame().GetPlayerController());
|
||||
if (!player || !player.GetControlledEntity()) return;
|
||||
if (!m_ERRHornSpawned)
|
||||
{
|
||||
m_ERRHornSpawned = true; m_ERRHornDeadline = System.GetTickCount() + 60000;
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
ERR_HornFixtureDriver driver = new ERR_HornFixtureDriver();
|
||||
vector position = ERR_Actors.Ground(player.GetControlledEntity().GetOrigin() + Vector(30 + i * 15, 0, 0));
|
||||
ResourceName prefab = ERR_CivilianTraffic.CarPrefab(0);
|
||||
if (i == 1) prefab = ERR_ConvoyResources.VehiclePrefab(0);
|
||||
driver.Car = Vehicle.Cast(ERR_Actors.Spawn(prefab, position));
|
||||
driver.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), position));
|
||||
if (!driver.Car || !driver.Group) { Print("ERR SMOKE FAIL: horn vehicle/group creation", LogLevel.ERROR); return; }
|
||||
driver.Group.SetPermanentLOD(0); driver.Group.ActivateAI();
|
||||
ERR_HornFixture.Drivers.Insert(driver);
|
||||
}
|
||||
return;
|
||||
}
|
||||
bool ready = true;
|
||||
foreach (ERR_HornFixtureDriver driver : ERR_HornFixture.Drivers)
|
||||
{
|
||||
// Vehicle compartments are registered asynchronously after spawning.
|
||||
if (!driver.Driver)
|
||||
{
|
||||
BaseVehicleControllerComponent controls = BaseVehicleControllerComponent.Cast(driver.Car.FindComponent(BaseVehicleControllerComponent));
|
||||
if (controls && controls.GetPilotCompartmentSlot())
|
||||
{
|
||||
AIGroup group = driver.Group;
|
||||
driver.Driver = controls.GetPilotCompartmentSlot().SpawnCharacterInCompartment(ERR_CivilianTraffic.DriverPrefab(), group, ERR_ConvoyResources.GroupPrefab());
|
||||
}
|
||||
}
|
||||
array<AIAgent> agents = {}; driver.Group.GetAgents(agents);
|
||||
foreach (AIAgent agent : agents) { agent.SetPermanentLOD(0); agent.ActivateAI(); }
|
||||
if (driver.Started < 0 || System.GetTickCount() - driver.Started < 32000) ready = false;
|
||||
}
|
||||
if (!ready && System.GetTickCount() < m_ERRHornDeadline) return;
|
||||
GetGame().GetCallqueue().Remove(ERR_HornTest);
|
||||
foreach (ERR_HornFixtureDriver driver : ERR_HornFixture.Drivers)
|
||||
{
|
||||
PrintFormat("ERR HORN RESULT: prefab=%1 allowedFrames=%2 blockedFrames=%3 violations=%4", driver.Car.GetPrefabData().GetPrefabName(), driver.Allowed, driver.Blocked, driver.Violations);
|
||||
if (!ready || driver.Allowed == 0 || driver.Blocked == 0 || driver.Violations != 0) { Print("ERR SMOKE FAIL: native AI horn limit", LogLevel.ERROR); return; }
|
||||
Print("ERR SMOKE PASS: native AI driver continuous horn demand bounded with cooldown");
|
||||
}
|
||||
ERR_HornFixture.Drivers.Clear();
|
||||
Print("ERR SMOKE COMPLETE: native UAZ and civilian AI horn input limits");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"change": "Half-second horn pulses with 30 seconds between starts, scoped to authoritative AI road drivers in Escape. Player controls and the separate breach alarm are unchanged.",
|
||||
"policy_tests": [
|
||||
"test_ai_driver_horn.py",
|
||||
"test_vehicle_alarm.py",
|
||||
"test_civilian_traffic.py"
|
||||
],
|
||||
"native": {
|
||||
"horn01": {
|
||||
"complete": false,
|
||||
"passes": 0,
|
||||
"failures": [
|
||||
"15:30:50.026 SCRIPT (E): ERR SMOKE FAIL: horn driver seat"
|
||||
]
|
||||
},
|
||||
"horn02": {
|
||||
"complete": true,
|
||||
"passes": 2,
|
||||
"failures": []
|
||||
}
|
||||
},
|
||||
"fixture_correction": "First fixture queried the pilot compartment immediately after vehicle spawning. The final fixture waits for asynchronous seat registration and runs its demand injector outside the production control override.",
|
||||
"limits": "Injected continuous demand in actual UAZ and S105 driver control chains. No reproduction of the reported traffic scene, audible waveform measurement or remote-client qualification. Native engine asset errors are reported by the runner separately.",
|
||||
"published": true,
|
||||
"publication": "User confirmed successful Workshop upload; local publishing manifest confirms 0.0.129."
|
||||
}
|
||||
Reference in New Issue
Block a user