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:
+5
-1
@@ -1,6 +1,10 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
Latest published Workshop version: **0.0.128**
|
Latest published Workshop version: **0.0.129**
|
||||||
|
|
||||||
|
## 2026-10-30 — Quieter AI traffic
|
||||||
|
|
||||||
|
- Limited AI drivers to short horn beeps with a 30-second cooldown to reduce repeated honking.
|
||||||
|
|
||||||
## 2026-10-30 — Results reading time and spectator hints
|
## 2026-10-30 — Results reading time and spectator hints
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Bound native AI horn requests without changing steering, braking or player input.
|
||||||
|
class ERR_AIDriverHornLimit
|
||||||
|
{
|
||||||
|
protected int m_End = -1;
|
||||||
|
protected int m_Next = 0;
|
||||||
|
protected int m_Last = -1;
|
||||||
|
bool Allow(bool requested, int now)
|
||||||
|
{
|
||||||
|
if (m_Last >= 0 && now < m_Last) { m_End = -1; m_Next = now + 30000; }
|
||||||
|
m_Last = now;
|
||||||
|
if (!requested) { m_End = -1; return false; }
|
||||||
|
if (m_End >= 0)
|
||||||
|
{
|
||||||
|
if (now < m_End) return true;
|
||||||
|
m_End = -1;
|
||||||
|
}
|
||||||
|
if (now < m_Next) return false;
|
||||||
|
m_End = now + 500;
|
||||||
|
m_Next = now + 30000;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modded class SCR_CharacterControllerComponent
|
||||||
|
{
|
||||||
|
protected ref ERR_AIDriverHornLimit m_ERRHornLimit;
|
||||||
|
protected override void OnApplyControls(IEntity owner, float timeSlice)
|
||||||
|
{
|
||||||
|
super.OnApplyControls(owner, timeSlice);
|
||||||
|
if (!Replication.IsServer() || !owner || ERR_Actors.Player(owner)) return;
|
||||||
|
ERR_GameMode mode = ERR_GameMode.Get();
|
||||||
|
if (!mode) return;
|
||||||
|
IEntity vehicle = CompartmentAccessComponent.GetVehicleIn(owner);
|
||||||
|
if (!vehicle || !vehicle.FindComponent(AICarMovementComponent)) return;
|
||||||
|
BaseVehicleControllerComponent controls = BaseVehicleControllerComponent.Cast(vehicle.FindComponent(BaseVehicleControllerComponent));
|
||||||
|
if (!controls || !controls.GetPilotCompartmentSlot() || controls.GetPilotCompartmentSlot().GetOccupant() != owner) return;
|
||||||
|
CharacterInputContext input = GetInputContext();
|
||||||
|
if (!input) return;
|
||||||
|
if (!m_ERRHornLimit) m_ERRHornLimit = new ERR_AIDriverHornLimit();
|
||||||
|
bool permitted = m_ERRHornLimit.Allow(mode.Active() && input.GetVehicleHorn() != 0, System.GetTickCount());
|
||||||
|
if (!permitted) input.SetVehicleHorn(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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."
|
||||||
|
}
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
# Finite ground transport
|
# Finite ground transport
|
||||||
|
|
||||||
|
During Escape missions, AI road-vehicle drivers are limited to half-second horn
|
||||||
|
beeps, with at least 30 seconds between beep starts per driver. Holding the horn
|
||||||
|
request or tapping it repeatedly cannot bypass this limit. The authority applies
|
||||||
|
the limit to the driver's native controls; player horns, passengers, aircraft
|
||||||
|
and the separate eight-second lock-breach alarm are unaffected. This bounds horn
|
||||||
|
noise without changing obstacle avoidance, braking or route recovery.
|
||||||
|
|
||||||
The normal Southern supply cache now attempts one locked successor `SupplyUAZ`, based on the vanilla UAZ. Its explicit `Optional 1` declaration permits omission if the parking survey, resource, security or readiness checks fail. The cache, physical clues and guards remain intact. Placement is attempted once after required stock, guards and extraction preparation finish; a spawned candidate gets a separate fixed 15-second settling deadline. Losing the candidate never schedules a replacement or refills fuel.
|
The normal Southern supply cache now attempts one locked successor `SupplyUAZ`, based on the vanilla UAZ. Its explicit `Optional 1` declaration permits omission if the parking survey, resource, security or readiness checks fail. The cache, physical clues and guards remain intact. Placement is attempted once after required stock, guards and extraction preparation finish; a spawned candidate gets a separate fixed 15-second settling deadline. Losing the candidate never schedules a replacement or refills fuel.
|
||||||
|
|
||||||
The Southern parking candidate is **6150/7038**, offset `0 0 -12`, heading 180°, clearance radius 4 m and maximum terrain range 1 m. The [normal-route native run](evidence/native-2026-09-30-normal-route/report.json) accepts this parking square with a 0.625 m terrain range and observes the owned vehicle at approximately 6149.98/7038.01. Its three dedicated checks confirm once-only admission, a resolved owned vehicle and an initially locked state. The earlier occupied-prefix refusal remains recorded. Ordinary driving, held breach input and remote behavior still need testing. A ready vehicle starts locked; the existing driver-door breach action is required. Successful breach now starts a bounded eight-second horn alarm; [alarm lifecycle and qualification limits](vehicle-security.md) distinguish implementation from audio and remote-client proof.
|
The Southern parking candidate is **6150/7038**, offset `0 0 -12`, heading 180°, clearance radius 4 m and maximum terrain range 1 m. The [normal-route native run](evidence/native-2026-09-30-normal-route/report.json) accepts this parking square with a 0.625 m terrain range and observes the owned vehicle at approximately 6149.98/7038.01. Its three dedicated checks confirm once-only admission, a resolved owned vehicle and an initially locked state. The earlier occupied-prefix refusal remains recorded. Ordinary driving, held breach input and remote behavior still need testing. A ready vehicle starts locked; the existing driver-door breach action is required. Successful breach now starts a bounded eight-second horn alarm; [alarm lifecycle and qualification limits](vehicle-security.md) distinguish implementation from audio and remote-client proof.
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
"""Run production horn limits and the control hook with engine boundaries doubled."""
|
||||||
|
from policy_harness import ROOT, body, run
|
||||||
|
p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_AIDriverHorn.c'
|
||||||
|
policy=body(p,'class ERR_AIDriverHornLimit').replace('protected ','').replace('class ERR_AIDriverHornLimit','struct ERR_AIDriverHornLimit')+';'
|
||||||
|
hook=body(p,'protected override void OnApplyControls(')
|
||||||
|
for a,b in [('protected override ','') ,('IEntity owner','Actor* owner'),('super.OnApplyControls(owner, timeSlice);','nativeCalls++;'),('Replication.IsServer()','authority'),('ERR_Actors.Player(owner)','owner->player'),('ERR_GameMode mode = ERR_GameMode.Get();','Mode* mode = currentMode;'),('IEntity vehicle = CompartmentAccessComponent.GetVehicleIn(owner);','Vehicle* vehicle = owner->vehicle;'),('vehicle.FindComponent(AICarMovementComponent)','vehicle->road'),('BaseVehicleControllerComponent controls = BaseVehicleControllerComponent.Cast(vehicle.FindComponent(BaseVehicleControllerComponent));','Controls* controls = vehicle->controls;'),('controls.GetPilotCompartmentSlot()','controls->seat'),('controls->seat.GetOccupant()','controls->seat->occupant'),('CharacterInputContext input = GetInputContext();','Input* input = inputs;'),('mode.Active()','mode->active'),('input.','input->'),('m_ERRHornLimit.Allow','m_ERRHornLimit->Allow'),('System.GetTickCount()','now')]: hook=hook.replace(a,b)
|
||||||
|
run(r'''
|
||||||
|
#include <cassert>
|
||||||
|
'''+policy+r'''
|
||||||
|
struct Input {int horn=1,writes=0;int GetVehicleHorn(){return horn;}void SetVehicleHorn(int v){writes++;horn=v;}};
|
||||||
|
struct Actor;struct Seat {Actor* occupant=nullptr;};struct Controls {Seat* seat=nullptr;};
|
||||||
|
struct Vehicle {bool road=true;Controls* controls=nullptr;};struct Actor {bool player=false;Vehicle* vehicle=nullptr;};
|
||||||
|
struct Mode {bool active=true;};
|
||||||
|
struct Controller {bool authority=true;int now=0,nativeCalls=0;Mode mode;Mode* currentMode=&mode;Input input;Input* inputs=&input;ERR_AIDriverHornLimit* m_ERRHornLimit=nullptr;
|
||||||
|
~Controller(){delete m_ERRHornLimit;}
|
||||||
|
'''+hook+r'''
|
||||||
|
};
|
||||||
|
int main(){
|
||||||
|
ERR_AIDriverHornLimit continuous;int allowed=0;
|
||||||
|
for(int t=0;t<90000;t+=10)if(continuous.Allow(true,t))allowed++;
|
||||||
|
assert(allowed==150); // three half-second beeps over 90 seconds, even held forever
|
||||||
|
ERR_AIDriverHornLimit tapped;
|
||||||
|
assert(tapped.Allow(true,0));assert(!tapped.Allow(false,100));
|
||||||
|
for(int t=200;t<30000;t+=100)assert(!tapped.Allow(t%200==0,t));
|
||||||
|
assert(tapped.Allow(true,30000));assert(!tapped.Allow(true,30500));
|
||||||
|
assert(!tapped.Allow(true,100));assert(!tapped.Allow(true,30099));assert(tapped.Allow(true,30100));
|
||||||
|
Controller c;Actor driver,passenger;Seat seat{&driver};Controls controls{&seat};Vehicle car{true,&controls};driver.vehicle=&car;passenger.vehicle=&car;
|
||||||
|
c.OnApplyControls(&driver,.02f);assert(c.input.horn==1&&c.nativeCalls==1);
|
||||||
|
c.now=500;c.OnApplyControls(&driver,.02f);assert(c.input.horn==0);
|
||||||
|
c.now=1000;c.input.horn=1;c.OnApplyControls(&driver,.02f);assert(c.input.horn==0);
|
||||||
|
// Player control, passengers, aircraft, replicas and unrelated scenarios are untouched.
|
||||||
|
driver.player=true;c.input.horn=1;c.OnApplyControls(&driver,.02f);assert(c.input.horn==1);
|
||||||
|
driver.player=false;c.OnApplyControls(&passenger,.02f);assert(c.input.horn==1);
|
||||||
|
car.road=false;c.OnApplyControls(&driver,.02f);assert(c.input.horn==1);car.road=true;
|
||||||
|
c.authority=false;c.OnApplyControls(&driver,.02f);assert(c.input.horn==1);c.authority=true;
|
||||||
|
c.currentMode=nullptr;c.OnApplyControls(&driver,.02f);assert(c.input.horn==1);c.currentMode=&c.mode;
|
||||||
|
c.mode.active=false;c.OnApplyControls(&driver,.02f);assert(c.input.horn==0);
|
||||||
|
c.inputs=nullptr;c.OnApplyControls(&driver,.02f);c.OnApplyControls(nullptr,.02f);
|
||||||
|
}
|
||||||
|
''')
|
||||||
|
print('PASS: held/tapped AI horn limits, half-second pulses, 30-second cooldown, clock reset, player/passenger/aircraft/replica exclusion and ended mission cleanup')
|
||||||
@@ -13,6 +13,7 @@ def check():
|
|||||||
subprocess.run([sys.executable,'-m','unittest','discover','-s',str(ROOT/'tests'),'-p','test_workshop_changelog.py'],check=True)
|
subprocess.run([sys.executable,'-m','unittest','discover','-s',str(ROOT/'tests'),'-p','test_workshop_changelog.py'],check=True)
|
||||||
subprocess.run([sys.executable,'-m','unittest','discover','-s',str(ROOT/'tests'),'-p','test_discord_changelog_ci.py'],check=True)
|
subprocess.run([sys.executable,'-m','unittest','discover','-s',str(ROOT/'tests'),'-p','test_discord_changelog_ci.py'],check=True)
|
||||||
subprocess.run([sys.executable,str(ROOT/'tests/test_round_statistics.py')],check=True)
|
subprocess.run([sys.executable,str(ROOT/'tests/test_round_statistics.py')],check=True)
|
||||||
|
subprocess.run([sys.executable,str(ROOT/'tests/test_ai_driver_horn.py')],check=True)
|
||||||
subprocess.run([sys.executable,str(ROOT/'tests/test_telemetry.py')],check=True)
|
subprocess.run([sys.executable,str(ROOT/'tests/test_telemetry.py')],check=True)
|
||||||
subprocess.run([sys.executable,str(ROOT/'tests/test_telemetry_credentials.py')],check=True)
|
subprocess.run([sys.executable,str(ROOT/'tests/test_telemetry_credentials.py')],check=True)
|
||||||
subprocess.run([sys.executable,str(ROOT/'tests/test_community_statistics.py')],check=True)
|
subprocess.run([sys.executable,str(ROOT/'tests/test_community_statistics.py')],check=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user