Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
184 lines
18 KiB
Diff
184 lines
18 KiB
Diff
--- a/README.md
|
||
+++ b/README.md
|
||
@@ -6,7 +6,9 @@
|
||
|
||
The current base supplies prison spawning, two independent scavenging/clue routes, finite medical and outpost stock, an optional ground vehicle, physical radio documents, named discovery markers, native objectives, a separate radio-defence service, capacity-checked helicopter extraction, identity-based partial results, early helicopter-loss defeat, saved mission time/speed, defeat/restart, identity-based lives and local spectator following. Authored site definitions and an injected vanilla faction/asset catalog own the initial physical population. Core provides administration and startup diagnostics; the AI, Intel, Chat and Map modules extend its interfaces inside one addon. HQ coordinates enemy responses through physical radios and available forces.
|
||
|
||
-The [latest development playtest handoff](.build/playtest-2026-10-04-ui-bmp/README.md) includes the inventory callback and BMP ammunition-accounting corrections. Its 439 authored files match 378 PAK payloads and three runtime files. The code/tests/tools bind to 309 passing regression programs; exact-source normal escape and ordinary packaged startup complete with retained engine errors. It remains an **unqualified development candidate**. Existing missions and prior packages are preserved. See [current status](docs/current-status.md) for exact scope and limits.
|
||
+The current working tree passes **326 registered regression programs** after the [5 October validation repairs](docs/evidence/review-audit-2026-10-05/review.json). These offline checks do not establish native gameplay or multiplayer correctness.
|
||
+
|
||
+The [4 October development playtest handoff](.build/playtest-2026-10-04-ui-bmp/README.md) includes the inventory callback and BMP ammunition-accounting corrections. Its 439 authored files match 378 PAK payloads and three runtime files. At that archived checkpoint, code/tests/tools bound to 309 passing regression programs; exact-source normal escape and ordinary packaged startup complete with retained engine errors. It remains an **unqualified development candidate**. Existing missions and prior packages are preserved. See [current status](docs/current-status.md) for exact scope and limits.
|
||
|
||
The [AI migration checkpoint](docs/ai-migration-progress.md) adds delivered-report memory, bounded trajectory prediction, physical infantry pursuit, status checks, body observations and finite equipment. Native memory and actual-sighting response fixtures pass their assertions, with four retained upstream localization errors. See [current development status](docs/current-status.md) for source-bound checkpoints. The earlier five-addon checkpoint shows the [real prisoner view and starting objective](docs/evidence/game-2026-09-19-125906-integrated-prison/ordinary-prison-view.png). Native tests cover scripted escape/restart, finite mortar support, physical radio delivery, shared-map ownership, and the reusable aircraft controller's vertical flight, airborne cancellation and an isolated 20 m fixed route. Archived five-addon packages pass ordinary Chat startup without loose successor source and compile/prepare the empty mission on an isolated Linux server. The latter retains four installed-asset errors reproduced by a vanilla control.
|
||
|
||
--- a/docs/current-status.md
|
||
+++ b/docs/current-status.md
|
||
@@ -1,4 +1,6 @@
|
||
# Current development status
|
||
+
|
||
+**5 October 2026 — review and regression repair:** all **326 registered regression programs pass**. Audio graph resource validation now handles escaped quotes and checks six verified vanilla audio resources; corrupted IDs/paths remain rejected. Stale roadblock, logging and five-vehicle convoy adapters were repaired without changing gameplay scripts. The development source bundle validates 455 authored files. This is offline validation, not a new native-game, multiplayer or FPS result. [Changes and review limits](evidence/review-audit-2026-10-05/review.json).
|
||
|
||
**5 October 2026 — configurable interim roadblocks:** Settings now includes a next-round count from 0–12 (default 4; 0 off), saved in the unified server configuration. Native road sampling places finite four-soldier checkpoints with roadside sandbags, selected-faction equipment and normal VirtualHQ reporting/tasking. A bounded optional search cannot hold up startup indefinitely. The final hidden Workbench run placed four posts with sixteen living guards, passed 51 assertions and completed player admission, prison release and radio progression without script/runtime failures. Its thirteen engine resource errors match the earlier spectator baseline. Live multiplayer and other-terrain placement remain playtest work. [Evidence and limits](evidence/roadblocks-2026-10-05/review.json).
|
||
|
||
--- a/docs/vanilla-resources.json
|
||
+++ b/docs/vanilla-resources.json
|
||
@@ -142,8 +142,14 @@
|
||
"Prefabs/Weapons/Mortars/2B14/Mortar_2B14.et": "D1FFE458E8AC4BDB",
|
||
"Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et": "FA5C25BF66A53DCF",
|
||
"Prefabs/World/Game/PerceptionManager.et": "028DAEAD63E056BE",
|
||
+ "Sounds/FinalMix.afm": "B764D803219C775E",
|
||
"Sounds/Items/_SharedData/PickUp/Items_PickUp_Map.acp": "13F8D0DE67678118",
|
||
"Sounds/Vehicles/Wheeled/UAZ469/Vehicles_UAZ469_Horn.acp": "7E64D37BE3E1F3FA",
|
||
+ "Sounds/_SharedData/Configs/Amplitude/_SharedData/Amplitude_-30LUFS_to_-25LUFS.conf": "BA1F5E72DA797274",
|
||
+ "Sounds/_SharedData/Configs/Frequency/_SharedData/Frequency_DynamicUpdate.conf": "0005FE42A6D80399",
|
||
+ "Sounds/_SharedData/Signals/OneSignal/HornState.sig": "6A92ECFA84F929B3",
|
||
+ "Sounds/_SharedData/Signals/Reverb/Reverb_Base.sig": "76CBB40EF7F227B6",
|
||
+ "Sounds/_SharedData/Signals/Vehicles/Vehicles_Occlusion_OutsideVehicleSource.sig": "D4A1555B02A77EDA",
|
||
"UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt": "3E7733BAC8C831F6",
|
||
"UI/Textures/DeployMenu/Loadout-BG/Loadout_BG_USSR.imageset": "21533F2089005056",
|
||
"UI/Textures/DeployMenu/Loadout-BG/Loadout_US_BG.imageset": "F8AC305446D5D480",
|
||
--- a/tests/test_command_maintenance.py
|
||
+++ b/tests/test_command_maintenance.py
|
||
@@ -24,6 +24,7 @@
|
||
#include <functional>
|
||
#include <string>
|
||
#include <vector>
|
||
+template<class... T>void PrintFormat(std::string,T...){}
|
||
bool server=true;int bumps=0;void Bump(){bumps++;}
|
||
std::vector<std::string> events;
|
||
struct World {float now=1234;float GetWorldTime(){return now;}}world;
|
||
--- a/tests/test_convoy_admission.py
|
||
+++ b/tests/test_convoy_admission.py
|
||
@@ -8,7 +8,7 @@
|
||
s=s.replace('array<AIAgent>','Array<AIAgent*>')
|
||
for cls in ['ERR_ConvoyTask','ERR_ConvoyVehicle','AIAgent','SCR_AIGroupUtilityComponent','SCR_AIVehicleUsageComponent','ERR_ForceRecord','ERR_HQ']:
|
||
s=re.sub(r'\b'+cls+r' ([A-Za-z_]\w*)',cls+r'* \1',s)
|
||
-for obj in ['task','unit','agent','utility','m_Mode','m_Pool','record','hq','active']:
|
||
+for obj in ['task','unit','agent','utility','m_Mode','m_Pool','record','hq','active','observed']:
|
||
s=re.sub(r'\b'+obj+r'\.',obj+'->',s)
|
||
s=s.replace('unit->Index.ToString()','std::to_string(unit->Index)').replace('Group.','Group->').replace('Entity.','Entity->').replace('Receipt.','Receipt->').replace('m_VehicleMgr.','m_VehicleMgr->').replace('Forces().','Forces()->')
|
||
s=s.replace('null','nullptr')
|
||
@@ -45,9 +45,9 @@
|
||
Mode mode;Pool pool;Service service{&mode,&pool};ERR_ConvoyReceipt receipt;ERR_ConvoyTask task;task.Receipt=&receipt;
|
||
ERR_ConvoyVehicle units[6];Group groups[6];Vehicle vehicles[6];IEntity bodies[6];AIAgent agents[6];
|
||
for(int i=0;i<6;i++){units[i].Index=i;units[i].Group=&groups[i];units[i].Entity=&vehicles[i];units[i].Members.Insert(&bodies[i]);agents[i].actor=&bodies[i];groups[i].agents.Insert(&agents[i]);task.Vehicles.Insert(&units[i]);}
|
||
- units[2].valid=false;assert(!service.Admit(&task,100));assert(mode.published==2&&pool.binds==2&&mode.ledger.calls==2&&service.inspections==6);
|
||
- mode.hq.bindOK=false;units[2].valid=true;assert(service.Admit(&task,200));assert(mode.published==6&&pool.binds==6&&mode.ledger.calls==6&&mode.hq.reservations==6&&mode.hq.vehicleBinds==6);
|
||
- assert(service.Admit(&task,300));assert(mode.hq.vehicleBinds==6);assert(mode.published==6&&pool.binds==6&&mode.ledger.calls==6);for(auto&g:groups){assert(g.manager.calls==2&&g.utility.additions==1);}
|
||
+ units[3].valid=false;assert(!service.Admit(&task,100));assert(mode.published==2&&pool.binds==2&&mode.ledger.calls==2&&service.inspections==5);
|
||
+ mode.hq.bindOK=false;units[3].valid=true;assert(service.Admit(&task,200));assert(mode.published==5&&pool.binds==5&&mode.ledger.calls==5&&mode.hq.reservations==5&&mode.hq.vehicleBinds==5);
|
||
+ assert(service.Admit(&task,300));assert(mode.hq.vehicleBinds==5);assert(mode.published==5&&pool.binds==5&&mode.ledger.calls==5);assert(!units[2].Registered);for(int i=0;i<6;i++){assert(groups[i].manager.calls==(i==2?0:2)&&groups[i].utility.additions==(i==2?0:1));}
|
||
Mode duplicate;Pool other;Service check{&duplicate,&other};ERR_ConvoyTask invalid;invalid.Receipt=&receipt;ERR_ConvoyVehicle wrong=units[0];wrong.Registered=false;wrong.Group=&groups[0];IEntity stranger;AIAgent extra{&stranger};groups[0].agents[0]=&extra;invalid.Vehicles.Insert(&wrong);assert(!check.Admit(&invalid,400)&&duplicate.ledger.calls==0);
|
||
groups[0].agents[0]=&agents[0];other.fail=true;assert(!check.Admit(&invalid,400)&&invalid.stopped&&!wrong.Registered&&duplicate.published==0);
|
||
}
|
||
--- a/tests/test_hq_convoy_dispatch.py
|
||
+++ b/tests/test_hq_convoy_dispatch.py
|
||
@@ -135,6 +135,7 @@
|
||
struct ERR_ConvoyStartup{string Status(){return "startup snapshot";}};
|
||
struct ReconService{string Status(){return "recon";}void Return(){}bool SetDelay(int n){return n>=1&&n<=1440;}};
|
||
struct HQ{
|
||
+ ReconService& Roadblocks(){return recon;}
|
||
ReconService recon;ReconService* Recon(){return &recon;}ReconService* AirAssault(){return &recon;}ReconService* Airfield(){return &recon;}ReconService* Recovery(){return &recon;}
|
||
Supply* m_SupplyConvoys=nullptr;bool ConvoyAdmissionReady(){return true;}
|
||
ERR_ConvoyStartup* m_ConvoyStartup=nullptr;
|
||
--- a/tests/test_intel_registration.py
|
||
+++ b/tests/test_intel_registration.py
|
||
@@ -17,7 +17,7 @@
|
||
s=s.replace('Actor.FindComponent(', 'Actor->FindComponent(').replace('SCR_InventoryStorageManagerComponent inventory', 'SCR_InventoryStorageManagerComponent* inventory').replace('SCR_AIGroupUtilityComponent utility', 'SCR_AIGroupUtilityComponent* utility')
|
||
s=s.replace('home->Definition.', 'home->Definition->').replace('Patrol.IsEmpty()', 'Patrol->IsEmpty()').replace('unit->StatusSampler.Bind(', 'unit->StatusSampler.Bind(')
|
||
s=s.replace('ERR_SiteRuntime home;', 'ERR_SiteRuntime home=nullptr;')
|
||
- s=s.replace('Forces().', 'Forces()->')
|
||
+ s=s.replace('Forces().', 'Forces()->').replace('m_Roadblocks.', 'm_Roadblocks->')
|
||
s=s.replace('Settings().','Settings()->').replace('GetGame().','GetGame()->').replace('GetWorld().','GetWorld()->')
|
||
s=s.replace('FindComponent(SCR_InventoryStorageManagerComponent)','FindComponent()').replace('FindComponent(SCR_AIGroupUtilityComponent)','FindComponent()')
|
||
s=s.replace('purpose: EStoragePurpose.PURPOSE_ANY','EStoragePurpose::PURPOSE_ANY').replace('cb: ', '').replace('Operation.', 'Operation->').replace('new ERR_IntelInsertCallback()', 'new Callback()')
|
||
@@ -91,7 +91,8 @@
|
||
struct BodyService:GroupService{void RegisterGroup(SCR_AIGroup g,Sampler& s){assert(s.Bound()&&s.group==g);GroupService::RegisterGroup(g);}};
|
||
struct Reporting{int initializations=0;void Initialize(int id,float now){assert(id>0&&now==10);initializations++;}};
|
||
struct Unit{Reporting AmmoReporting;string HomeId,ForceSourceId;bool ReservedForSupport=false;bool HomeGuard=false;float NextStatus=0;Sampler StatusSampler;int Id=0;SCR_AIGroup Group=nullptr;struct Utility* Utility=nullptr;float NextReport=0;};using ERR_HQUnit=Unit*;
|
||
-struct HQ{void BindSpecialSkills(SCR_AIGroup){}void AuditGroup(ERR_HQUnit){}Mode* m_Mode;GroupService m_Equipment;BodyService m_Bodies;int m_NextId=0;Array<ERR_HQUnit> m_Units;Mortars* m_Mortars=nullptr;Scouts* m_Scouts=nullptr;Recon* m_Recon=nullptr;Airfield* m_Airfield=nullptr;Scouts* m_Hunters=nullptr;Scouts* m_Towns=nullptr;
|
||
+struct Roadblocks{SCR_AIGroup owned=nullptr;bool Contains(SCR_AIGroup g){return g&&g==owned;}};
|
||
+struct HQ{void BindSpecialSkills(SCR_AIGroup){}void AuditGroup(ERR_HQUnit){}Mode* m_Mode;GroupService m_Equipment;BodyService m_Bodies;int m_NextId=0;Array<ERR_HQUnit> m_Units;Mortars* m_Mortars=nullptr;Scouts* m_Scouts=nullptr;Recon* m_Recon=nullptr;Airfield* m_Airfield=nullptr;Scouts* m_Hunters=nullptr;Scouts* m_Towns=nullptr;Roadblocks* m_Roadblocks=nullptr;
|
||
'''+hqbind+hqregister+r'''
|
||
};
|
||
Intel* intel_;HQ* hq_;
|
||
@@ -148,7 +149,11 @@
|
||
assert(!hq.m_Units[4]->ReservedForSupport && !hq.m_Units[4]->HomeGuard && hq.m_Units[4]->HomeId==hq.m_Units[3]->HomeId);
|
||
Group hunter;Scouts hunters;hunters.owned=&hunter;hq.m_Hunters=&hunters;hq.RegisterGroup(&hunter);
|
||
assert(hq.m_Equipment.ids.back()==1&&hq.m_Equipment.fits.back()); // every hunter selects physical manpack fitting
|
||
- Group ordinaryAgain;hq.RegisterGroup(&ordinaryAgain);assert(hq.m_Equipment.ids.back()==7); // ordinary fitting remains distributed
|
||
+ Group checkpoint;Roadblocks posts;posts.owned=&checkpoint;hq.m_Roadblocks=&posts;hq.RegisterGroup(&checkpoint);
|
||
+ assert(hq.m_Equipment.ids.back()==1&&hq.m_Equipment.fits.back());
|
||
+ int checkpointUnits=hq.m_Units.Count(),checkpointFits=hq.m_Equipment.ids.Count();
|
||
+ hq.RegisterGroup(&checkpoint);assert(hq.m_Units.Count()==checkpointUnits&&hq.m_Equipment.ids.Count()==checkpointFits);
|
||
+ Group ordinaryAgain;hq.RegisterGroup(&ordinaryAgain);assert(hq.m_Equipment.ids.back()==8); // ordinary fitting remains distributed
|
||
Group town;Scouts towns;towns.owned=&town;hq.m_Towns=&towns;hq.RegisterGroup(&town);
|
||
assert(hq.m_Equipment.ids.back()==1&&hq.m_Equipment.fits.back()&&!hq.m_Units.back()->ReservedForSupport&&!hq.m_Units.back()->HomeGuard);
|
||
}
|
||
--- a/tests/test_native_convoy_cover_probe.py
|
||
+++ b/tests/test_native_convoy_cover_probe.py
|
||
@@ -113,7 +113,7 @@
|
||
for forbidden in ('ApplyCombatOrder(', 'PendingReport = new', 'new ERR_HQRoute', 'SetAmmoCount(', 'TrySpawnPrefabToStorage(', 'SetOrigin('):assert forbidden not in s
|
||
convoy_path=Path(os.environ.get('ERR_CONVOY_PROBE_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeConvoyProbe.c'))
|
||
convoy=convoy_path.read_text()
|
||
-assert convoy.index('all six native vehicles drove at least 20m')<convoy.index('m_Cover = new')
|
||
+assert convoy.index('all five native vehicles drove at least 20m')<convoy.index('m_Cover = new')
|
||
assert 'if (m_Step == 4) return m_Cover.Tick(hq, now);' in convoy
|
||
stop=body(convoy_path,'void Stop(')
|
||
assert 'if (m_Cover) m_Cover.Stop();' in stop and 'if (m_Intercept) m_Intercept.Stop();' in stop
|
||
--- a/tests/test_native_mounted_intercept.py
|
||
+++ b/tests/test_native_mounted_intercept.py
|
||
@@ -124,7 +124,7 @@
|
||
except SystemExit as error:assert error.code==2
|
||
else:raise AssertionError('conflicting fixture accepted')
|
||
convoy=Path(os.environ.get('ERR_CONVOY_PROBE_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeConvoyProbe.c')).read_text()
|
||
-assert convoy.index('all six native vehicles drove at least 20m')<convoy.index('m_Intercept = new ERR_NativeMountedInterceptProbe')
|
||
+assert convoy.index('all five native vehicles drove at least 20m')<convoy.index('m_Intercept = new ERR_NativeMountedInterceptProbe')
|
||
assert 'if (m_Step == 5) return m_Intercept.Tick(now);' in convoy
|
||
smoke=Path(os.environ.get('ERR_NATIVE_SMOKE_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c'))
|
||
assert 'if (ERR_NativeMountedInterceptProbe.Enabled()) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 840000;' in smoke.read_text()
|
||
--- a/tests/test_source_bundle.py
|
||
+++ b/tests/test_source_bundle.py
|
||
@@ -84,6 +84,17 @@
|
||
self.edit(name, '58D0FB3206B6F859', '5F16D7E4A1CBE075')
|
||
with self.assertRaisesRegex(ValueError, 'identity/dependency'): bundle.build(self.root)
|
||
|
||
+ def test_escaped_audio_graph_references_are_checked(self):
|
||
+ name = 'addons/core/Scripts/Game/ERR/10_AI/ERR_SoldierSpeechAudio.c'
|
||
+ bundle.build(self.root)
|
||
+ self.edit(name, 'D4A1555B02A77EDA', '1111111111111111')
|
||
+ with self.assertRaisesRegex(ValueError, 'unresolved or nonvanilla.*Vehicles_Occlusion'):
|
||
+ bundle.build(self.root)
|
||
+ self.edit(name, '1111111111111111', 'D4A1555B02A77EDA')
|
||
+ self.edit(name, 'Vehicles_Occlusion_OutsideVehicleSource.sig', 'Missing.sig')
|
||
+ with self.assertRaisesRegex(ValueError, 'unresolved or nonvanilla.*Missing.sig'):
|
||
+ bundle.build(self.root)
|
||
+
|
||
def test_missing_project_refused(self):
|
||
(self.root / 'addons/core/addon.gproj').unlink()
|
||
with self.assertRaisesRegex(ValueError, 'identity/dependency'): bundle.build(self.root)
|
||
--- a/tools/resource_references.py
|
||
+++ b/tools/resource_references.py
|
||
@@ -28,4 +28,9 @@
|
||
identity_offset = text.index(OPTIONAL_METHOD) + OPTIONAL_METHOD.index(OPTIONAL_RESOURCE)
|
||
for match in re.finditer(r'\{([A-F0-9]{16})\}([^"\n]+)', text):
|
||
if match.start() != identity_offset:
|
||
- yield match.groups()
|
||
+ guid, resource = match.groups()
|
||
+ # Enforce strings that build a graph escape its closing quote. The
|
||
+ # escape belongs to the string syntax, not to the resource path.
|
||
+ if str(relative_path).endswith('.c') and resource.endswith('\\') and text[match.end():match.end()+1] == '"':
|
||
+ resource = resource[:-1]
|
||
+ yield guid, resource
|
||
--- a/tools/source_bundle.py
|
||
+++ b/tools/source_bundle.py
|
||
@@ -158,7 +158,7 @@
|
||
if path != resource or target not in visible:
|
||
raise ValueError(f'resource outside project dependency closure: {name}')
|
||
elif vanilla.get(resource) != guid and not resource_allowed(external,visible,guid,resource):
|
||
- raise ValueError(f'unresolved or nonvanilla resource: {name}')
|
||
+ raise ValueError(f'unresolved or nonvanilla resource: {name}: {{{guid}}}{resource}')
|
||
return projects
|
||
|
||
|