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:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
PASS: production convoy spacing, physical arrival, stable follower targets, supply ownership, leader promotion and bounded stuck recovery
|
||||
PASS: convoy combat keeps native manoeuvres for unchanged contacts and refreshes when contact moves
|
||||
PASS: native convoy proof excludes pre-departure movement and ends via explicit fixture reset, not prohibited player death
|
||||
PASS: boarding retries only after approach/seat progress stops, capped at three, with delayed native reissue and completed-team preservation
|
||||
PASS: slow only owned vehicles near fit dismounted infantry; ignore passengers, casualties and released teams/vehicles
|
||||
PASS: restore missing native driving registration only for healthy owned seated crews; preserve existing association and refuse invalid/released vehicles
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"docs/current-status.md": {
|
||||
"before": "027a565076981f9985019baab93413ef127d7fbb5cd336b55e33a402f4e59af2",
|
||||
"after": "79ea42eacfab8399556bd2f4f7d513a050a7908ac937d3955293b093c8d16d5f"
|
||||
},
|
||||
"docs/mounted-interception.md": {
|
||||
"before": "20f80f34919c32535c1d57da7894ecadee6699f7f17b1993a32bed5837342bec",
|
||||
"after": "f40d1dd93deb7e89f48c3a9645cf35c32531fd5ce3d9ce3247b6561d60155ebb"
|
||||
},
|
||||
"docs/original-feature-coverage.json": {
|
||||
"before": "fa260dc8f7ef94e327c9d0f6a888070c608a19f75384bbefd335915383cbfaa4",
|
||||
"after": "fec7f3fa341b40db09b1d7c9d96907929210b2474048127c4415de0de27f4383"
|
||||
},
|
||||
"docs/original-feature-coverage.md": {
|
||||
"before": "8386a0f56e11041915ec023a078dce627be5bb3732bf148b7d9955f19342cacc",
|
||||
"after": "fa4a79d7dc98a92433fb9157ca9f054b5cedaec333069296deca7a70c4a8047b"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"baseline.json": "88133e797476ce5c09f0e73e2a4958a881ce92214679f403166f0f449c378539",
|
||||
"column-pair.log": "1aca51b7fd5c9a8b48a3cd5d3caf99fe4d7f68c4a0f1666b24400c9da3ea6615",
|
||||
"documentation.json": "6b2944c0a5d7d541b4453a204946574bfb07d7a8895f5b2d4509d3c4bec7b33a",
|
||||
"finish.py": "5596fdfa933c1baf80414a2fa034b1589369b50dca32707355e4c65f33687d57",
|
||||
"full-suite.log": "d51b5de6ad6837d8d1c651c7a297814f301901f86c275d0ef63eb406aebf1151",
|
||||
"geometry-and-review/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c": "a37edcf66e43945f331db47fe81cb2e0e767a7aaf8115244dcf9e6295932be98",
|
||||
"geometry-and-review/apply-check.log": "926b0307f326c4186fe067b9f9fd3b2469165f3c23603518d8cb7089ac84f6ba",
|
||||
"geometry-and-review/baseline/ERR_HQMountedIntercept.c": "c239aaad0ac556744f76b94286c6f204be51b51acdf44e788ca794c2ae967eae",
|
||||
"geometry-and-review/baseline/index-absence.txt": "fc8ca5ea2e7ea6a342a9a49a3a51b7d74ea2d9873517989481ef83f84a3e09cc",
|
||||
"geometry-and-review/baseline/test_hq_mounted_intercept.py": "444411a6a97ebaff69a056302f444f986daa5f6f7f792e622fdbcca731f939c8",
|
||||
"geometry-and-review/dispatcher-review.json": "c3d2e52f96b111b66c26e42622d637d13b5e579e14a6d2899ab6e8640f6d08ca",
|
||||
"geometry-and-review/existing-green.log": "20410d8a7171f3e140f415186697a442452d6eeea8564d622ed093d2259bbc8d",
|
||||
"geometry-and-review/fix.patch": "184363078545edc1fc505dfaf90e51631cb476143f96ade3dc0ef269d91d99b6",
|
||||
"geometry-and-review/initial-second-clear-mutation-unused-variable.log": "2b43987be1a153ae7679cfc2561f1f2cbb335228ae01ff9c1075fe90dbff58f0",
|
||||
"geometry-and-review/integrated-review.json": "6e887383de9d8a1e5872f3d1d167f617c1341ea535c79a9a7b068b768edb7f62",
|
||||
"geometry-and-review/mutation-results.json": "44402073d526f7ab2168e88de75fa9b0e0d41035715ab420dfe7f559f5e7f479",
|
||||
"geometry-and-review/name-fix-review.json": "0541b7f5353121a0964396211c2b728577896bdea5956889f59a051bca4b1be4",
|
||||
"geometry-and-review/native-review.json": "0ad03baa8df8948ecb41a30d52a6dfe8340d9fb20bcf9f460a6507e8e75ec614",
|
||||
"geometry-and-review/pair-budget-removed-red.log": "e143ea30b07a8b51443a2495f697ae0b9778158e556e9a22af5a06903e0edbee",
|
||||
"geometry-and-review/pair-budget-removed.c": "a7ba966da9402edabad480f25400890bf1311bd2cae6896563b3412dcaf95322",
|
||||
"geometry-and-review/pair-cases.cpp": "c7c95beab28a1c201b8bd5a4fb45329a507a22a9b534972afc404d1edb4d1c31",
|
||||
"geometry-and-review/pair-chord-instead-of-arc-red.log": "ad5c1a435b66ae76ac319be3690bac241431011b2b878134f1fded18d455bf51",
|
||||
"geometry-and-review/pair-chord-instead-of-arc.c": "c17fffd9f613c829580eb1c38886d9b7148c3cda4ff2a0506e5eb7f87bf508e7",
|
||||
"geometry-and-review/pair-green.log": "295e0d7fe50d343f78f30abbf2df0b6c660fc5799d8fd0dad1adec170b7feb85",
|
||||
"geometry-and-review/pair-second-clear-checks-first-red.log": "eb70314d2c3231974485ce08086240093aaa29818124c859bbfa5861c1708b7f",
|
||||
"geometry-and-review/pair-second-clear-checks-first.c": "6f7b87e01bd86deaeb68712ce4cf1acb690d11106560a2ba7aa2877d0774870e",
|
||||
"geometry-and-review/pair-second-clear-removed.c": "d280c1f6c2af23b66a84698158cf60a036a76acd7e0ff4876eed15fe6acbcf96",
|
||||
"geometry-and-review/review.json": "ba79376d1ce53f1a4a643c6eb89defbd9422f4a11173ab17df117e287d73be0f",
|
||||
"geometry-and-review/service-review.json": "dde756fd8d7b429e7b5b2ee303dc2f42916a3e64923f9c69f3c724eee84bce3e",
|
||||
"geometry-and-review/source-manifest.json": "b439a06d5826ebae5681b330d8edc078f7156c02a9a16f451eb50e542fd298e1",
|
||||
"geometry-and-review/spacing-removed-red.log": "2198ec3b181d8ef59fb7be2a8bf7f1b9b9caaf87fc06a44312c668fd64b782b8",
|
||||
"geometry-and-review/spacing-removed.c": "00583e80d0baabf7d5f3e7d2b29e9b7adaaf74de80f17795df0fd796f08df876",
|
||||
"geometry-and-review/tests/test_hq_mounted_intercept_pair.py": "a8d758b1ff20bb6d1a95a9ff1d38f687e43c819c279f5ea9692e84f766dc4fb9",
|
||||
"hq/INTEGRATION.txt": "4c8e9b44dc4d6188732538a885d6c26e454b934cbbab279534bf95201a13b854",
|
||||
"hq/adapt.py": "694a3d7cbedc8a0867cabb7ba52fc1b3288a7961e586a2155915625063b2363f",
|
||||
"hq/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": "60d0fef83f43eea0501bf5fe46f369556e22acda6d034908b5965d8820b28893",
|
||||
"hq/apply-check.log": "9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa",
|
||||
"hq/baseline/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": "e98e931e010124ba567f415690999b96868f32a18edc7c4e1f21c0775b3c095f",
|
||||
"hq/baseline/tests/test_hq_mounted_dispatch.py": "2b2ebf810ef0084bad5f4cfaf3d155ecac0f002b82bbd54d9756a9592d364fa9",
|
||||
"hq/baseline.log": "d648778af27092056612f45a6005ba4aae4faae994e36db686d0bc438a3742a1",
|
||||
"hq/edit.py": "dcd0cd2fbef65d621b60a843b5606183d8fe6ff05593382227582365ab79b638",
|
||||
"hq/fix.patch": "b9847147b3b5d5940a5dcb3135fdd22a96cef9c91e3550be83182fe80680d3d0",
|
||||
"hq/green.log": "0ef29fa159293cac8ba289a719e5741ed398b432efb94ebbb4dfbd5708a5b5bb",
|
||||
"hq/mutations/partner-claim-cleanup/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": "3d7a3c05c800181f913be394ca14de62babd461e9643644a2328b8fac4335298",
|
||||
"hq/mutations/partner-count/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": "96372367477311e1a6a8e09ae489c1d3f13e3392e23348ccddee0cd392874cd8",
|
||||
"hq/mutations/receipt-barrier/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": "3947a642c6954707fcc7ffc2d748411b900329ee92f0b9a5cbbeea19531f73c1",
|
||||
"hq/mutations/survey-budget/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": "a1b43180f281bf2bd2d21607da06cee07bf0e4eb80bd21fae8722496151a6d2e",
|
||||
"hq/mutations/token-domain/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": "81ff705e26b5468e5079b244b19e033c72d0b10b7adb11f1aaa323b05877bb9c",
|
||||
"hq/partner-claim-cleanup.log": "c3a35ed3879a2fee37effa757b232b4ceb95c7ef1ba434a729e25c36f3af4972",
|
||||
"hq/partner-count.log": "d2509326b40ce5a9cf48af52cebc8b14ea86e737351d05039efc1767366b459a",
|
||||
"hq/receipt-barrier.log": "8b54a8b5bae603f2cbdfafa940765cc1a3adde356ca9f815cf5cf3f5db88ba4b",
|
||||
"hq/results.json": "81c88f3369f7d60123ddc0c2ca2d56aebdf9e4162aec2aad08dc52a58d434ef9",
|
||||
"hq/source-manifest.json": "2664094d357c679efdd195016d662846797cc90423ae74332ee85e852baf413d",
|
||||
"hq/survey-budget.log": "51b2a23d24f365c0b3ac0e1efc66d7c898a8315139b47420c41f4211608f537f",
|
||||
"hq/tests/test_hq_mounted_dispatch.py": "50e8973b730c34a54775e7ba5ae2a72c9a6a101bc9b14a1521c4b5c6d401552e",
|
||||
"hq/token-domain.log": "308d5b92360fdb63970edbb8ffe7917399ff38000c68bfa011d48c003421514b",
|
||||
"hq/verify.py": "1fde0bda54a9b0391eb4ecdc9d0e5d8a118fc61a7dc6ae9aca5d7e6e6d2fe1f3",
|
||||
"installed.json": "da0f6a4856ef831567ff58f38e3079f5cf2528f1026c2e86f53dc070fa36c9b6",
|
||||
"name-fix-test.log": "0ef29fa159293cac8ba289a719e5741ed398b432efb94ebbb4dfbd5708a5b5bb",
|
||||
"name-fix.json": "59de64aef61389f09606469862bde7868e9ed7937e7b4af7c826073aa6f38902",
|
||||
"normal.log": "e1698c7d57fe5875617f234d6780b152cfb84911109e28d34f521708728d20aa",
|
||||
"pre-native-name-fix/full-suite.log": "52b74eea16e8846f0936743164b24615b2123c00125a1f780a724cbdc81ba37c",
|
||||
"pre-native-name-fix/installed.json": "15a2b934f6885611a26e5a0d4b98056e6fb3f2747ff2e9265f42a1bb69cb8a5b",
|
||||
"pre-native-name-fix/normal.log": "8d08964f2cee80ae3c84853ccbcb9b248e0c910228b2df2c9e4413c71e840d68",
|
||||
"pre-native-name-fix/suite-inputs.json": "4eddea7702b382301403bc616a04082965eb1410be893e23b1abf76d1bc0d2cb",
|
||||
"pre-native-name-fix/suite-result.json": "87844f9f6d95ab2e22388e3ef4e8fef5807aaf605fee44745409dfa0f53bafc6",
|
||||
"preservation.json": "9210ac71b42dc8ee8a70d927ec40aeed48b8695237ff3b81a258483928222ea3",
|
||||
"review.json": "63db3509f8d67c3b24fdf787c7dd00d77f2a72b8acfe47ae6f9350f10c29cf62",
|
||||
"run-suite.py": "b609a9132f93339d09e9b12cc233f6c1d1984a4df1e9819e55008590dd262646",
|
||||
"service/addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c": "b59016d83cb9d40136b98b4e0067ea490bc553574fc53603593efd5522dbed20",
|
||||
"service/apply-check.log": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"service/baseline/addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c": "e503e3a2eeaff638dee7fac6830cfbac6b8d87402e1191ff65aaa9961d9e0b86",
|
||||
"service/baseline-red.log": "3282ef3cdcb9b5f5557227a37a795e310240d6d598dce23e261bc7fbe8dd308b",
|
||||
"service/baseline.json": "464f9a60275428a64a75fa808a6947d32c0bfa3bc1dd4fa0cbe160b8ffa29a15",
|
||||
"service/build.py": "e939c391668a86cf9823499e5bf1aa52473b21bbc3a3f0aca531cc49def04bd4",
|
||||
"service/finding.json": "7268014a105f877113945712452019a9b7e4023c112fda7510edaf932b67af18",
|
||||
"service/finish_source.py": "118824bf1d4c47f2664de22633238d3aac8ab53ea0d1ca12870c3274a6712336",
|
||||
"service/fix.patch": "dd034f9837703ba3cbf157490999152ef1d00fae7878c5fe8073d808e3abe1c4",
|
||||
"service/green.log": "376c9f1c0e0fa25eee0d283e57561cd2ed6d1b826a8c2fb9486d681e82ef0f8b",
|
||||
"service/lifecycle.log": "6e7f1708a5e9c0af68ffd0ace1edc33e99edff479cee2e6750bcaca1c94317ff",
|
||||
"service/mutation-rollback-red.log": "810fb2ce2337fe3f25ee3404b12a49a263e0310318e54cc2e7b3fe21119e9df7",
|
||||
"service/mutation-rollback.c": "da1737a13c17d3f1472d8632f66fd8b248e161fb90d5b608dac22cffece4cc7d",
|
||||
"service/mutation-spacing-red.log": "d11417b94e4ddf6357314e03e6e8560e145e685d094dfaf5a4681171c02c39c7",
|
||||
"service/mutation-spacing.c": "d32933a8def63a962900a7a31e93a25c67cd934c2f7946e90cbb82ef16b0135c",
|
||||
"service/rules.log": "20410d8a7171f3e140f415186697a442452d6eeea8564d622ed093d2259bbc8d",
|
||||
"service/seal.py": "7034fbcfeab09ba95c19c1c7b2257a53a608abff44631b71a79937e7a0586cd6",
|
||||
"service/source-manifest.json": "e27b7654dd458683e8689f1621149b589070ea0177588c08c7328d7d2604cf86",
|
||||
"service/tests/test_convoy_intercept_pair.py": "52b55727b7c2441d43530efa701045b9439b9175c71fbd2ae7577f851d2c98ec",
|
||||
"source-after.json": "d7b2a8a4c7bdca7cd78a23747c3190b0f1c41a36c16bb9d14490bdc40792e648",
|
||||
"suite-inputs.json": "5d89f7171a55bd59a581efaa326ebafeb11633bd907ac7c91871987cd7385d22",
|
||||
"suite-result.json": "9d22857ddcf86ee7ceec3ba976b1d562fd1b5eac48ee73dbe990513600df03e7"
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
from pathlib import Path
|
||||
import hashlib,json,subprocess,shutil,sys
|
||||
R=Path('/home/jakub/devel/escapistsrereintroduced');B=Path('/tmp/erri-pair-root-0054');E=R/'docs/evidence/mounted-pair-2026-10-03';N=R/'docs/evidence/native-2026-10-03-mounted-pair-normal'
|
||||
load=lambda p:json.loads(p.read_text())
|
||||
sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
def write(p,v):p.write_text(json.dumps(v,indent=2)+'\n')
|
||||
b=load(B/'baseline.json');installed=load(B/'installed.json');suite=load(B/'suite-result.json');inputs=load(B/'suite-inputs.json')
|
||||
assert suite['exit']==0 and suite['inputs_unchanged'] and suite['regression_programs']==294
|
||||
assert len({x['path'] for x in suite['programs']})==294 and all(x['exit']==0 for x in suite['programs'])
|
||||
assert all(sha(R/n)==h for n,h in inputs.items())
|
||||
assert all(sha(R/n)==h for n,h in installed.items())
|
||||
assert all(sha(R/n)==h for n,h in b['files'].items() if n not in installed)
|
||||
sys.path.insert(0,str(R/'tools'));from source_manifest import addon_manifest
|
||||
source=addon_manifest(R);runner=load(N/'runner.json.txt');report=load(N/'report.json')
|
||||
assert source==load(N/'source-sha256.json')==load(N/'launch-source-sha256.json')
|
||||
assert report['assertions_passed']==63 and report['smoke_completions']==1 and not report['script_or_runtime_failures'] and len(report['engine_errors'])==13
|
||||
assert runner['cleanup_confirmed'] and runner['source_unchanged_after_run'] and runner['external_addons_unchanged_after_run'] and sha(N/'console.log.txt')==runner['console_sha256']
|
||||
index=hashlib.sha256(subprocess.check_output(['git','diff','--cached','--binary'],cwd=R)).hexdigest();assert index==b['index_diff_sha256']
|
||||
original=load(Path('/tmp/erri-heartbeat-20260930-1542-reference.json'));assert all(sha(Path('/home/jakub/devel/escapists')/n)==h for n,h in original.items())
|
||||
candidate=R/'.build/playtest-2026-10-02-convoy-survivors';seal=load(R/'docs/evidence/playtest-refresh-2026-10-02-convoy-survivors/candidate-sha256.json');assert {str(p.relative_to(candidate)):sha(p) for p in candidate.rglob('*') if p.is_file()}==seal
|
||||
assert Path('/tmp/erri-pair-geometry-0054/native-review.json').is_file()
|
||||
E.mkdir(exist_ok=False)
|
||||
for name in ['baseline.json','installed.json','preservation.json','run-suite.py','suite-inputs.json','suite-result.json','full-suite.log','normal.log','finish.py','column-pair.log','name-fix.json','name-fix-test.log']:
|
||||
shutil.copyfile(B/name,E/name)
|
||||
shutil.copytree(B/'pre-native-name-fix',E/'pre-native-name-fix')
|
||||
for folder,name in [('/tmp/erri-pair-service-0054','service'),('/tmp/erri-pair-geometry-0054','geometry-and-review'),('/tmp/erri-pair-hq-0054','hq')]:
|
||||
shutil.copytree(folder,E/name,ignore=shutil.ignore_patterns('__pycache__','*.pyc'))
|
||||
write(E/'source-after.json',source)
|
||||
result={'feature':'Two original convoy escorts dispatched to spaced positions on one road from delivered HQ memory','authored_files':len(source),'changed_production_files':[n for n in installed if n.startswith('addons/')],'regression_programs':294,'native_assertions':63,'native_completions':1,'native_script_failures':0,'native_engine_errors':13,'strict_native_failure_retained':True,'native_archive':str(N.relative_to(R)),'cleanup_source_external_unchanged':True,'original_tracked_files_unchanged':len(original),'prior_paths_preserved_except_allowlisted':len(b['files']),'candidate_files_unchanged':len(seal),'index_diff_sha256':index,'first_native_compile_failure':'docs/evidence/native-2026-10-03-mounted-pair-compile','native_compiler_fix':'Renamed new choice.Vehicle field to choice.Escort; native Vehicle type collision. Dispatch adapter changed correspondingly.','package_refreshed':False,'release_qualified':False,'limits':['Native run proves compilation and ordinary local escape integration, not paired physical driving/holding/rejoining.','Native AddWaypoint/ClearOrder operations are not transactional; callbacks cancel exact owned claims after commit begins.','Human escape, real multiplayer/JIP/reconnect/twelve-player play and comparative FPS remain unverified.','Existing development package predates paired interception and remains unchanged.']}
|
||||
write(E/'review.json',result)
|
||||
status=R/'docs/current-status.md';first,tail=status.read_text().split('\n',1)
|
||||
section="""**3 October 2026 — HQ can dispatch a pair of original convoy escorts:** two armed escorts from the same finite convoy can receive separate radio orders to spaced positions on one actual road ahead of delivered travel memory. Both receipts are required before either detour begins. Interrupted delivery, stale history or loss of an original crew cancels the pair; there is no replacement partner, deadline renewal or new combat force. Single-escort fallback, force limits and the total two-road-survey budget remain intact.
|
||||
|
||||
All **294 registered regression programs pass**. Independent review corrected a feasible-pair selection gap; source tests cover radio arrival order, interrupted delivery, waypoint allocation failure, exact ownership and two-escort column rejoining. A hidden/muted normal Everon run completes **63 assertions and one round**, with **zero script/runtime failures and thirteen retained engine errors**. The first native compile caught a field/type naming conflict; its failed result is retained, and the corrected source was retested. Cleanup and unchanged source/external inputs are confirmed. This establishes compilation and ordinary-loop integration, not native physical paired interception, human play, multiplayer or FPS. Existing missions, the Git index and the previous playtest package are preserved; that package predates this addition. [Implementation and evidence](evidence/mounted-pair-2026-10-03/review.json).
|
||||
"""
|
||||
status.write_text(first+'\n\n'+section+'\n'+tail.lstrip('\n'))
|
||||
doc=R/'docs/mounted-interception.md';doc.write_text(doc.read_text().replace('The existing packaged playtest predates this implementation.', 'The existing packaged playtest predates this paired extension.'))
|
||||
for name in ['docs/original-feature-coverage.json','docs/original-feature-coverage.md']:
|
||||
p=R/name;old='One existing armed convoy escort can receive a finite road-interception radio order from delivered travel history; native interception movement/hold/rejoin remain unqualified.'
|
||||
new='One or two original armed escorts from the same convoy can receive finite road-interception orders from delivered travel history. A pair occupies spaced positions on one road only after both real downlinks arrive; native interception movement/hold/rejoin remain unqualified.'
|
||||
text=p.read_text();assert text.count(old)==1;p.write_text(text.replace(old,new))
|
||||
write(E/'documentation.json',{n:{'before':inputs[n],'after':sha(R/n)} for n in ['docs/current-status.md','docs/mounted-interception.md','docs/original-feature-coverage.json','docs/original-feature-coverage.md']})
|
||||
write(E/'evidence-sha256.json',{str(p.relative_to(E)):sha(p) for p in sorted(E.rglob('*')) if p.is_file()})
|
||||
print(json.dumps(result,indent=2))
|
||||
@@ -0,0 +1,720 @@
|
||||
PASS: actual objective overlay lifecycle preserves separate ownership, late readiness/reopen, data refresh, pan/zoom/culling, supported sibling/shared-frame parents, coordinate offsets, native task-clear isolation and terminal cleanup; native rendering remains unverified
|
||||
PASS: actual map element initializes each widget, retries missing widgets, updates position and changes only stale text/icon data; no native image-load claim
|
||||
PASS: actual native fixture root/config ownership and idempotent cleanup preserve foreign map, selector excludes ordinary route, fixed budgets/screenshots/UGC boundaries retained; native operations not executed
|
||||
PASS: actual full Inspect rejects canvas parenting, wrong active container/canvas, omitted origin offset, wrong rendered box and stale coordinates; differing origins and DPI accepted
|
||||
PASS: actual long-warning phases retain visible label through1500ms checkpoint then restore radio before close; synthetic marker separated from unmoved player
|
||||
PASS: actual Center places marker100screenpx left of pan center at both fixture zooms without altering disclosed coordinates
|
||||
PASS: missing, child-only, malformed, zero and duplicate prefab root IDs rejected
|
||||
PASS: delayed scene/inventory arrival, deferred preload, identity checks, stale replies, timeout and observer cleanup
|
||||
PASS: actual inventory manifest, delayed/missing/wrong-owner item links, finite cap, consumed-stock refresh and preload ordering
|
||||
PASS: spawn scene remains held through final acknowledgement and nearby use; leaving, reassignment, disconnect and teardown release it
|
||||
PASS: production prison AI hold/release, repeat activation, authority/faction/player exclusion, dead actor and stop cleanup
|
||||
PASS: actual protected/capped damage plus exact-actor terminal fixture bypass through delayed native damage, fail-closed ownership and untouched ordinary actor; no native claim
|
||||
PASS: actual full planner selection/commit body, 360 equivalence scenarios, orders/reservations/cooldowns identical; one128-unit scan vs >10000 repeated unit visits; no FPS claim
|
||||
PASS: actual observer monitor/recovery/cleanup rejects retained old controller, preserves new camera/editor/hints/timer, restarts once and retains normal recovery; native effects modeled from SDK, no runtime claim
|
||||
PASS: former chat controller releases only its own callbacks and preserves replacement menu; current-owner stop/scope and repeated teardown remain intact
|
||||
PASS: wrong observer fixture selector rejected before runtime/profile setup; no engine launched
|
||||
PASS: actual depot radio/planning/two-leg state transitions, fixed deadline, late proof quarantine, finite import and no adoption after Stop
|
||||
PASS: actual depot HQ lifecycle, existing planner priority, server/mode/owner gates, idempotent polling and owner replacement
|
||||
PASS: actual full depot leaf, exact custody, isolated callbacks, finite reservation, false/unknown and Stop quarantine, once-only delivery and caps; native movement unqualified
|
||||
PASS: actual authored identity capture, READY-only detached provenance, same shared stock admission, completed exact two-leg proof, lifetime caps and Stop no late adoption
|
||||
usage: test_native_mounted_intercept.py [-h] [--steam STEAM]
|
||||
[--external-addons EXTERNAL_ADDONS]
|
||||
--compat COMPAT --profile PROFILE
|
||||
[--smoke {normal-escape-route,escape-route,optional-site-skip,infantry-combat,medical-combat,hunter-faction,unconscious-camera,objective-map,town-garrisons,site-garrisons,radio-interaction,enemy-loadout,aggression,air-logistics,air-assault,air-recon,airfield-markers,opening-tools,mortar-station,generated-sites,hq-local-handoff,infantry-transfer,hq-body,convoy,supply-convoys,mounted-ammo,alarm-hearing,hq-memory,hq-awareness,aircraft-field,aircraft-route,native-end-active,native-end-preparing,aircraft-control,aircraft-cancel,aircraft-translate,map-ownership,mortar-refresh,mortar-salvo,mortar-courier,station-teardown,prison-breach,powered-flight,powered-translate,local-admission,mortar-expiry,mortar-transfer,mortar-fire,patrol-hq,victory,lost-helicopter,player-death,radio-network,hq-response,mortar-inventory,startup-failure,site-survey,cache-loss,extraction-capacity,early-helicopter-loss,extraction-startup-failure,extraction-boundary-failure,transport-loss,transport-survey,lifecycle,objective-ui,recovery,prisoner-kit,medical,casualty-load,casualty-direct,casualty-bench,casualty-remove,environment,environment-restart,patrols,radio-document-loss,radio-document-loss-partial,guard-cancel,casualty-service}]
|
||||
[--maintenance-refuel]
|
||||
[--mounted-swap-eligibility]
|
||||
[--mine-stock] [--mounted-courier]
|
||||
[--mounted-intercept] [--convoy-cover]
|
||||
[--field-terrain-audit]
|
||||
[--field-service-cancel]
|
||||
[--route-service-cancel]
|
||||
[--station-teardown-diagnostics]
|
||||
[--flight-autohover]
|
||||
[--flight-action-inputs]
|
||||
[--flight-input-diagnostics]
|
||||
[--recon-takeover-only]
|
||||
[--body-recovery-only]
|
||||
[--rounds ROUNDS] [--timeout TIMEOUT]
|
||||
[--project {core,chat}]
|
||||
test_native_mounted_intercept.py: error: --mounted-intercept requires Chat --smoke convoy, one round and timeout at least900 seconds
|
||||
usage: test_native_mounted_intercept.py [-h] [--steam STEAM]
|
||||
[--external-addons EXTERNAL_ADDONS]
|
||||
--compat COMPAT --profile PROFILE
|
||||
[--smoke {normal-escape-route,escape-route,optional-site-skip,infantry-combat,medical-combat,hunter-faction,unconscious-camera,objective-map,town-garrisons,site-garrisons,radio-interaction,enemy-loadout,aggression,air-logistics,air-assault,air-recon,airfield-markers,opening-tools,mortar-station,generated-sites,hq-local-handoff,infantry-transfer,hq-body,convoy,supply-convoys,mounted-ammo,alarm-hearing,hq-memory,hq-awareness,aircraft-field,aircraft-route,native-end-active,native-end-preparing,aircraft-control,aircraft-cancel,aircraft-translate,map-ownership,mortar-refresh,mortar-salvo,mortar-courier,station-teardown,prison-breach,powered-flight,powered-translate,local-admission,mortar-expiry,mortar-transfer,mortar-fire,patrol-hq,victory,lost-helicopter,player-death,radio-network,hq-response,mortar-inventory,startup-failure,site-survey,cache-loss,extraction-capacity,early-helicopter-loss,extraction-startup-failure,extraction-boundary-failure,transport-loss,transport-survey,lifecycle,objective-ui,recovery,prisoner-kit,medical,casualty-load,casualty-direct,casualty-bench,casualty-remove,environment,environment-restart,patrols,radio-document-loss,radio-document-loss-partial,guard-cancel,casualty-service}]
|
||||
[--maintenance-refuel]
|
||||
[--mounted-swap-eligibility]
|
||||
[--mine-stock] [--mounted-courier]
|
||||
[--mounted-intercept] [--convoy-cover]
|
||||
[--field-terrain-audit]
|
||||
[--field-service-cancel]
|
||||
[--route-service-cancel]
|
||||
[--station-teardown-diagnostics]
|
||||
[--flight-autohover]
|
||||
[--flight-action-inputs]
|
||||
[--flight-input-diagnostics]
|
||||
[--recon-takeover-only]
|
||||
[--body-recovery-only]
|
||||
[--rounds ROUNDS] [--timeout TIMEOUT]
|
||||
[--project {core,chat}]
|
||||
test_native_mounted_intercept.py: error: --mounted-intercept requires Chat --smoke convoy, one round and timeout at least900 seconds
|
||||
usage: test_native_mounted_intercept.py [-h] [--steam STEAM]
|
||||
[--external-addons EXTERNAL_ADDONS]
|
||||
--compat COMPAT --profile PROFILE
|
||||
[--smoke {normal-escape-route,escape-route,optional-site-skip,infantry-combat,medical-combat,hunter-faction,unconscious-camera,objective-map,town-garrisons,site-garrisons,radio-interaction,enemy-loadout,aggression,air-logistics,air-assault,air-recon,airfield-markers,opening-tools,mortar-station,generated-sites,hq-local-handoff,infantry-transfer,hq-body,convoy,supply-convoys,mounted-ammo,alarm-hearing,hq-memory,hq-awareness,aircraft-field,aircraft-route,native-end-active,native-end-preparing,aircraft-control,aircraft-cancel,aircraft-translate,map-ownership,mortar-refresh,mortar-salvo,mortar-courier,station-teardown,prison-breach,powered-flight,powered-translate,local-admission,mortar-expiry,mortar-transfer,mortar-fire,patrol-hq,victory,lost-helicopter,player-death,radio-network,hq-response,mortar-inventory,startup-failure,site-survey,cache-loss,extraction-capacity,early-helicopter-loss,extraction-startup-failure,extraction-boundary-failure,transport-loss,transport-survey,lifecycle,objective-ui,recovery,prisoner-kit,medical,casualty-load,casualty-direct,casualty-bench,casualty-remove,environment,environment-restart,patrols,radio-document-loss,radio-document-loss-partial,guard-cancel,casualty-service}]
|
||||
[--maintenance-refuel]
|
||||
[--mounted-swap-eligibility]
|
||||
[--mine-stock] [--mounted-courier]
|
||||
[--mounted-intercept] [--convoy-cover]
|
||||
[--field-terrain-audit]
|
||||
[--field-service-cancel]
|
||||
[--route-service-cancel]
|
||||
[--station-teardown-diagnostics]
|
||||
[--flight-autohover]
|
||||
[--flight-action-inputs]
|
||||
[--flight-input-diagnostics]
|
||||
[--recon-takeover-only]
|
||||
[--body-recovery-only]
|
||||
[--rounds ROUNDS] [--timeout TIMEOUT]
|
||||
[--project {core,chat}]
|
||||
test_native_mounted_intercept.py: error: --mounted-intercept requires Chat --smoke convoy, one round and timeout at least900 seconds
|
||||
usage: test_native_mounted_intercept.py [-h] [--steam STEAM]
|
||||
[--external-addons EXTERNAL_ADDONS]
|
||||
--compat COMPAT --profile PROFILE
|
||||
[--smoke {normal-escape-route,escape-route,optional-site-skip,infantry-combat,medical-combat,hunter-faction,unconscious-camera,objective-map,town-garrisons,site-garrisons,radio-interaction,enemy-loadout,aggression,air-logistics,air-assault,air-recon,airfield-markers,opening-tools,mortar-station,generated-sites,hq-local-handoff,infantry-transfer,hq-body,convoy,supply-convoys,mounted-ammo,alarm-hearing,hq-memory,hq-awareness,aircraft-field,aircraft-route,native-end-active,native-end-preparing,aircraft-control,aircraft-cancel,aircraft-translate,map-ownership,mortar-refresh,mortar-salvo,mortar-courier,station-teardown,prison-breach,powered-flight,powered-translate,local-admission,mortar-expiry,mortar-transfer,mortar-fire,patrol-hq,victory,lost-helicopter,player-death,radio-network,hq-response,mortar-inventory,startup-failure,site-survey,cache-loss,extraction-capacity,early-helicopter-loss,extraction-startup-failure,extraction-boundary-failure,transport-loss,transport-survey,lifecycle,objective-ui,recovery,prisoner-kit,medical,casualty-load,casualty-direct,casualty-bench,casualty-remove,environment,environment-restart,patrols,radio-document-loss,radio-document-loss-partial,guard-cancel,casualty-service}]
|
||||
[--maintenance-refuel]
|
||||
[--mounted-swap-eligibility]
|
||||
[--mine-stock] [--mounted-courier]
|
||||
[--mounted-intercept] [--convoy-cover]
|
||||
[--field-terrain-audit]
|
||||
[--field-service-cancel]
|
||||
[--route-service-cancel]
|
||||
[--station-teardown-diagnostics]
|
||||
[--flight-autohover]
|
||||
[--flight-action-inputs]
|
||||
[--flight-input-diagnostics]
|
||||
[--recon-takeover-only]
|
||||
[--body-recovery-only]
|
||||
[--rounds ROUNDS] [--timeout TIMEOUT]
|
||||
[--project {core,chat}]
|
||||
test_native_mounted_intercept.py: error: --mounted-intercept requires Chat --smoke convoy, one round and timeout at least900 seconds
|
||||
usage: test_native_mounted_intercept.py [-h] [--steam STEAM]
|
||||
[--external-addons EXTERNAL_ADDONS]
|
||||
--compat COMPAT --profile PROFILE
|
||||
[--smoke {normal-escape-route,escape-route,optional-site-skip,infantry-combat,medical-combat,hunter-faction,unconscious-camera,objective-map,town-garrisons,site-garrisons,radio-interaction,enemy-loadout,aggression,air-logistics,air-assault,air-recon,airfield-markers,opening-tools,mortar-station,generated-sites,hq-local-handoff,infantry-transfer,hq-body,convoy,supply-convoys,mounted-ammo,alarm-hearing,hq-memory,hq-awareness,aircraft-field,aircraft-route,native-end-active,native-end-preparing,aircraft-control,aircraft-cancel,aircraft-translate,map-ownership,mortar-refresh,mortar-salvo,mortar-courier,station-teardown,prison-breach,powered-flight,powered-translate,local-admission,mortar-expiry,mortar-transfer,mortar-fire,patrol-hq,victory,lost-helicopter,player-death,radio-network,hq-response,mortar-inventory,startup-failure,site-survey,cache-loss,extraction-capacity,early-helicopter-loss,extraction-startup-failure,extraction-boundary-failure,transport-loss,transport-survey,lifecycle,objective-ui,recovery,prisoner-kit,medical,casualty-load,casualty-direct,casualty-bench,casualty-remove,environment,environment-restart,patrols,radio-document-loss,radio-document-loss-partial,guard-cancel,casualty-service}]
|
||||
[--maintenance-refuel]
|
||||
[--mounted-swap-eligibility]
|
||||
[--mine-stock] [--mounted-courier]
|
||||
[--mounted-intercept] [--convoy-cover]
|
||||
[--field-terrain-audit]
|
||||
[--field-service-cancel]
|
||||
[--route-service-cancel]
|
||||
[--station-teardown-diagnostics]
|
||||
[--flight-autohover]
|
||||
[--flight-action-inputs]
|
||||
[--flight-input-diagnostics]
|
||||
[--recon-takeover-only]
|
||||
[--body-recovery-only]
|
||||
[--rounds ROUNDS] [--timeout TIMEOUT]
|
||||
[--project {core,chat}]
|
||||
test_native_mounted_intercept.py: error: --mounted-intercept and --convoy-cover are mutually exclusive
|
||||
PASS: actual native interception witness requires real radio cursor/lineage, original actor identities, fixed movement/hold/expiry/resume and immutable deadline; no movement/delivery injection
|
||||
PASS: actual CLI rejects wrong mode/project/rounds/899s/conflict before runtime; valid flag and terminal branch identify synthetic-observation qualification
|
||||
PASS: visible base hook and actual fixture override delegate once; delivery proof uses retained pre-super route only on successful production transition
|
||||
actual mounted HQ dispatch planning, radio, ownership and lifecycle checks passed
|
||||
PASS: actual mounted intercept eligibility/order chronology, fixed destinations, native road selection bounds, width, segment heading, clearance and settlement delegation; no native driving claim
|
||||
PASS: actual paired arc geometry,40m/80m spacing, separate original polylines, both-position validation and128road/4096segment budget; no native driving proof
|
||||
PASS: actual pair admission, rollback, native allocation/revocation boundaries, per-member ownership, hold and expiry
|
||||
PASS: actual Tick clock initialization admits fractional first ticks and expires without extending the original start
|
||||
PASS: actual finite conservation/deadline/callback policies; read-only selection, bounded once-only inventory submission and no can refill; native transfer not executed
|
||||
PASS: actual exact-context membership, bounded identity/reach gates, native start/cache/eligibility ordering and no duplicate/foreign action start; fuel manager identity and empty-can headroom guards checked
|
||||
PASS: actual detached node/value snapshot and conservation refuse mismatched lengths before indexed reads, retain original identities under native out-array aliasing and log bounded counts once; native cause remains unknown
|
||||
PASS: actual mine evidence sealing/recovery, historical coarse publication, caps, canonical ownership, marker join retry/owner-change/stop; native arming and replicated vector-array transport untested
|
||||
PASS: actual full document recovery retains authored/sealed targets, one shared spent ledger, retry/exhaustion semantics and canonical multi-player ownership alongside supplemental mine intel
|
||||
PASS: actual infantry search suspension, quiet grace, finite resume, expiry/replacement and defensive/ownership boundaries
|
||||
PASS: infantry combat gate precedes radio delivery and preserves non-task transports
|
||||
PASS: actual HQ contact transition wakes only fit squad members, cancels only task orders and logs changes once
|
||||
PASS: actual finite medicine grants, authored stock deduction, full-backpack refusal/pouch fallback, no loot replenishment, unresolved-operation quarantine and ownership/stop guards
|
||||
PASS: only recent native detected/identified threats suspend hunter travel; stale/disarmed/destroyed contacts cannot strand patrols
|
||||
PASS: actual hunter combat transition cancels only its travel once, holds through renewed contact and resumes after quiet; unregistered startup preserved
|
||||
PASS: captured sighting retention, immutable details, expiry, replay rejection, bounded sound uncertainty/cooldown, radio loss, priority and overdue-status fairness
|
||||
PASS: arrow press/release, held keys, opposing keys, disabled/menu input and firearm-only retention
|
||||
PASS: town trigger and hard 700m exclusion for all observers, empty/invalid positions and final-check crossings
|
||||
PASS: exact finite site squads, invalid roster/count rejection, committed-site spacing and safe fallback boundary
|
||||
PASS: mortar couriers use the mobile site team and refuse permanent-only garrisons
|
||||
PASS: omitted preferred mortar depot selects a surviving owned camp/mobile team; no camp, foreign cache and permanent-only sites are unavailable; historical priority retained
|
||||
PASS: real-session mortar deadline, failed-support convoy admission and hunter survivor/recovery gates
|
||||
PASS: native-route retry preserves delivered target, limits attempts and suspends during combat
|
||||
PASS: stale/deleted AI utility cannot receive Complete; mortar HQ preflight withholds unsafe orders; mounted convoy cover never creates infantry Defend
|
||||
PASS: flight crew affiliation and group precede native boarding; preparation remains bounded even while passengers wait
|
||||
PASS: factionless/deleted map targets do not abort perception; faction lookup tolerates teardown; deleting helicopter releases without capture/engine access
|
||||
PASS: shoot adapter waits for a physical round; bounded same-shell retries, interrupted/never-started loads, committed no-retry, order/owner changes and normal native delegation
|
||||
PASS: missing/invalid faction crew defers before any native spawn or allowance insertion; all registered RHS factions have explicit soldier and medic profiles
|
||||
PASS: vehicle radio issued once; pending, refusal and looting cannot duplicate or fabricate a physical radio link
|
||||
PASS: actual vehicle-radio link requires the original seated crew, physical manpack and live uncaptured mission vehicle; looting, dismount, release and teardown remove it
|
||||
PASS: actual hunter release preserves survivors/HQ, releases driving ownership once, delegates capture/teardown, and excludes prison/patrol/mortar from generic objective defence
|
||||
PASS: actual player-entry hook latches original hunter capture before native callbacks; both release layers cancel ownership without new entities/refund; bounded current-owner guards and prior-release policy
|
||||
PASS: real opening road geometry enforces wide azimuths; fresh delivered contact preempts patrol cooldown, respects radio failures and does not starve in-flight interception
|
||||
PASS: actual scoped 10% tighter REGULAR aim error and alert perception; preserves equipment multiplier and restores vanilla behavior when unbound
|
||||
PASS: physical inspection and parent-wreck retention; server-only searched/report delivery unlocks native cleanup once with a 300-second delay
|
||||
PASS: bounded same-group empty-spawn retries, no casualty refill, queue retirement, delivery-gated intel, prepared-team reuse, throttled diagnostics and LOD release
|
||||
PASS: production airborne memory confidence, persistence, ground coverage, confirmed losses, artillery veto, cooldown and invalid-input rules
|
||||
PASS: production cover retains the carried HQ target until its deadline; airport reload waits until the drop pass and cover have finished
|
||||
PASS: air logistics reservation preserves delivered search until radio handoff, excludes guards/combat/in-flight orders, and quarantines unresolved native loads/moves
|
||||
PASS: actual ammunition cargo selector handles empty rifle/machinegun/sniper magazines, preserves loaded variants, excludes auxiliary launchers and tolerates missing native metadata
|
||||
PASS: original 15-minute launch/loss cadence, active/captured timer disarm, saved delay reset, stop, 300m orbit and bounded close-contact standoff at 30/60/120 Hz
|
||||
PASS: actual aircraft-removal notification guards owner/server/active/stop and schedules one full delay without replay extension
|
||||
PASS: actual helicopter delay applies resolved Random values, preserves countdown on save failure, and leaves active flights running
|
||||
PASS: hill prominence/elevation, actual road edges/heights/bends and server-only finite scout admission, pending stock, timeout and no replacements/refills
|
||||
PASS: mortar duty scope excludes players, other crews, stopped/client/inactive states; only destruction releases an active assignment, while the gunner may approach the gun
|
||||
PASS: random mode restrictions, full numeric ranges, independent calendar dates and strict daylight boundaries
|
||||
PASS: actual killais eligibility includes the 1000m boundary, excludes players/retained bodies/dead/non-character entities and checks full 3D distance
|
||||
PASS: actual generation scheduler bounds each tick, enforces deadline/candidate ceiling, validates fallback, commits only accepted positions, skips only dispensable sites, preserves required objectives/intel, and resets the next search budget
|
||||
PASS: terrain placement samples world bounds, retains streaming candidates, bounds waits/work, rejects foreign fallbacks, preserves objectives/intel and validates completion
|
||||
PASS: actual navigation readiness supports static meshes, yields only for loadable streaming tiles and refuses unreachable positions
|
||||
PASS: real sampling handles absent airports and extraction clearance preserves geometry/navigation/flatness while selecting the correct aircraft survey
|
||||
PASS: universal connector, embedded/standalone exports, multiple terrains, stable IDs, native nav configs, missing-nav visibility, path validation and overwrite protection
|
||||
PASS: airfield detection accepts physical runways/towers and bounded authored markers, rejects unrelated props, water and out-of-world markers, and samples terrain height
|
||||
PASS: configured airport parsing, bounded readiness, stale admission rejection and cleanup wiring
|
||||
PASS: automatic default, explicit header/CLI opt-out, editor/authored-mode guards, optional airport override precedence and fresh-round saving guard
|
||||
PASS: singleton identity guards, paired faction deserialization and client world-cleanup wiring
|
||||
PASS: task/map/respawn getters survive delayed old destruction and preserve ordinary-mode behavior
|
||||
PASS: replication hierarchy refuses absent/aliased nodes and establishes delivery dependencies idempotently
|
||||
PASS: standalone old faction delegates collected once, with null/deleted owners ignored
|
||||
PASS: actual terrain takeover startup skips mode-less/menu worlds, always forwards super, retains eligible/unsupported/authored/client/editor semantics
|
||||
PASS: native name callback preserves authored modes, opt-outs and failures; terrain suffix follows world changes, cached resolution and confirmed takeover metadata restores on cleanup without changing scenario resource or overwriting later titles
|
||||
PASS: actual terrain parent traversal, Armenhof/Nogova names, Everon alias, missing-resource fallback, empty names and bounded cycles/depth
|
||||
PASS: saved terrain GUIDs, defaults, restart persistence, clearing, backup recovery, corrupt-file preservation, write failures, and client write denial
|
||||
PASS: startup terrain capture protects client/editor/menu worlds, default behavior, same-map loads and reload-loop recovery
|
||||
PASS: actual transition waits for native startup, disconnects without bans, requires an empty session, retains every loaded mod, bounds waiting and consumes requests once
|
||||
PASS: actual faction resolution reserves explicit roles, samples without replacement, substitutes disabled selections, and rejects duplicate/unknown/missing choices and insufficient pools
|
||||
PASS: Random all submits persistent random roles immediately; manual mixed choices, stale/unauthorized UI guards and debounce preserved
|
||||
PASS: actual content policy gates parent packs, individual factions, faction identity and finite group rotation with vanilla fallback
|
||||
PASS: actual delivered road eligibility, ahead projection without chords, reservations, stock road query bounds, terrain/water/obstruction refusal and clean outputs
|
||||
PASS: actual delivered-task lineage, expiry, old-order retention, native commit, release, road precision expiry and duplicate delivery
|
||||
PASS: actual original-roster local handoff,100m3D bound,busy carrier/budget exclusion,origin interruption including deletedsource,normal radio delivery and per-source replay history
|
||||
PASS: actual visual intake retains source/time/position/id, rejects replay, prefers usable own route, excludes simultaneous callers and conservatively resets negative-sweep dwell
|
||||
PASS: actual fixture local/radio gating, immutable source/time/position, interrupted-report exclusion and exact-owner cleanup
|
||||
PASS: local-handoff runner refuses wrong project/round count/insufficient deadline before runtime work
|
||||
PASS: actual fixture allocator terminal/unsubmitted guard, original-group lookup and both candidate gates
|
||||
PASS: actual report-derived waypoint insertion/order and tighter positive radii; no formation navigation claim
|
||||
PASS: actual Place/Stop waits native pose update, bounds four attempts, preserves original position, restores pending/settled placement and respects ownership/accepted-move quarantine
|
||||
PASS: actual fixture current/original recipient ownership, unresolved transfer cleanup, exact-token release and idempotent Stop
|
||||
PASS: actual fixture phased-cargo readiness, bounded callbacks, captured-stock mixed-caliber selection/continuation, reservation cleanup and component magazine counts
|
||||
PASS: actual inventory-fixture setup uses ordinary prison unlock before constructing its convoy-waiting probe; failed unlock cannot run fixture
|
||||
PASS: actual authored storage anchors, native trace flags, own-hierarchy exclusion, external wall/vehicle obstruction, range, unknown geometry and bounded context refusal
|
||||
PASS: actual convoy binding, finite identities, disjoint tokens, shared report replay/cooldown, original recipients and HQ cleanup
|
||||
PASS: actual finite convoy supply pool; immutable slots, shared demand state, sparse/capped discovery and permanent shutdown
|
||||
PASS: actual HQ rotates finite donors, skips unavailable/contacted trucks, uses delivered reports and dispatches at most one trip per planning cadence
|
||||
PASS: actual original turret identity, native/canonical guards, scoped maximum-skill profile, reset, dismount/reboard, requested-skill preservation and restoration
|
||||
PASS: bounded HQ startup/memory windows and outcome-specific full-route deadlines
|
||||
PASS: explicit escape-route skips only administrator stress, preserving original full-fixture wait/failure paths
|
||||
PASS: normal escape route keeps ordinary catalog and generated placement; historical selectors remain unchanged
|
||||
PASS: actual read migrates old valid settings, preserves explicit disabled state, rejects invalid data; next-round feedback and fixed menu remain honest
|
||||
PASS: actual automatic convoy timing/authority/admission/one-attempt/manual-task/stop policy; native deployment unproved
|
||||
PASS: late authoritative failure reason refreshes open result; detach, mode change and success subscriptions preserved
|
||||
infantry resupply actual rules, radio transitions, owned cleanup and unresolved-operation tests passed
|
||||
PASS: actual resupply Roster preserves permanent release, original slot mapping and bounded ownership
|
||||
PASS: actual designated zero/one original carrier selection, settled startup, shared finite claims, no refund after submission
|
||||
PASS: actual HQ startup grant readiness forwarding is authoritative and fail-closed
|
||||
PASS: actual finite PMN4 designated-zero/one entitlement, pinned original ownership, shared commit, physical custody, callback quarantine/deletion and no retry
|
||||
PASS: actual bounded empty-return and finite delivery; phase-isolated callbacks, exact ownership proof, stop quarantine and original actor/vehicle boundaries
|
||||
PASS: actual shared finite identity reservation; competing consumers, submission permanence, stale cleanup and96-item bound
|
||||
PASS: actual carried-mounted swap, shared committed claim, physical slot reach, exact two-way proof, uncertainty and terminal cleanup
|
||||
PASS: actual mounted pickup, exact finite ownership, pre-submission commit, physical custody, bounded callback and quarantine lifecycle; no native coverage claim
|
||||
PASS: actual mounted courier admission, radio, individual orders, two-stage transfer, reboarding, token cleanup and quarantine lifecycle
|
||||
PASS: actual scoped crew permit and native board completion/failure preserves foreign seats without teleport fallback
|
||||
PASS: actual shared mounted stock/history ownership, atomic exact HQ pair claim, and bounded stopped-owner proof polling
|
||||
PASS: actual carried/mounted early ammunition policy and status-first shared uplink; complete threshold, phases, cooldown, failures requested check-ins and non-urgent detached carried-mine status
|
||||
PASS: actual group radio startup preservation, finite native grants, callback quarantine, frozen selection/actor ownership and bounded observation
|
||||
PASS: actual maintenance native-stock check rejects missing/duplicate/wrong-identity/unowned/depleted/malformed stock and is read-only; native runtime not executed
|
||||
..................
|
||||
----------------------------------------------------------------------
|
||||
Ran 18 tests in 0.235s
|
||||
|
||||
OK
|
||||
PASS: actual radio node rejects current incapacitation, death, ownership and faction changes despite cached inventory; one-second capability refresh remains explicit
|
||||
PASS: Southern finite maintenance stock preserves weapons/clues and optional transport, historical Western fixture stays separate; native capacity/consumption untested
|
||||
PASS: optional transport deferred behind required readiness, bounded once-only spawn/omission, isolated cleanup, occupied-candidate retention and required-default preservation
|
||||
PASS: actual diagnostic fallback runs only on failed missing-context call, derives unique native pilot door, preserves stock eligibility and restores prior index on success/failure; diagnostics mutate no game state.
|
||||
PASS: actual native-prelude cleanup restores only original changed damage, refuses stale/incapacitated/aboard teleport; bounded five-seat/four-door fixture wiring checked. Native execution remains pending.
|
||||
PASS: actual CreateTransport requires successful scoped Configure after handbrake, preserves ordinary vehicles, tracks spawned entity before failures and never treats failed lock registration as ready.
|
||||
PASS: actual preparation Configure guards/idempotence/breach preservation, security eligibility/final current-body server mutation and all scoped action guards; all five seat/four door resource overrides. Native hold, prefab merge, replication and boarding remain unverified.
|
||||
PASS: production vehicle alarm single trigger/replica completion/no replay, fixed deadline/readiness wait, failed audio, dedicated server, moving exact handle, destruction/native end/deletion/clock and mission teardown. Native sound and multiplayer delivery require engine qualification.
|
||||
PASS: production alarm emitter/receiver original time/location, explicit native queue only, 32-entry cap, 50m local witness eligibility, bound perception, native-super preservation; actual engine broadcast/hearing remains unverified.
|
||||
PASS: actual optional candidate barrier, exact callback/custody proof and unchanged mine/primary snapshots; no native transfer claim
|
||||
Native finite mine-stock runner: parser, scope, exclusions and command pass
|
||||
PASS: actual bounded native mounted proof/detached payload/deadline/cleanup methods, production-only setup and isolated runner selector
|
||||
PASS: actual one-existing-convoy candidate readiness, sender-root identity, incomplete cargo refusal and fixed three-candidate bound
|
||||
PASS: actual once-only native dry query, exact before/after slots, native refusal, shutdown; shared bounded discovery and distinct opt-in verdict
|
||||
PASS: actual completion selector and pre-cleanup variant capture preserve separate radio and eligibility verdicts
|
||||
PASS: actual cargo diagnostic stages separate absent/foreign magazines, used/empty/invalid rounds, well mismatch and exact truck storage/attachment without mutation
|
||||
PASS: actual native courier witness rejects missing radio/custody/swap/reboard evidence; CLI opt-in bounded; native success remains unqualified
|
||||
PASS: actual courier constructor/Tick waits for preparation, returns before physical checks, preserves fixed deadline and sticky stop/failure; Enfusion startup awaits native verification
|
||||
PASS: actual arranged native alarm fixture deadline, single emit, natural-route observation, original timestamp/area/detail assertions, natural observer proximity/activation wait, failure cleanup and independent canonical-body restoration; native delivery itself remains unqualified.
|
||||
PASS: actual convoy cover observation/snapshot/uplink/HOLD/RESUME/cleanup transitions; native boundaries mocked, non-convoy runner opt-in refused
|
||||
Enemy optics actual-service ownership, preservation, callback, deadline and bounded-inspection policies passed
|
||||
PASS: actual delivered cover freshness/lineage/replay and canonical executor; owned waypoint-only hold, one resume at preserved route index, invalid ownership/crew/ledger refusal and fail-closed native recreation
|
||||
PASS: actual Stinger show/set/help state precise actor-roll effects; read-only help, bounded values, malformed syntax and failed-save preservation; no rerolls/gameplay edits and no native persistence claim
|
||||
PASS: new/retasked search and radio delivery ammo gates; active tasks and delivered release preserved
|
||||
HQ convoy delivered-report planning and downlink regressions passed
|
||||
PASS: actual own-inventory ammo inspection deduplicates loaded/carried/shared wells, separates chambers/launchers, freezes scalar snapshots and treats unsupported/capped/foreign inventory as unknown
|
||||
PASS: actual carried PMN4 inventory, original-owner guards, bounded deduplication, independent unknown state and detached counts
|
||||
PASS: actual mounted hierarchy sampling, ownership exclusion, deduplication, completeness/caps, quarter threshold and detached validation; no runtime policy or ammunition mutation
|
||||
PASS: WORKBENCH opt-in diagnostics distinguish cycles/cap/storage/manager/mount failures without repeated native traversal
|
||||
PASS: actual HQ original convoy-root admission rejects wrong force/server/group and never supplies replacement lookup
|
||||
PASS: production mounted structural seat scan ignores >128 carried/cargo descendants, shares exact native mount/magazine ownership, preserves attachment seats and exact occupants, rejects unknown/cyclic/oversize structures; native binding still requires qualification.
|
||||
PASS: native body fixture uses existing immutable actors, real production LOS/queued uplink, pre-delivery isolation, actual status overlap, bounded cleanup and no fabricated reports/spawns
|
||||
PASS: actual native fixture lookup selects exact BODY kind/identity/message from bounded evaluations, never sighting-only Get
|
||||
PASS: actual ammo snapshot comparison checks every scalar/category flag; native probe requires real roster rescan, loaded-round lower bound and separate immutable pending/Watch objects
|
||||
PASS: actual delivered slot-union bodies preserve overlap/disjoint evidence, replay idempotence, status subset integrity, full-wipe closure and formation-local bounds
|
||||
test_review_added_allowed_media_invalidates_snapshot (__main__.ReviewTests.test_review_added_allowed_media_invalidates_snapshot) ... ok
|
||||
test_review_added_source_directory_rejected (__main__.ReviewTests.test_review_added_source_directory_rejected) ... ok
|
||||
test_review_loose_root_resource_and_symlink_rejected (__main__.ReviewTests.test_review_loose_root_resource_and_symlink_rejected) ... ok
|
||||
test_review_metadata_media_and_sidecars_remain_allowed_and_bound (__main__.ReviewTests.test_review_metadata_media_and_sidecars_remain_allowed_and_bound) ... ok
|
||||
test_review_missing_link_rejected (__main__.ReviewTests.test_review_missing_link_rejected) ... ok
|
||||
test_review_package_guard_sees_new_scripts (__main__.ReviewTests.test_review_package_guard_sees_new_scripts) ... ok
|
||||
test_review_retargeted_private_link_rejected (__main__.ReviewTests.test_review_retargeted_private_link_rejected) ... ok
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 7 tests in 0.059s
|
||||
|
||||
OK
|
||||
PASS: actual report methods reproduce old starvation; status-first scheduler delivers check-ins and requires 30s without combat or actual visible contacts before negative reports
|
||||
PASS: actual actor group lookup retains controlled character membership when seated/inherited vehicle agents differ; missing control or parent refuses
|
||||
.................
|
||||
----------------------------------------------------------------------
|
||||
Ran 17 tests in 3.435s
|
||||
|
||||
OK
|
||||
PASS: actual convoy motion thresholds, native-only proof boundaries, physical cargo/crew ownership and pre-launch runner restrictions
|
||||
PASS: actual filled rotated-footprint TraceBox and nine terrain/water samples; broad tree/cable bounds alone accepted, actual collision/nonfinite trace refused, nonzero sweep and ENTS/OCEAN flags, bounded diagnostic query and reset
|
||||
PASS: actual road interpolation/projection/orientation, six35m starts,200m forward tail, preserved bends, short/far/degenerate/nonfinite/overdense/hairpin refusal and deterministic candidate ties
|
||||
PASS: actual authenticated convoy dispatch, active/ready admission gates, canonical receipt, safe unconsumed preflight retry, pool refusal, retained partial failure and consumed no-retry
|
||||
PASS: actual native readiness exact membership, one-time per-vehicle ledger admission/reservation, partial readiness retry without duplicate records and one-pass56-item cargo allocation without replenishment
|
||||
PASS: actual per-vehicle readiness reasons, native agent/activity counts, intended-seat diagnostics, five-second throttle and read-only observation
|
||||
PASS: actual convoy dispatch validates before receipt consumption, consumes once before native mutation, retains all12 partial-failure paths, accepts the selected non-USSR enemy, and allocates exact19 bodies without capacity shrinking
|
||||
PASS: own memberless prefab disables automatic spawn before initialization; actual admission rejects populated, immediate or authored-slot templates before finite crew creation
|
||||
PASS: actual native seated-group arrival outside35m, native waypoint removal, idempotent advance, and convoy waypoint idempotence/arrival/deadline, missing/dead/captured/reparented crew refusal, failed native waypoint release, original vehicle/member preservation and no replacement
|
||||
PASS: actual convoy/team Release retains original active survivors and HQ identity, rejects takeover/terminal/unknown roster, preserves exact logistics quarantine and normal eligibility
|
||||
PASS: actual convoy receipt identity/one-use, shared60-body and128-formation caps, outstanding/native-ledger accounting, exact19-body composition, source reuse, mode/client guards and no consumed receipt refunds
|
||||
PASS: actual supply route cycling/arrival/stops, finite startup bounds, contact freshness, dismount hold, quiet interval, interrupted boarding, timeout and lost-truck release
|
||||
PASS: moving-convoy perception boundaries and scoped individual/group abandonment priorities
|
||||
PASS: actual supply startup reasons expose unchanged body budget/requirement, journal only failure changes, preserve retry cadence/caps and report the true final refusal
|
||||
PASS: actual combat start releases only exact intercept claims before owning route/orders; foreign claim defers combat takeover
|
||||
PASS: actual StartOne plus actual pool preserve unconsumed receipt retry, reject consumed replay, skip impossible new-budget surveys, retain equality, retry caps and journal dedup; no FPS claim
|
||||
PASS: actual admission seeds use exact installed loaded prefab, reject replacement/foreign/projectile paths and preserve bounded immutable identity
|
||||
PASS: actual primary-before-mounted one-shot batch, shared96/128 caps, physical snapshot headroom, original ownership, callback refusal and admission pinning
|
||||
PASS: actual bounded HQ query validates only existing original binding without admission or replacement lookup
|
||||
PASS: exact optional PMN4 startup, primary callback independence, original owner,128 capacity/callback limits, terminal no-retry and callback-proven freeze
|
||||
PASS: production convoy spacing, physical arrival, stable follower targets, supply ownership, leader promotion and bounded stuck recovery
|
||||
PASS: convoy combat keeps native manoeuvres for unchanged contacts and refreshes when contact moves
|
||||
PASS: native convoy proof excludes pre-departure movement and ends via explicit fixture reset, not prohibited player death
|
||||
PASS: boarding retries only after approach/seat progress stops, capped at three, with delayed native reissue and completed-team preservation
|
||||
PASS: slow only owned vehicles near fit dismounted infantry; ignore passengers, casualties and released teams/vehicles
|
||||
PASS: restore missing native driving registration only for healthy owned seated crews; preserve existing association and refuse invalid/released vehicles
|
||||
PASS: actual body observer enforces owned physical roster, local death-only candidates, native trace gating, budget/cadence/caps, immutable body-only snapshots, radio retries and confirmed suppression
|
||||
PASS: own initial roster, explicit retained corpse evidence, unconscious/missing/ref transfer guards, fixed strength, no replacement adoption, detached samples, surviving radio sender, immutable convoy root/ledger, mounted capture release and lost-root unknown
|
||||
PASS: delivered-only status snapshots, two missed check-ins/reply deadline, last reported coordinates, episode/replay guards, confirmed-loss idempotence, detached/validated mounted payload replacement 256-record cap and detached carried-mine radio delivery/chronology
|
||||
PASS: actual failed-retask deadline rollback plus assignment cancellation/release preserves radio ownership, native waypoint lifecycle and failed-order retry boundaries
|
||||
PASS: actual force ledger, finite casualties/identity/cap, routes/arrival/elevation/overshoot, server and physical guards, staggered 256-record simulated hour without world queries
|
||||
PASS: actual equipment coordinator registration dedupe, startup delay, five member scans, finite component retries, unregister/stop terminal behavior and 256-group bound
|
||||
PASS: actual percentage selection, native/preferred launcher preservation, failure/removal fallback, pending-operation quarantine, capture, finite two-mine grants and no replenishment
|
||||
PASS: automatic inventory one-shot roll, exact item ownership, finite compatible ammo, rejection, capture/stop, deletion, loot and late callback quarantine
|
||||
PASS: enabled/valid catalog eligibility, category restrictions, content filtering, GUID deduplication across factions and bounded catalogs
|
||||
PASS: actual engineer mine preparation/deadline, original custody/identity, once-only callback removal, cancellation quarantine, withdrawal/clearance, native refusal and owned-action cleanup; native execution untested
|
||||
PASS: actual road mine-site annulus/bend geometry, deterministic bounded candidates, server/query limits, and native ground/water/obstruction rejection; no runtime placement claim
|
||||
PASS: actual HQ mine admission, one site query per tick, maximum two active/32 retained jobs, delivered-only original post, once-per-unit refusal latch, 256-item ceiling and maintenance/teardown ownership
|
||||
PASS: actual objective heading/threat/approach rules, persistent doctrine weight, fresh-contact coverage invalidation, stale-sweep rejection and atomic radio retask ownership
|
||||
PASS: actual bounded delivered-negative coverage, replay/freshness/expiry/invalid guards, oldest eviction, small-circle and nearest-unswept selection, reserved-cell exclusion and cleanup
|
||||
PASS: actual HQ active/pending reservations, interruption, committed replacement, bounded coverage and unchanged force counts
|
||||
PASS: actual bounded pursuit roles/objectives/scores, stale search degradation, physical/busy/home guards, finite lifetime and two-bearing escape gap; no hidden player reads or spawning
|
||||
PASS: actual HQ memory rules/service preserve delivered snapshots, chronology, replay bounds, decay, motion limits, identity and negative-search revisions
|
||||
.......
|
||||
----------------------------------------------------------------------
|
||||
Ran 7 tests in 0.023s
|
||||
|
||||
OK
|
||||
PASS: actual session Tick + journal router delivers each grant completion and new HQ line once, without repeats on empty/unavailable feeds or unauthorized ticks
|
||||
.....
|
||||
----------------------------------------------------------------------
|
||||
Ran 5 tests in 0.378s
|
||||
|
||||
OK
|
||||
PASS: actual current-controller/admin/mode authorization, per-round anti-replay, token-rotation refusal, command/snapshot throttles and client scope; owner-only replies and player-tool cleanup wiring
|
||||
PASS: mission provider registration is idempotent, optional dispatch is ordered, status aggregates, journals preserve cursors, aliases stay with their addons and topic help is bounded
|
||||
PASS: actual menu command routing covers all fifteen settings, rejects unsupported keys, gates companions, and matches unique authored widgets
|
||||
PASS: explicit weapon retention buttons use the same ready/authorized server command path
|
||||
PASS: item editor, preset controls and dynamic item-row layout are structurally valid
|
||||
PASS: catalog requests use authenticated sessions, independent admin page selections, loaded-mod allowlisting, bounded pages and allowlisted item mutations
|
||||
PASS: actual item rules, shared-source conflicts, missing/returning mods, bulk reset, independent named presets, bounds, malformed data and failed-save state preservation
|
||||
PASS: actual summaries preserve queued/delivered meaning, clear counts and IDs; raw records, legacy display, cursor gaps and RPC limits remain intact
|
||||
PASS: actual admin body authorization, god restoration, vanilla item deposit, callback completion/failure, timeout quarantine and stop/revocation paths
|
||||
PASS: same-body revoke/regrant preserves pending insertions and timeout quarantine; new-body binding isolates late callbacks and never inherits god mode
|
||||
PASS: actual Intel commands distinguish status/start/failure, preserve authorization gate and reject malformed requests; spent ledger is idempotent, server-only and bounded
|
||||
PASS: actual reroll snapshot, authority, cancellation, ownership, accepted operations, quarantine, bounded work and stop methods; no native runtime claim
|
||||
PASS: production Intel feedback coalescing, rough secrecy, rate limit, bounded dedupe, local/remote delivery and round cleanup; native UI/RPC not exercised
|
||||
PASS: actual per-squad bounded report selector rotates visible targets, survives ordering/turnover, records only queued attempts, and preserves radio intake ordering
|
||||
PASS: actual action denial/clearing, server request failure, single submitted-result feedback, current rescuer identity and terminal/stop suppression; native UI/RPC pending
|
||||
PASS: actual physical denial precedence and transport-seat occupancy/reservation/access/lock checks
|
||||
PASS: actual initial admission exhaustion and ServerTick grace; new/pending/empty/controller/life guards, normal death preserved
|
||||
......................
|
||||
----------------------------------------------------------------------
|
||||
Ran 22 tests in 0.226s
|
||||
|
||||
OK
|
||||
......................
|
||||
----------------------------------------------------------------------
|
||||
Ran 22 tests in 22.895s
|
||||
|
||||
OK
|
||||
..................
|
||||
----------------------------------------------------------------------
|
||||
Ran 18 tests in 2.657s
|
||||
|
||||
OK
|
||||
.........
|
||||
----------------------------------------------------------------------
|
||||
Ran 9 tests in 2.055s
|
||||
|
||||
OK
|
||||
...............
|
||||
----------------------------------------------------------------------
|
||||
Ran 15 tests in 1.103s
|
||||
|
||||
OK
|
||||
PASS: actual field result/braking observer and Chat dispatch rules; one finite fixed candidate, deferred callback failure and guarded cleanup; no native measurement
|
||||
PASS: actual opt-in terrain audit records one fixed1681-point grid, preserves original survey refusal/success and exact no-allocation scope; malformed heights stop without retry; no native data acquired
|
||||
....
|
||||
----------------------------------------------------------------------
|
||||
Ran 4 tests in 0.573s
|
||||
|
||||
OK
|
||||
PASS: actual field PI/ramp/vector caps, antiwindup, braking hypothesis, immutable admission limits and segment bounds; no native physics tested
|
||||
PASS: actual100m whole-field survey sampling/4096cap, rotated coverage, all-node terrain and native trace refusal; original certificate lineage retained; no native qualification
|
||||
PASS: actual QualifyField preserves original identity/footprint/time, orders complete surveys before issuance, and refuses failed stages/moved body/expired or changed world; native boundaries faked
|
||||
PASS: WORKBENCH first-refusal diagnostics preserve ordinary predicates, query order/count/budget and opt-in scope; no native logging run
|
||||
PASS: actual field startup delegation, immutable lease/target, braking/settling/cancel transitions, early abort, callback scope, physical terminal policy and shared landing demand; native boundaries faked
|
||||
PASS: actual inherited airborne pre-CLIMB cancellation enables verified native autohover once, refuses failed enable and never writes after capture
|
||||
PASS: actual Core maintenance/default/tick/stop methods; empty/populated authority, terminal reentry, no tactical resume or new scheduler; native boundaries substituted
|
||||
PASS: actual finite registration, unique claims/tokens, retention/cancel/stop, readiness gating, capture/expiry retirement and bounded cross-kind order history and typed field/route construction; native boundaries faked
|
||||
PASS: actual WORKBENCH route observer returns exact retained route subtype and refuses absent/field controllers
|
||||
.....
|
||||
----------------------------------------------------------------------
|
||||
Ran 5 tests in 0.581s
|
||||
|
||||
OK
|
||||
......................
|
||||
----------------------------------------------------------------------
|
||||
Ran 22 tests in 0.156s
|
||||
|
||||
OK
|
||||
................
|
||||
----------------------------------------------------------------------
|
||||
Ran 16 tests in 9.269s
|
||||
|
||||
OK
|
||||
......
|
||||
----------------------------------------------------------------------
|
||||
Ran 6 tests in 0.016s
|
||||
|
||||
OK
|
||||
PASS: actual production bridge lookup, lease/world/mode/seat/capture guards, no allocation, native super ordering and read-only fixture hooks
|
||||
...............
|
||||
----------------------------------------------------------------------
|
||||
Ran 15 tests in 0.047s
|
||||
|
||||
OK
|
||||
.............
|
||||
----------------------------------------------------------------------
|
||||
Ran 13 tests in 4.402s
|
||||
|
||||
OK
|
||||
PASS: actual route gain/caps/braking, callback selection, old landing/cancellation control and matching read-only telemetry
|
||||
..............................
|
||||
----------------------------------------------------------------------
|
||||
Ran 30 tests in 0.376s
|
||||
|
||||
OK
|
||||
..............
|
||||
----------------------------------------------------------------------
|
||||
Ran 14 tests in 0.181s
|
||||
|
||||
OK
|
||||
.........
|
||||
----------------------------------------------------------------------
|
||||
Ran 9 tests in 0.101s
|
||||
|
||||
OK
|
||||
PASS: actual gate observer binds native registered action/context and current life, reads accumulated progress, refuses stale bindings and never revives outside original bounds
|
||||
PASS: actual read-only look observer methods enforce explicit scope, pinned native camera/body/identity, normalized pose, terminal cancellation and original sample/time bounds
|
||||
...........
|
||||
----------------------------------------------------------------------
|
||||
Ran 11 tests in 0.062s
|
||||
|
||||
OK
|
||||
.......................
|
||||
----------------------------------------------------------------------
|
||||
Ran 23 tests in 0.032s
|
||||
|
||||
OK
|
||||
PASS: actual initial WAITING task/guide copy, configured duration, unchanged interruption and rounded progress; native task assertion pending
|
||||
........
|
||||
----------------------------------------------------------------------
|
||||
Ran 8 tests in 0.010s
|
||||
|
||||
OK
|
||||
PASS: actual inventory adapter scope, grid placement, sole compatible host, refresh reuse, rollback, foreign hierarchy refusal and exactly-once native delegation; native compatibility pending
|
||||
.................................................................................
|
||||
----------------------------------------------------------------------
|
||||
Ran 81 tests in 0.481s
|
||||
|
||||
OK
|
||||
......
|
||||
----------------------------------------------------------------------
|
||||
Ran 6 tests in 0.064s
|
||||
|
||||
OK
|
||||
.................
|
||||
----------------------------------------------------------------------
|
||||
Ran 17 tests in 0.004s
|
||||
|
||||
OK
|
||||
...........
|
||||
----------------------------------------------------------------------
|
||||
Ran 11 tests in 0.067s
|
||||
|
||||
OK
|
||||
.........
|
||||
----------------------------------------------------------------------
|
||||
Ran 9 tests in 0.302s
|
||||
|
||||
OK
|
||||
PASS: actual observer nonce, admission pins, baseline/menu sequencing, snapshots, bounded clocks/samples, cleanup and one-attempt latch; native untested
|
||||
PASS: actual corridor caps/height/hold and whole-rectangle survey bounds, terrain refusal, native trace refusal and exact certificate lineage; native route unqualified
|
||||
PASS: actual route entry, fixed corridor/target, hover hold, cancellation landing freeze, shared lease/capture/deadline gate and unchanged native startup/tick sequence
|
||||
PASS: finite opt-in route fixture, original native input order, meaningful physical-arrival evidence and guarded cleanup; no native launch
|
||||
PASS: actual Chat route step result dispatch and scoped completion/cleanup wiring; six real early-parser refusal paths leave no prefix/profile output
|
||||
PASS: actual retained-service fixture entry, immutable controller/token/deadline/count bindings, observer getter and cleanup ownership ordering; native route validation pending
|
||||
PASS: actual measured cancellation gate, one-shot retained service Stop, no premature drain, immutable lease, fresh real controller readback and separate physical cancellation result policy
|
||||
PASS: actual rough-intel objective selection, exact precedence, source grid dimensions and unchanged other-phase dispatch; native UI pending
|
||||
.................................
|
||||
----------------------------------------------------------------------
|
||||
Ran 33 tests in 0.091s
|
||||
|
||||
OK
|
||||
...............
|
||||
----------------------------------------------------------------------
|
||||
Ran 15 tests in 0.028s
|
||||
|
||||
OK
|
||||
PASS: actual preparation status scope, persistent updates, spinner restoration, placeholder replacement/deletion/destruction, native observation persistence/timeout; rendering remains native-pending
|
||||
PASS: actual guide ownership and scoped timer cancellation, current-controller preservation/reacquisition, hint retry/dedup and observer boundaries; native UI not exercised
|
||||
PASS: actual native-end shutdown, existing terminal snapshots, reentrancy, unchanged native end data and stale preparation guards
|
||||
....
|
||||
----------------------------------------------------------------------
|
||||
Ran 4 tests in 1.219s
|
||||
|
||||
OK
|
||||
PASS: actual three-state result wording and UI refresh; stale winner IDs never manufacture escape
|
||||
...........
|
||||
----------------------------------------------------------------------
|
||||
Ran 11 tests in 0.105s
|
||||
|
||||
OK
|
||||
PASS: actual immutable target, bounded autohover demand, drift refusal, five-second hold and unchanged qualification/deadline
|
||||
PASS: actual admission attempt budget bounds blocked/missing prerequisites; preparation, spent life and async pending remain protected
|
||||
PASS: delayed admission retry retains controller ownership across same-account recycled connection IDs
|
||||
PASS: actual scene deadline overrides delayed native preload only for rejection; late completion cannot assign, ordinary verified admission preserved
|
||||
PASS: actual action permission matrix unchanged; useful phase/intel/consciousness denials, stale reason cleared, native door denial preserved; no native UI claim
|
||||
.....
|
||||
----------------------------------------------------------------------
|
||||
Ran 5 tests in 0.235s
|
||||
|
||||
OK
|
||||
PASS: actual aircraft cancellation/stage rules, ground-only completion, expiry/capture faults, input ownership and no physical shortcuts
|
||||
PASS: actual exact-instance single-use qualification, native-clock staleness/movement refusal, footprint bounds and ownership-safe ground/fault cleanup
|
||||
PASS: actual optional screenshot helper flag/identity/world/life revalidation, one queued attempt, delayed capture and mission/world cleanup; no native image/presentation claim
|
||||
PASS: actual yard diagnostic exact registered prison ownership, eight native-bound corner projections, no root-sibling escape, bounded duplicate/deep/wide hierarchy traversal; native projection/mask still unmeasured
|
||||
PASS: actual CurrentActor, SpawnPosition and observer-selection methods reject pending/unregistered/replaced/recycled/foreign/dead bodies; canonical selection/cycling, unconscious following and prison/geometry fallback preserved; native boundaries adapted
|
||||
PASS: actual native-map fixture ownership, cleanup and callback evidence fail closed; native execution remains separate
|
||||
PASS: actual shared-map callbacks bind exact map/config/root, preserve replacement UI/camera, defer focus, and remove only owned delayed work
|
||||
PASS: actual Intel/HQ methods and one-shot qualified readiness handoff deliver late actors once; duplicate readiness, accepted spawn, pickup/drop, death and original negative rolls do not mint assignments
|
||||
PASS: Workbench-only read accessor rejects unknown/nonserver identities; Chat observes all distinct ready crew before existing shell-depletion mutation
|
||||
PASS: actual native-event latch survives deselection, requires exact activity/order, detaches before cleanup, preserves failure/timeout and never infers completion from physical shot count
|
||||
.........
|
||||
----------------------------------------------------------------------
|
||||
Ran 9 tests in 0.100s
|
||||
|
||||
OK
|
||||
PASS: actual refresh-fixture gates pin immutable observations/actual downlinks/original activity, reject unaccepted or relabelled commits, stop queueing early and require all queued contacts claimed before replay
|
||||
..........................
|
||||
----------------------------------------------------------------------
|
||||
Ran 26 tests in 0.189s
|
||||
|
||||
OK
|
||||
PASS: actual refresh scheduler/delivery/cancellation/projectile methods reject interrupted or replayed reports, retain committed ownership, preserve immutable snapshots/aim geometry and isolate stopped or replaced orders; native radio/crew behavior remains separate
|
||||
.....................
|
||||
----------------------------------------------------------------------
|
||||
Ran 21 tests in 0.032s
|
||||
|
||||
OK
|
||||
...........
|
||||
----------------------------------------------------------------------
|
||||
Ran 11 tests in 0.034s
|
||||
|
||||
OK
|
||||
PASS: actual full-salvo gates require eight fresh committed physical launches, natural closure and exact finite inventory; reject partial/extra/replayed events
|
||||
PASS: actual reason/availability guidance and refresh matrix; no-target copy omits cycling, hint retry retained, existing command/admin paths unchanged; native UI pending
|
||||
PASS: actual client camera policy closes immediately on healing or mission end; late unconscious metadata cannot keep the view active
|
||||
PASS: actual input dispatch, one press per edge, menu-held suppression; actual revival camera handoff with editor close/retry, missing camera, replacement body and stopped round
|
||||
PASS: first-person pose survives missing samples without drift, follows body motion/rotation, resumes fresh pose and cannot leak between targets
|
||||
................usage: test_native_package.py [-h] [--project {core}] --session SESSION
|
||||
--compat COMPAT [--steam STEAM]
|
||||
[--timeout TIMEOUT]
|
||||
[--external-addons EXTERNAL_ADDONS]
|
||||
[--silent-workbench | --exit-after-init]
|
||||
[--continue-on-failure]
|
||||
test_native_package.py: error: argument --exit-after-init: not allowed with argument --silent-workbench
|
||||
................................
|
||||
----------------------------------------------------------------------
|
||||
Ran 48 tests in 12.080s
|
||||
|
||||
OK
|
||||
.......
|
||||
----------------------------------------------------------------------
|
||||
Ran 7 tests in 0.219s
|
||||
|
||||
OK
|
||||
..............
|
||||
----------------------------------------------------------------------
|
||||
Ran 14 tests in 2.265s
|
||||
|
||||
OK
|
||||
....
|
||||
----------------------------------------------------------------------
|
||||
Ran 4 tests in 0.281s
|
||||
|
||||
OK
|
||||
....
|
||||
----------------------------------------------------------------------
|
||||
Ran 4 tests in 0.059s
|
||||
|
||||
OK
|
||||
PASS: actual finite-courier age, token, exact ownership/callback and delivered-status admission reject stale, replayed, captured, canceled and ambiguous work
|
||||
PASS: actual native-gun ownership gate rejects player/foreign/replaced/dead occupant, faction change, missing seat and missing registry
|
||||
PASS: four-battery courier selection preserves pending trip/report/transfer identity, skips unavailable crews, and selects the next depleted battery without new stock
|
||||
PASS: 64 bounded distinct storage candidates; native gates, five-store barrier and finite-stock ownership retained; native placement pending
|
||||
PASS: 128 coarse+80 local refinement+169 airport grid candidates retain all native clearance gates; real terrain untested
|
||||
PASS: actual prison registration/crossing policies reject missing/duplicate/destroyed initial gate, incomplete ownership/scan, outside-only body, jumps and stale samples; physical exit independent of later gate damage
|
||||
PASS: actual prison door execution blocks direct AI/UI bypass while locked and delegates unchanged native action once after unlock and permission approval
|
||||
..................
|
||||
----------------------------------------------------------------------
|
||||
Ran 18 tests in 1.953s
|
||||
|
||||
OK
|
||||
.....
|
||||
----------------------------------------------------------------------
|
||||
Ran 5 tests in 1.082s
|
||||
|
||||
OK
|
||||
PASS: native local-admission snapshot rejects changed life/key/body/participant/pending state; authentication is not simulated
|
||||
PASS: actual mortar report ledger prevents duplicate response after cancellation, expiry and interleaved reports; native scheduler separate
|
||||
.........
|
||||
----------------------------------------------------------------------
|
||||
Ran 9 tests in 0.012s
|
||||
|
||||
OK
|
||||
....
|
||||
----------------------------------------------------------------------
|
||||
Ran 4 tests in 0.146s
|
||||
|
||||
OK
|
||||
PASS: actual aim renewal requires fresh same-report solve and original-command equivalence; native firing untested
|
||||
........
|
||||
----------------------------------------------------------------------
|
||||
Ran 8 tests in 0.015s
|
||||
|
||||
OK
|
||||
PASS: actual startup/generation/ownership/timeout/callback policies and native-counter wait; integration preserves one-shot allocation and pending-crew cancellation
|
||||
PASS: rapid mortar cycle counts eight physical rounds once and waits for native completion without a ninth aim or shell
|
||||
PASS: actual probe binds native events to fresh immutable commits, permits delayed emission, rejects unrelated expiry closure and premature stop
|
||||
PASS: actual noSplash Load body requires both explicit diagnostic and noSplash, preserves vanilla otherwise, and handles repeated Load/reload
|
||||
PASS: actual fixture method permits one native resilience operation and restores either prior protection state, including failure paths
|
||||
PASS: actual safe-exit boundary policy, native action/door selection, stop/cache cleanup, separate old mechanism and opt-in runner contracts
|
||||
PASS: actual one-shot deadline/stability policies and preparation ownership, fuel, cleanup integration contracts
|
||||
PASS: pending-only cancellation retires copied instance intent, preserves physical/control actors, and stops once
|
||||
PASS: actual short-cast hit, upward support normal, no self support and no dynamic support
|
||||
PASS: actual committed-ticket structure preserves reservation after death, rejects missing bodies/seats and ends at observation expiry
|
||||
PASS: actual pending callback expires an empty-server reservation once, preserves its full lease, and cancels on completion/Stop
|
||||
PASS: rescue lease expiry/reordering, fresh five-second settling, independent rescue ranges and grounded upright aircraft motion boundaries
|
||||
PASS: requested guard population requires complete physical conscious membership before deadline
|
||||
......................................
|
||||
----------------------------------------------------------------------
|
||||
Ran 38 tests in 0.021s
|
||||
|
||||
OK
|
||||
..........
|
||||
----------------------------------------------------------------------
|
||||
Ran 10 tests in 0.002s
|
||||
|
||||
OK
|
||||
.....
|
||||
----------------------------------------------------------------------
|
||||
Ran 5 tests in 0.865s
|
||||
|
||||
OK
|
||||
PASS: authored radio/optional-site bounds and physical horizontal/vertical boundaries
|
||||
...
|
||||
----------------------------------------------------------------------
|
||||
Ran 3 tests in 0.001s
|
||||
|
||||
OK
|
||||
PASS: all phase transitions, terminal states, defence boundaries, stale/future observations, intel discovery
|
||||
PASS: Core document-discovery alias, companion-only alias exclusion, strict integer parsing and setting boundaries
|
||||
PASS: player admission phase/life/identity/controller matrix, preload displacement limits, pending reservation overlap and owner exclusion
|
||||
PASS: actual gate and pre-inventory document guards reject noncanonical/faction/life/disconnect/server mismatches; canonical reconnect and unconscious carried intel preserved
|
||||
PASS: WB-only native fault injection restores both body mutations before checks/returns and requires eligible held-document negative/positive proof; runtime pending
|
||||
PASS: actual discovery coordinates disclose stable 500m cell center until identified, then exact point, including negative/boundary coordinates
|
||||
PASS: actual local admission context/selection/latch, same-round late identity, replacement/mode failure, fresh round and untouched network path
|
||||
PASS: prison search packs 16 unique 3m-spaced inside-wall centers, prioritizes inner/edge positions and never bypasses clearance
|
||||
PASS: physical body occupancy rejects overlap, permits diagonal neighboring slots, and excludes own body and unrelated broad-phase bounds
|
||||
PASS: recovery state excludes dead bodies, preserves reservations, requires controlled rescuers and distinguishes empty/all-dead teams
|
||||
PASS: authority-scoped slow recovery, bleeding pauses resilience, medicine unaffected, player-only lethal damage cap without healing
|
||||
PASS: bleeding severity survives low health, retains armour, causes no knockout and restores the actual damage/blood-loss cap
|
||||
PASS: viable passive recovery survives native delay; bleeding, disabled/full effects and low-blood resilience do not block defeat
|
||||
PASS: every minute round-trips, malformed clocks rejected, defaults limited to added keys, progression bounds and day duration aligned
|
||||
PASS: native settings policy matches calendar boundaries 1900-2050, date/profile parsing is strict, authored defaults preserve old worlds
|
||||
PASS: view-distance bounds, default, save failures, command parsing, temporary ownership, server/platform limits and lifecycle wiring
|
||||
.....
|
||||
----------------------------------------------------------------------
|
||||
Ran 5 tests in 0.542s
|
||||
|
||||
OK
|
||||
.
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.319s
|
||||
|
||||
OK
|
||||
PASS: radio authorization/discovery/phase matrix, exact distance limits, defender handover, interruption, empty samples, clock regression and duration boundary
|
||||
PASS: actual radio feedback policy, initial wait, interruption persistence, resumption and terminal/new-round reset
|
||||
...............
|
||||
----------------------------------------------------------------------
|
||||
Ran 15 tests in 2.074s
|
||||
|
||||
OK
|
||||
..............................................
|
||||
----------------------------------------------------------------------
|
||||
Ran 46 tests in 0.803s
|
||||
|
||||
OK
|
||||
PASS: legacy values preserved, unsupported fields reported, invalid data rejected, existing files protected, runtime bounds aligned
|
||||
PASS: failed initial settings promotion leaves defaults writable after restart; prior/fallback saves survive copy, readback and mismatch faults
|
||||
PASS: real-carrier shortest-hop paths, 500m boundary, three-hop cap, no-manpack failure, three-second originating-call interruption, unchanged downlink hop timing, completed-hop survival, interrupted final uplink, radio loss, capture, motion, timeout, reset and network bounds
|
||||
PASS: migrated aircraft availability policy (controller migration still pending)
|
||||
PASS: migrated salvo offsets are deterministic, spread and bounded (native firing and complete salvo qualification are separate)
|
||||
PASS: 35 prefabs have unique saved root entity IDs
|
||||
PASS: 299 original script entries tracked across 18 unfinished responsibilities
|
||||
PASS: 1 isolated addons, acyclic dependencies, 58 unique resources, exact vanilla/authorized external references
|
||||
All repository checks passed. Native compilation and multiplayer tests are separate requirements.
|
||||
Suite exit 0 programs 294 unchanged True
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
// Detached radio order. Only its issuer chooses Position and the fixed deadline.
|
||||
class ERR_ConvoyInterceptOrder
|
||||
{
|
||||
int Token;
|
||||
SCR_AIGroup Group;
|
||||
string SourceId;
|
||||
int VehicleIndex;
|
||||
vector Position;
|
||||
float IssuedAt;
|
||||
float ExpiresAt;
|
||||
float ObservedAt;
|
||||
int BeliefId;
|
||||
int BeliefRevision;
|
||||
int ReportId;
|
||||
}
|
||||
|
||||
// Delivered ground history only; no target entity reads, force creation or orders.
|
||||
class ERR_HQMountedInterceptRules
|
||||
{
|
||||
static bool Eligible(ERR_HQBelief belief, float now)
|
||||
{
|
||||
if (!ERR_HQRoadIntercept.Eligible(belief, now)) return false;
|
||||
if (belief.BeliefId <= 0 || belief.BeliefRevision <= 0 || belief.ObservedAt < 0) return false;
|
||||
return ERR_HQPursuitRules.PositionValid(belief.Position) && ERR_HQPursuitRules.PositionValid(belief.PredictedPosition);
|
||||
}
|
||||
static bool Current(ERR_ConvoyInterceptOrder order, ERR_HQBelief belief, float now)
|
||||
{
|
||||
if (!order || !Eligible(belief, now) || !order.Group || order.SourceId.IsEmpty()) return false;
|
||||
if (order.Token < 400001 || order.Token > 400256) return false;
|
||||
if (order.VehicleIndex != 0 && order.VehicleIndex != 3 && order.VehicleIndex != 5) return false;
|
||||
if (order.BeliefId != belief.BeliefId || order.BeliefRevision != belief.BeliefRevision
|
||||
|| order.ReportId != belief.MessageId || order.ObservedAt != belief.ObservedAt) return false;
|
||||
if (!ERR_ForceRules.Finite(order.IssuedAt) || !ERR_ForceRules.Finite(order.ExpiresAt)) return false;
|
||||
if (order.IssuedAt < belief.LastDeliveredAt || now < order.IssuedAt || now - order.IssuedAt > 30000) return false;
|
||||
if (order.ExpiresAt <= order.IssuedAt || order.ExpiresAt > order.ObservedAt + 120000 || now >= order.ExpiresAt) return false;
|
||||
return ERR_HQPursuitRules.PositionValid(order.Position);
|
||||
}
|
||||
static bool Ahead(vector origin, vector velocity, vector point)
|
||||
{
|
||||
if (!ERR_ForceRules.FiniteVector(origin) || !ERR_ForceRules.FiniteVector(point) || !ERR_HQPursuitRules.Heading(velocity, true)) return false;
|
||||
vector delta = point - origin; delta[1] = 0; velocity[1] = 0;
|
||||
float distance = delta.Length();
|
||||
return distance >= 300 && distance <= 900 && vector.Dot(delta.Normalized(), velocity.Normalized()) >= 0.8;
|
||||
}
|
||||
static bool FromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, out vector result, out float score, out float heading)
|
||||
{
|
||||
result = vector.Zero; score = 1e20; heading = 0;
|
||||
if (!points || points.Count() < 2 || points.Count() > 1024 || !ERR_ForceRules.FiniteVector(seed)) return false;
|
||||
foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
|
||||
bool found;
|
||||
for (int i = 1; i < points.Count(); i++)
|
||||
{
|
||||
vector delta = points[i] - points[i - 1]; delta[1] = 0;
|
||||
float length = delta.Length();
|
||||
if (length < 0.01) continue;
|
||||
vector offset = seed - points[i - 1]; offset[1] = 0;
|
||||
float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), 0, 1);
|
||||
vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
|
||||
float distance = vector.DistanceXZ(seed, candidate);
|
||||
if (distance > 100 || !Ahead(origin, velocity, candidate) || !ERR_HQRoadIntercept.Unreserved(candidate, reserved)) continue;
|
||||
if (!found || ERR_HQRoadIntercept.Better(distance, candidate, score, result)) { result = candidate; score = distance; heading = Math.Atan2(delta[0], delta[2]) * Math.RAD2DEG; found = true; }
|
||||
}
|
||||
return found;
|
||||
}
|
||||
static bool Select(ERR_HQBelief belief, float now, array<vector> reserved, out vector result)
|
||||
{
|
||||
result = vector.Zero;
|
||||
if (!Replication.IsServer() || !Eligible(belief, now) || !reserved || reserved.Count() > 128) return false;
|
||||
if (!GetGame() || !GetGame().GetWorld()) return false;
|
||||
vector velocity = belief.Velocity; velocity[1] = 0;
|
||||
vector seed = belief.PredictedPosition + velocity.Normalized() * Math.Clamp(velocity.Length() * 60, 300, 900);
|
||||
if (!ERR_HQPursuitRules.PositionValid(seed)) return false;
|
||||
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (!world || !world.GetRoadNetworkManager()) return false;
|
||||
array<BaseRoad> roads = {};
|
||||
world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
|
||||
if (roads.Count() > 128) return false;
|
||||
int segments;
|
||||
bool found;
|
||||
vector best;
|
||||
float bestScore = 1e20;
|
||||
float bestHeading;
|
||||
foreach (BaseRoad road : roads)
|
||||
{
|
||||
if (!road || !ERR_ForceRules.Finite(road.GetWidth()) || road.GetWidth() < 6) continue;
|
||||
array<vector> points = {}; road.GetPoints(points);
|
||||
if (points.Count() < 2 || points.Count() > 1024) continue;
|
||||
segments += points.Count() - 1;
|
||||
if (segments > 4096) return false;
|
||||
vector candidate;
|
||||
float score, heading;
|
||||
if (!FromPolyline(points, belief.PredictedPosition, velocity, seed, reserved, candidate, score, heading)) continue;
|
||||
if (!found || ERR_HQRoadIntercept.Better(score, candidate, bestScore, best))
|
||||
{ best = candidate; bestScore = score; bestHeading = heading; found = true; }
|
||||
}
|
||||
if (!found) return false;
|
||||
vector grounded = ERR_Actors.Ground(best);
|
||||
// Never convert an elevated bridge point into terrain beneath the road.
|
||||
if (!ERR_HQPursuitRules.PositionValid(grounded) || Math.AbsFloat(grounded[1] - best[1]) > 2) return false;
|
||||
ERR_SettlementSafety settlements = new ERR_SettlementSafety();
|
||||
if (!settlements.OutsideSettlements(grounded)) return false;
|
||||
// Existing conservative vehicle footprint: nine terrain/water samples,
|
||||
// filled collision volume and segment-aligned heading; no scenery changes.
|
||||
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
|
||||
if (!placement.Clear(grounded, bestHeading)) return false;
|
||||
result = grounded;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Keep both escorts on one real polyline. Horizontal arc length determines
|
||||
// placement; interpolation retains each road segment's height and heading.
|
||||
static bool PairFromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, out vector first, out vector second, out float score, out float firstHeading, out float secondHeading)
|
||||
{
|
||||
first = vector.Zero; second = vector.Zero; score = 1e20; firstHeading = 0; secondHeading = 0;
|
||||
if (!points || points.Count() < 2 || points.Count() > 1024 || !ERR_ForceRules.FiniteVector(seed)) return false;
|
||||
if (!ERR_ForceRules.FiniteVector(origin) || !ERR_HQPursuitRules.Heading(velocity, true) || !reserved || reserved.Count() > 128) return false;
|
||||
foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
|
||||
velocity[1] = 0;
|
||||
float total; float anchor; float nearest = 1e20;
|
||||
float along;
|
||||
vector projected;
|
||||
bool found;
|
||||
for (int i = 1; i < points.Count(); i++)
|
||||
{
|
||||
vector delta = points[i] - points[i - 1]; delta[1] = 0;
|
||||
float length = delta.Length();
|
||||
if (!ERR_ForceRules.Finite(length)) return false;
|
||||
if (length < 0.01) continue;
|
||||
vector offset = seed - points[i - 1]; offset[1] = 0;
|
||||
float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), 0, 1);
|
||||
vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
|
||||
float distance = vector.DistanceXZ(seed, candidate);
|
||||
if (!found || ERR_HQRoadIntercept.Better(distance, candidate, nearest, projected))
|
||||
{
|
||||
projected = candidate; nearest = distance; anchor = total + length * fraction;
|
||||
along = vector.Dot(delta.Normalized(), velocity.Normalized()); found = true;
|
||||
}
|
||||
total += length;
|
||||
if (!ERR_ForceRules.Finite(total)) return false;
|
||||
}
|
||||
if (!found || nearest > 100 || total < 40) return false;
|
||||
float span = Math.Clamp(total, 40, 60);
|
||||
float start = anchor - span * 0.5;
|
||||
// Longitudinal roads extend the pair ahead of the projection, so the first
|
||||
// point at the 300m planning boundary is not shifted behind that boundary.
|
||||
if (along >= 0.25) start = anchor;
|
||||
else if (along <= -0.25) start = anchor - span;
|
||||
start = Math.Clamp(start, 0, total - span);
|
||||
float finish = start + span;
|
||||
float travelled;
|
||||
vector a, b;
|
||||
float aHeading; float bHeading;
|
||||
bool haveFirst; bool haveSecond;
|
||||
for (int j = 1; j < points.Count(); j++)
|
||||
{
|
||||
vector segment = points[j] - points[j - 1]; segment[1] = 0;
|
||||
float segmentLength = segment.Length();
|
||||
if (segmentLength < 0.01) continue;
|
||||
if (!haveFirst && start <= travelled + segmentLength)
|
||||
{
|
||||
a = vector.Lerp(points[j - 1], points[j], Math.Clamp((start - travelled) / segmentLength, 0, 1));
|
||||
aHeading = Math.Atan2(segment[0], segment[2]) * Math.RAD2DEG; haveFirst = true;
|
||||
}
|
||||
if (!haveSecond && finish <= travelled + segmentLength)
|
||||
{
|
||||
b = vector.Lerp(points[j - 1], points[j], Math.Clamp((finish - travelled) / segmentLength, 0, 1));
|
||||
bHeading = Math.Atan2(segment[0], segment[2]) * Math.RAD2DEG; haveSecond = true;
|
||||
}
|
||||
if (haveFirst && haveSecond) break;
|
||||
travelled += segmentLength;
|
||||
}
|
||||
if (!haveFirst || !haveSecond || vector.DistanceXZ(a, b) < 40) return false;
|
||||
if (vector.DistanceXZ(seed, a) > 100 || vector.DistanceXZ(seed, b) > 100) return false;
|
||||
if (!Ahead(origin, velocity, a) || !Ahead(origin, velocity, b)) return false;
|
||||
if (!ERR_HQRoadIntercept.Unreserved(a, reserved) || !ERR_HQRoadIntercept.Unreserved(b, reserved)) return false;
|
||||
// Canonical order does not depend on native road enumeration direction.
|
||||
if (ERR_HQRoadIntercept.Better(0, b, 0, a))
|
||||
{
|
||||
vector swap = a; a = b; b = swap;
|
||||
float swapHeading = aHeading; aHeading = bHeading; bHeading = swapHeading;
|
||||
}
|
||||
first = a; second = b; score = nearest; firstHeading = aHeading; secondHeading = bHeading;
|
||||
return true;
|
||||
}
|
||||
static bool SelectPair(ERR_HQBelief belief, float now, array<vector> reserved, out vector first, out vector second)
|
||||
{
|
||||
first = vector.Zero; second = vector.Zero;
|
||||
if (!Replication.IsServer() || !Eligible(belief, now) || !reserved || reserved.Count() > 128) return false;
|
||||
if (!GetGame() || !GetGame().GetWorld()) return false;
|
||||
vector velocity = belief.Velocity; velocity[1] = 0;
|
||||
vector seed = belief.PredictedPosition + velocity.Normalized() * Math.Clamp(velocity.Length() * 60, 300, 900);
|
||||
if (!ERR_HQPursuitRules.PositionValid(seed)) return false;
|
||||
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (!world || !world.GetRoadNetworkManager()) return false;
|
||||
array<BaseRoad> roads = {};
|
||||
world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
|
||||
if (roads.Count() > 128) return false;
|
||||
int segments;
|
||||
bool found;
|
||||
vector bestFirst, bestSecond;
|
||||
float bestScore = 1e20;
|
||||
float bestFirstHeading; float bestSecondHeading;
|
||||
foreach (BaseRoad road : roads)
|
||||
{
|
||||
if (!road || !ERR_ForceRules.Finite(road.GetWidth()) || road.GetWidth() < 6) continue;
|
||||
array<vector> points = {}; road.GetPoints(points);
|
||||
if (points.Count() < 2 || points.Count() > 1024) continue;
|
||||
segments += points.Count() - 1;
|
||||
if (segments > 4096) return false;
|
||||
vector a, b;
|
||||
float score; float aHeading; float bHeading;
|
||||
if (!PairFromPolyline(points, belief.PredictedPosition, velocity, seed, reserved, a, b, score, aHeading, bHeading)) continue;
|
||||
if (!found || ERR_HQRoadIntercept.Better(score, a, bestScore, bestFirst))
|
||||
{ bestFirst = a; bestSecond = b; bestScore = score; bestFirstHeading = aHeading; bestSecondHeading = bHeading; found = true; }
|
||||
}
|
||||
if (!found) return false;
|
||||
vector groundedFirst = ERR_Actors.Ground(bestFirst);
|
||||
vector groundedSecond = ERR_Actors.Ground(bestSecond);
|
||||
if (!ERR_HQPursuitRules.PositionValid(groundedFirst) || !ERR_HQPursuitRules.PositionValid(groundedSecond)) return false;
|
||||
if (Math.AbsFloat(groundedFirst[1] - bestFirst[1]) > 2 || Math.AbsFloat(groundedSecond[1] - bestSecond[1]) > 2) return false;
|
||||
if (vector.DistanceXZ(groundedFirst, groundedSecond) < 40) return false;
|
||||
ERR_SettlementSafety settlements = new ERR_SettlementSafety();
|
||||
if (!settlements.OutsideSettlements(groundedFirst) || !settlements.OutsideSettlements(groundedSecond)) return false;
|
||||
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
|
||||
if (!placement.Clear(groundedFirst, bestFirstHeading) || !placement.Clear(groundedSecond, bestSecondHeading)) return false;
|
||||
first = groundedFirst; second = groundedSecond;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
PASS: git apply --check
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
// Detached radio order. Only its issuer chooses Position and the fixed deadline.
|
||||
class ERR_ConvoyInterceptOrder
|
||||
{
|
||||
int Token;
|
||||
SCR_AIGroup Group;
|
||||
string SourceId;
|
||||
int VehicleIndex;
|
||||
vector Position;
|
||||
float IssuedAt;
|
||||
float ExpiresAt;
|
||||
float ObservedAt;
|
||||
int BeliefId;
|
||||
int BeliefRevision;
|
||||
int ReportId;
|
||||
}
|
||||
|
||||
// Delivered ground history only; no target entity reads, force creation or orders.
|
||||
class ERR_HQMountedInterceptRules
|
||||
{
|
||||
static bool Eligible(ERR_HQBelief belief, float now)
|
||||
{
|
||||
if (!ERR_HQRoadIntercept.Eligible(belief, now)) return false;
|
||||
if (belief.BeliefId <= 0 || belief.BeliefRevision <= 0 || belief.ObservedAt < 0) return false;
|
||||
return ERR_HQPursuitRules.PositionValid(belief.Position) && ERR_HQPursuitRules.PositionValid(belief.PredictedPosition);
|
||||
}
|
||||
static bool Current(ERR_ConvoyInterceptOrder order, ERR_HQBelief belief, float now)
|
||||
{
|
||||
if (!order || !Eligible(belief, now) || !order.Group || order.SourceId.IsEmpty()) return false;
|
||||
if (order.Token < 400001 || order.Token > 400256) return false;
|
||||
if (order.VehicleIndex != 0 && order.VehicleIndex != 3 && order.VehicleIndex != 5) return false;
|
||||
if (order.BeliefId != belief.BeliefId || order.BeliefRevision != belief.BeliefRevision
|
||||
|| order.ReportId != belief.MessageId || order.ObservedAt != belief.ObservedAt) return false;
|
||||
if (!ERR_ForceRules.Finite(order.IssuedAt) || !ERR_ForceRules.Finite(order.ExpiresAt)) return false;
|
||||
if (order.IssuedAt < belief.LastDeliveredAt || now < order.IssuedAt || now - order.IssuedAt > 30000) return false;
|
||||
if (order.ExpiresAt <= order.IssuedAt || order.ExpiresAt > order.ObservedAt + 120000 || now >= order.ExpiresAt) return false;
|
||||
return ERR_HQPursuitRules.PositionValid(order.Position);
|
||||
}
|
||||
static bool Ahead(vector origin, vector velocity, vector point)
|
||||
{
|
||||
if (!ERR_ForceRules.FiniteVector(origin) || !ERR_ForceRules.FiniteVector(point) || !ERR_HQPursuitRules.Heading(velocity, true)) return false;
|
||||
vector delta = point - origin; delta[1] = 0; velocity[1] = 0;
|
||||
float distance = delta.Length();
|
||||
return distance >= 300 && distance <= 900 && vector.Dot(delta.Normalized(), velocity.Normalized()) >= 0.8;
|
||||
}
|
||||
static bool FromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, out vector result, out float score, out float heading)
|
||||
{
|
||||
result = vector.Zero; score = 1e20; heading = 0;
|
||||
if (!points || points.Count() < 2 || points.Count() > 1024 || !ERR_ForceRules.FiniteVector(seed)) return false;
|
||||
foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
|
||||
bool found;
|
||||
for (int i = 1; i < points.Count(); i++)
|
||||
{
|
||||
vector delta = points[i] - points[i - 1]; delta[1] = 0;
|
||||
float length = delta.Length();
|
||||
if (length < 0.01) continue;
|
||||
vector offset = seed - points[i - 1]; offset[1] = 0;
|
||||
float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), 0, 1);
|
||||
vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
|
||||
float distance = vector.DistanceXZ(seed, candidate);
|
||||
if (distance > 100 || !Ahead(origin, velocity, candidate) || !ERR_HQRoadIntercept.Unreserved(candidate, reserved)) continue;
|
||||
if (!found || ERR_HQRoadIntercept.Better(distance, candidate, score, result)) { result = candidate; score = distance; heading = Math.Atan2(delta[0], delta[2]) * Math.RAD2DEG; found = true; }
|
||||
}
|
||||
return found;
|
||||
}
|
||||
static bool Select(ERR_HQBelief belief, float now, array<vector> reserved, out vector result)
|
||||
{
|
||||
result = vector.Zero;
|
||||
if (!Replication.IsServer() || !Eligible(belief, now) || !reserved || reserved.Count() > 128) return false;
|
||||
if (!GetGame() || !GetGame().GetWorld()) return false;
|
||||
vector velocity = belief.Velocity; velocity[1] = 0;
|
||||
vector seed = belief.PredictedPosition + velocity.Normalized() * Math.Clamp(velocity.Length() * 60, 300, 900);
|
||||
if (!ERR_HQPursuitRules.PositionValid(seed)) return false;
|
||||
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (!world || !world.GetRoadNetworkManager()) return false;
|
||||
array<BaseRoad> roads = {};
|
||||
world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
|
||||
if (roads.Count() > 128) return false;
|
||||
int segments;
|
||||
bool found;
|
||||
vector best;
|
||||
float bestScore = 1e20;
|
||||
float bestHeading;
|
||||
foreach (BaseRoad road : roads)
|
||||
{
|
||||
if (!road || !ERR_ForceRules.Finite(road.GetWidth()) || road.GetWidth() < 6) continue;
|
||||
array<vector> points = {}; road.GetPoints(points);
|
||||
if (points.Count() < 2 || points.Count() > 1024) continue;
|
||||
segments += points.Count() - 1;
|
||||
if (segments > 4096) return false;
|
||||
vector candidate;
|
||||
float score, heading;
|
||||
if (!FromPolyline(points, belief.PredictedPosition, velocity, seed, reserved, candidate, score, heading)) continue;
|
||||
if (!found || ERR_HQRoadIntercept.Better(score, candidate, bestScore, best))
|
||||
{ best = candidate; bestScore = score; bestHeading = heading; found = true; }
|
||||
}
|
||||
if (!found) return false;
|
||||
vector grounded = ERR_Actors.Ground(best);
|
||||
// Never convert an elevated bridge point into terrain beneath the road.
|
||||
if (!ERR_HQPursuitRules.PositionValid(grounded) || Math.AbsFloat(grounded[1] - best[1]) > 2) return false;
|
||||
ERR_SettlementSafety settlements = new ERR_SettlementSafety();
|
||||
if (!settlements.OutsideSettlements(grounded)) return false;
|
||||
// Existing conservative vehicle footprint: nine terrain/water samples,
|
||||
// filled collision volume and segment-aligned heading; no scenery changes.
|
||||
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
|
||||
if (!placement.Clear(grounded, bestHeading)) return false;
|
||||
result = grounded;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
fatal: path 'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c' exists on disk, but not in the index
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
"""Actual delivered-memory road selector, geometry and native-query boundaries."""
|
||||
from pathlib import Path
|
||||
import os,re
|
||||
from policy_harness import ROOT,body,run
|
||||
p=Path(os.environ.get('ERR_MOUNTED_INTERCEPT_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c'))
|
||||
source=p.read_text()
|
||||
base=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQRoadIntercept.c'
|
||||
source='class ERR_HQRoadIntercept\n{\n'+'\n'.join(body(base,x) for x in ('static bool Eligible(', 'static bool Unreserved(', 'static bool Better('))+'\n}\n'+source
|
||||
for forbidden in ('GetPlayer','GetControlledEntity','GetOrigin','Spawn(', 'AddWaypoint','SetOrigin','CallLater'):
|
||||
assert forbidden not in source,forbidden
|
||||
rules='\n'.join(body(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c', x) for x in ('static bool Finite(', 'static bool PositionValid(', 'static bool Actionable(', 'static bool Heading(', 'static ERR_HQPursuitRole Role(', 'static vector Direction(', 'static vector Objective('))
|
||||
rules=rules.replace('Math.','Math::').replace('vector.','vector::').replace('ERR_HQPursuitRole.','ERR_HQPursuitRole::').replace('vector offset;','vector offset{};')
|
||||
s=re.sub(r'//[^\n]*','',source).replace('class ERR_HQRoadIntercept','struct ERR_HQRoadIntercept').replace('class ERR_HQMountedInterceptRules','struct ERR_HQMountedInterceptRules').replace('class ERR_ConvoyInterceptOrder','struct ERR_ConvoyInterceptOrder')
|
||||
s=s.replace('out vector','vector&').replace('out float','float&').replace('array<vector>','Array<vector>').replace('array<BaseRoad>','Array<BaseRoad*>').replace('foreach (','for (')
|
||||
s=s.replace('SCR_AIWorld.Cast(GetGame().GetAIWorld())','GetGame()->GetAIWorld()')
|
||||
for name in ('ERR_HQBelief','SCR_AIWorld','BaseRoad','TraceBox','SCR_AIGroup','ERR_ConvoyInterceptOrder','ERR_SettlementSafety','ERR_ConvoyPlacement'):
|
||||
s=re.sub(r'\b'+name+r' (\w+)',name+r'* \1',s)
|
||||
for name in ('belief','world','road','body','order','settlements','placement'):
|
||||
s=s.replace(name+'.',name+'->')
|
||||
s=s.replace('GetGame().','GetGame()->').replace('GetRoadNetworkManager().','GetRoadNetworkManager()->').replace('GetWorld().','GetWorld()->')
|
||||
for name in ('ERR_ForceRules','ERR_HQPursuitRules','ERR_HQPursuitRole','ERR_HQMemoryKind','vector','Math','ERR_Actors','ChimeraWorldUtils','TraceFlags','Replication','ERR_HQRoadIntercept'):
|
||||
s=s.replace(name+'.',name+'::')
|
||||
s=s.replace('null','nullptr').replace('string SourceId','std::string SourceId').replace('.IsEmpty()', '.empty()')
|
||||
s=re.sub(r'\b(bool|int|float) (\w+);',r'\1 \2{};',s).replace('float score, heading;', 'float score=0, heading=0;').replace('\n}\n','\n};\n')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
enum class ERR_HQPursuitRole{SEARCH,INTERCEPT,CONTAIN};enum class ERR_HQMemoryKind{SIGHTING,DISTRESS,BODY};
|
||||
struct SCR_AIGroup{};
|
||||
struct Math{static constexpr float RAD2DEG=57.2957795131f;static float Atan2(float x,float y){return std::atan2(x,y);}static float Clamp(float x,float a,float b){return std::clamp(x,a,b);}static float AbsFloat(float x){return std::abs(x);}};
|
||||
struct vector{float x=0,y=0,z=0;vector()=default;vector(float a,float b,float c):x(a),y(b),z(c){}vector(const char*s){sscanf(s,"%f %f %f",&x,&y,&z);}float&operator[](int i){return i==0?x:i==1?y:z;}float operator[](int i)const{return i==0?x:i==1?y:z;}
|
||||
vector operator+(vector b)const{return{x+b.x,y+b.y,z+b.z};}vector operator-(vector b)const{return{x-b.x,y-b.y,z-b.z};}vector operator*(float n)const{return{x*n,y*n,z*n};}bool operator==(vector b)const{return x==b.x&&y==b.y&&z==b.z;}
|
||||
float Length()const{return std::sqrt(x*x+y*y+z*z);}vector Normalized()const{float n=Length();return{x/n,y/n,z/n};}static float Dot(vector a,vector b){return a.x*b.x+a.y*b.y+a.z*b.z;}static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}static vector Lerp(vector a,vector b,float n){return a+(b-a)*n;}static const vector Zero;};
|
||||
const vector vector::Zero{};vector Vector(float a,float b,float c){return{a,b,c};}
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;using std::vector<T>::operator=;int Count(){return this->size();}void Insert(T v){this->push_back(v);}explicit operator bool(){return true;}};
|
||||
struct ERR_ForceRules{static bool Finite(float n){return std::isfinite(n);}static bool FiniteVector(vector p){return Finite(p.x)&&Finite(p.y)&&Finite(p.z);}};
|
||||
struct ERR_HQPursuitRules{''' + rules + r'''};
|
||||
struct ERR_HQBelief{ERR_HQMemoryKind Kind=ERR_HQMemoryKind::SIGHTING;int SampleCount=2,MessageId=1,BeliefId=3,BeliefRevision=2;vector Position={1000,10,1000};float ObservedAt=5000,LastDeliveredAt=7000,Confidence=.9,Radius=40;std::string TravelMode="on foot";vector PredictedPosition={1000,10,1000},Velocity={0,0,4};bool HasVelocity=true;};
|
||||
struct TraceFlags{static constexpr int WORLD=1,ENTS=2;};struct TraceBox{vector Start,End,Mins,Maxs;int Flags=0;};
|
||||
struct NativeWorld{int traces=0;float fraction=1;float TraceMove(TraceBox* t,void*){traces++;assert(t->Flags==3&&t->Mins.y==-.9f);return fraction;}};
|
||||
struct BaseRoad{float width=6;Array<vector>points={{900,10,1300},{1100,10,1300}};float GetWidth(){return width;}void GetPoints(Array<vector>&p){p=points;}};
|
||||
struct RoadManager{Array<BaseRoad*>roads;int queries=0;void GetRoadsInAABB(vector low,vector high,Array<BaseRoad*>&out){queries++;assert(high.x-low.x==200&&high.z-low.z==200&&high.y-low.y==2000);out=roads;}};
|
||||
struct SCR_AIWorld{RoadManager manager;bool available=true;RoadManager*GetRoadNetworkManager(){return available?&manager:nullptr;}};
|
||||
struct Game{NativeWorld world;SCR_AIWorld ai;NativeWorld*GetWorld(){return &world;}SCR_AIWorld*GetAIWorld(){return &ai;}}game;Game*GetGame(){return &game;}
|
||||
struct Replication{static inline bool server=true;static bool IsServer(){return server;}};
|
||||
struct ERR_Actors{static inline bool steep=false;static vector Ground(vector p){p.y=10;if(steep&&p.x>1000)p.y=11;return p;}};
|
||||
struct ChimeraWorldUtils{static inline bool water=false;static bool TryGetWaterSurfaceSimple(NativeWorld*,vector){return water;}};
|
||||
|
||||
struct ERR_SettlementSafety{static inline bool clear=true;static inline int calls=0;bool OutsideSettlements(vector){calls++;return clear;}};
|
||||
struct ERR_ConvoyPlacement{static inline bool clear=true;static inline int calls=0;static inline float heading=0;bool Clear(vector p,float h){calls++;heading=h;assert(p.y==10);return clear;}};
|
||||
''' + s + r'''
|
||||
int main(){
|
||||
using Rules=ERR_HQMountedInterceptRules;ERR_HQBelief belief;SCR_AIGroup group;
|
||||
assert(Rules::Eligible(&belief,10000));
|
||||
ERR_ConvoyInterceptOrder order;order.Token=400001;order.Group=&group;order.SourceId="original-convoy";order.VehicleIndex=0;order.Position={1000,10,1300};order.IssuedAt=8000;order.ExpiresAt=125000;order.ObservedAt=5000;order.BeliefId=3;order.BeliefRevision=2;order.ReportId=1;
|
||||
vector frozen=order.Position;assert(Rules::Current(&order,&belief,10000));belief.PredictedPosition={1050,10,1000};assert(Rules::Current(&order,&belief,11000)&&order.Position==frozen);belief.PredictedPosition={1000,10,1000};
|
||||
for(int fault=0;fault<21;fault++){auto b=belief;auto o=order;float now=10000;
|
||||
if(fault==0)o.Token=400000;
|
||||
if(fault==1)o.Token=400257;
|
||||
if(fault==2)o.Group=nullptr;
|
||||
if(fault==3)o.SourceId="";
|
||||
if(fault==4)o.VehicleIndex=1;
|
||||
if(fault==5)o.BeliefId++;
|
||||
if(fault==6)o.BeliefRevision++;
|
||||
if(fault==7)o.ReportId++;
|
||||
if(fault==8)o.ObservedAt++;
|
||||
if(fault==9)o.IssuedAt=6000;
|
||||
if(fault==10)o.IssuedAt=11000;
|
||||
if(fault==11)o.ExpiresAt=125001;
|
||||
if(fault==12)o.ExpiresAt=10000;
|
||||
if(fault==13)o.IssuedAt=NAN;
|
||||
if(fault==14)o.Position.x=NAN;
|
||||
if(fault==15)b.HasVelocity=false;
|
||||
if(fault==16)b.SampleCount=1;
|
||||
if(fault==17)b.LastDeliveredAt=10001;
|
||||
if(fault==18)b.TravelMode="aircraft";
|
||||
if(fault==19)now=38001;
|
||||
if(fault==20)b.ObservedAt=-1;
|
||||
assert(!Rules::Current(&o,&b,now));}
|
||||
order.VehicleIndex=3;assert(Rules::Current(&order,&belief,38000));order.VehicleIndex=5;assert(Rules::Current(&order,&belief,38000));
|
||||
assert(!Rules::Eligible(&belief,50001));assert(!Rules::Eligible(nullptr,10000));
|
||||
Array<vector> reserved;vector result;BaseRoad road;game.ai.manager.roads={&road};
|
||||
assert(Rules::Select(&belief,10000,reserved,result)&&result==vector(1000,10,1300));assert(ERR_ConvoyPlacement::calls==1&&ERR_SettlementSafety::calls==1);assert(std::abs(ERR_ConvoyPlacement::heading-90)<.001);
|
||||
road.width=5.99;assert(!Rules::Select(&belief,10000,reserved,result)&&result==vector::Zero);road.width=6;
|
||||
int placementCalls=ERR_ConvoyPlacement::calls;ERR_SettlementSafety::clear=false;assert(!Rules::Select(&belief,10000,reserved,result)&&result==vector::Zero&&ERR_ConvoyPlacement::calls==placementCalls);ERR_SettlementSafety::clear=true;
|
||||
ERR_ConvoyPlacement::clear=false;assert(!Rules::Select(&belief,10000,reserved,result)&&result==vector::Zero);ERR_ConvoyPlacement::clear=true;
|
||||
road.points={{900,20,1300},{1100,20,1300}};placementCalls=ERR_ConvoyPlacement::calls;assert(!Rules::Select(&belief,10000,reserved,result)&&ERR_ConvoyPlacement::calls==placementCalls);road.points={{900,10,1300},{1100,10,1300}};
|
||||
reserved.Insert({1000,10,1300});assert(!Rules::Select(&belief,10000,reserved,result));reserved.clear();
|
||||
game.ai.manager.roads.resize(129,&road);assert(!Rules::Select(&belief,10000,reserved,result));game.ai.manager.roads={&road};
|
||||
auto unknown=belief;unknown.HasVelocity=false;int queries=game.ai.manager.queries;assert(!Rules::Select(&unknown,10000,reserved,result)&&game.ai.manager.queries==queries);
|
||||
Replication::server=false;assert(!Rules::Select(&belief,10000,reserved,result)&&game.ai.manager.queries==queries);Replication::server=true;
|
||||
float score,heading;assert(Rules::FromPolyline({{900,10,1300},{1100,10,1300}},belief.Position,belief.Velocity,{1000,10,1350},reserved,result,score,heading)&&result.z==1300);
|
||||
assert(!Rules::Ahead(belief.Position,belief.Velocity,{1000,10,1299}));assert(!Rules::Ahead(belief.Position,belief.Velocity,{1000,10,1901}));
|
||||
Array<vector> oversized(1025,{1000,10,1300});assert(!Rules::FromPolyline(oversized,belief.Position,belief.Velocity,{1000,10,1350},reserved,result,score,heading));
|
||||
}
|
||||
''')
|
||||
print('PASS: actual mounted intercept eligibility/order chronology, fixed destinations, native road selection bounds, width, segment heading, clearance and settlement delegation; no native driving claim')
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "/tmp/erri-pair-hq-0054/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c",
|
||||
"reviewed_sha256": "adf4ee9dfde64b159f2335b80118f64878dba7239b41d105c0a25ade85f1e11d",
|
||||
"verdict": "One concrete pre-admission pair-selection defect reported to author/root; no additional demonstrated safety blocker in reviewed dispatcher.",
|
||||
"finding": {
|
||||
"method": "PlanMountedIntercept paired candidate selection",
|
||||
"problem": "Greedy closestA excludes the only escort capable of reachingB; singleton fallback may occur despite a complete admissible pair.",
|
||||
"reproducer": "Response radius800; destinations A.x=0,B.x=60; original same-convoy eligible U.x=0,V.x=-800. U is closestA; V cannot reachB(860). V\u2192A,U\u2192B is feasible. Both radios and original status otherwise valid.",
|
||||
"required_change": "Bounded enumeration of eligible original escort assignments/permutations before pre-admission singleton fallback. At most3 escort slots. Add actual planner regression.",
|
||||
"status": "Sent to hq_memory_review and root; reviewer makes no edits."
|
||||
},
|
||||
"reviewed_good": [
|
||||
"queries increments before pair and single selection; per-pass<=2 includes failed pair+fallback. Rotating belief cursor advances even when both queries spent on one blocked track.",
|
||||
"Two independent strongly-owned routes, two latched receipt flags; ApplyInterceptPair only after both positive real Advance results and both original status/contact/claim checks.",
|
||||
"Assigned and HuntingAssigned count each member once; role eligibility excludes base assignments, pending orders and logistics. Pending/current pair respects global and hunting caps.",
|
||||
"Separate monotonically allocated400001..400256 tokens spend1or2; refusal preserves spent attempt/revision, no post-claim fallback or partner replacement.",
|
||||
"Release detaches both HQ job refs before native service release, clears only matching HQ logistics tokens and retains unrelated claims. ReleaseUnit of either original ends both.",
|
||||
"Pending order chronology enforces same delivered belief/revision/report,30-second delivery freshness and fixed120-second observation expiry. Active job intentionally retains immutable order until expiry; no live player retargeting."
|
||||
],
|
||||
"service_boundary_note": "ClearIntercept still performs native waypoint removal before token reset; sent author targeted direct-vehicle reentrancy question. No demonstrated engine callback reproduction, so not asserted as a blocker.",
|
||||
"tests_run": false,
|
||||
"target_writes": false,
|
||||
"source_edits": false,
|
||||
"native": false
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
PASS: actual mounted intercept eligibility/order chronology, fixed destinations, native road selection bounds, width, segment heading, clearance and settlement delegation; no native driving claim
|
||||
@@ -0,0 +1,263 @@
|
||||
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c
|
||||
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c
|
||||
@@ -106,4 +106,124 @@
|
||||
result = grounded;
|
||||
return true;
|
||||
}
|
||||
+
|
||||
+ // Keep both escorts on one real polyline. Horizontal arc length determines
|
||||
+ // placement; interpolation retains each road segment's height and heading.
|
||||
+ static bool PairFromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, out vector first, out vector second, out float score, out float firstHeading, out float secondHeading)
|
||||
+ {
|
||||
+ first = vector.Zero; second = vector.Zero; score = 1e20; firstHeading = 0; secondHeading = 0;
|
||||
+ if (!points || points.Count() < 2 || points.Count() > 1024 || !ERR_ForceRules.FiniteVector(seed)) return false;
|
||||
+ if (!ERR_ForceRules.FiniteVector(origin) || !ERR_HQPursuitRules.Heading(velocity, true) || !reserved || reserved.Count() > 128) return false;
|
||||
+ foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
|
||||
+ velocity[1] = 0;
|
||||
+ float total; float anchor; float nearest = 1e20;
|
||||
+ float along;
|
||||
+ vector projected;
|
||||
+ bool found;
|
||||
+ for (int i = 1; i < points.Count(); i++)
|
||||
+ {
|
||||
+ vector delta = points[i] - points[i - 1]; delta[1] = 0;
|
||||
+ float length = delta.Length();
|
||||
+ if (!ERR_ForceRules.Finite(length)) return false;
|
||||
+ if (length < 0.01) continue;
|
||||
+ vector offset = seed - points[i - 1]; offset[1] = 0;
|
||||
+ float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), 0, 1);
|
||||
+ vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
|
||||
+ float distance = vector.DistanceXZ(seed, candidate);
|
||||
+ if (!found || ERR_HQRoadIntercept.Better(distance, candidate, nearest, projected))
|
||||
+ {
|
||||
+ projected = candidate; nearest = distance; anchor = total + length * fraction;
|
||||
+ along = vector.Dot(delta.Normalized(), velocity.Normalized()); found = true;
|
||||
+ }
|
||||
+ total += length;
|
||||
+ if (!ERR_ForceRules.Finite(total)) return false;
|
||||
+ }
|
||||
+ if (!found || nearest > 100 || total < 40) return false;
|
||||
+ float span = Math.Clamp(total, 40, 60);
|
||||
+ float start = anchor - span * 0.5;
|
||||
+ // Longitudinal roads extend the pair ahead of the projection, so the first
|
||||
+ // point at the 300m planning boundary is not shifted behind that boundary.
|
||||
+ if (along >= 0.25) start = anchor;
|
||||
+ else if (along <= -0.25) start = anchor - span;
|
||||
+ start = Math.Clamp(start, 0, total - span);
|
||||
+ float finish = start + span;
|
||||
+ float travelled;
|
||||
+ vector a, b;
|
||||
+ float aHeading; float bHeading;
|
||||
+ bool haveFirst; bool haveSecond;
|
||||
+ for (int j = 1; j < points.Count(); j++)
|
||||
+ {
|
||||
+ vector segment = points[j] - points[j - 1]; segment[1] = 0;
|
||||
+ float segmentLength = segment.Length();
|
||||
+ if (segmentLength < 0.01) continue;
|
||||
+ if (!haveFirst && start <= travelled + segmentLength)
|
||||
+ {
|
||||
+ a = vector.Lerp(points[j - 1], points[j], Math.Clamp((start - travelled) / segmentLength, 0, 1));
|
||||
+ aHeading = Math.Atan2(segment[0], segment[2]) * Math.RAD2DEG; haveFirst = true;
|
||||
+ }
|
||||
+ if (!haveSecond && finish <= travelled + segmentLength)
|
||||
+ {
|
||||
+ b = vector.Lerp(points[j - 1], points[j], Math.Clamp((finish - travelled) / segmentLength, 0, 1));
|
||||
+ bHeading = Math.Atan2(segment[0], segment[2]) * Math.RAD2DEG; haveSecond = true;
|
||||
+ }
|
||||
+ if (haveFirst && haveSecond) break;
|
||||
+ travelled += segmentLength;
|
||||
+ }
|
||||
+ if (!haveFirst || !haveSecond || vector.DistanceXZ(a, b) < 40) return false;
|
||||
+ if (vector.DistanceXZ(seed, a) > 100 || vector.DistanceXZ(seed, b) > 100) return false;
|
||||
+ if (!Ahead(origin, velocity, a) || !Ahead(origin, velocity, b)) return false;
|
||||
+ if (!ERR_HQRoadIntercept.Unreserved(a, reserved) || !ERR_HQRoadIntercept.Unreserved(b, reserved)) return false;
|
||||
+ // Canonical order does not depend on native road enumeration direction.
|
||||
+ if (ERR_HQRoadIntercept.Better(0, b, 0, a))
|
||||
+ {
|
||||
+ vector swap = a; a = b; b = swap;
|
||||
+ float swapHeading = aHeading; aHeading = bHeading; bHeading = swapHeading;
|
||||
+ }
|
||||
+ first = a; second = b; score = nearest; firstHeading = aHeading; secondHeading = bHeading;
|
||||
+ return true;
|
||||
+ }
|
||||
+ static bool SelectPair(ERR_HQBelief belief, float now, array<vector> reserved, out vector first, out vector second)
|
||||
+ {
|
||||
+ first = vector.Zero; second = vector.Zero;
|
||||
+ if (!Replication.IsServer() || !Eligible(belief, now) || !reserved || reserved.Count() > 128) return false;
|
||||
+ if (!GetGame() || !GetGame().GetWorld()) return false;
|
||||
+ vector velocity = belief.Velocity; velocity[1] = 0;
|
||||
+ vector seed = belief.PredictedPosition + velocity.Normalized() * Math.Clamp(velocity.Length() * 60, 300, 900);
|
||||
+ if (!ERR_HQPursuitRules.PositionValid(seed)) return false;
|
||||
+ SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
+ if (!world || !world.GetRoadNetworkManager()) return false;
|
||||
+ array<BaseRoad> roads = {};
|
||||
+ world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
|
||||
+ if (roads.Count() > 128) return false;
|
||||
+ int segments;
|
||||
+ bool found;
|
||||
+ vector bestFirst, bestSecond;
|
||||
+ float bestScore = 1e20;
|
||||
+ float bestFirstHeading; float bestSecondHeading;
|
||||
+ foreach (BaseRoad road : roads)
|
||||
+ {
|
||||
+ if (!road || !ERR_ForceRules.Finite(road.GetWidth()) || road.GetWidth() < 6) continue;
|
||||
+ array<vector> points = {}; road.GetPoints(points);
|
||||
+ if (points.Count() < 2 || points.Count() > 1024) continue;
|
||||
+ segments += points.Count() - 1;
|
||||
+ if (segments > 4096) return false;
|
||||
+ vector a, b;
|
||||
+ float score; float aHeading; float bHeading;
|
||||
+ if (!PairFromPolyline(points, belief.PredictedPosition, velocity, seed, reserved, a, b, score, aHeading, bHeading)) continue;
|
||||
+ if (!found || ERR_HQRoadIntercept.Better(score, a, bestScore, bestFirst))
|
||||
+ { bestFirst = a; bestSecond = b; bestScore = score; bestFirstHeading = aHeading; bestSecondHeading = bHeading; found = true; }
|
||||
+ }
|
||||
+ if (!found) return false;
|
||||
+ vector groundedFirst = ERR_Actors.Ground(bestFirst);
|
||||
+ vector groundedSecond = ERR_Actors.Ground(bestSecond);
|
||||
+ if (!ERR_HQPursuitRules.PositionValid(groundedFirst) || !ERR_HQPursuitRules.PositionValid(groundedSecond)) return false;
|
||||
+ if (Math.AbsFloat(groundedFirst[1] - bestFirst[1]) > 2 || Math.AbsFloat(groundedSecond[1] - bestSecond[1]) > 2) return false;
|
||||
+ if (vector.DistanceXZ(groundedFirst, groundedSecond) < 40) return false;
|
||||
+ ERR_SettlementSafety settlements = new ERR_SettlementSafety();
|
||||
+ if (!settlements.OutsideSettlements(groundedFirst) || !settlements.OutsideSettlements(groundedSecond)) return false;
|
||||
+ ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
|
||||
+ if (!placement.Clear(groundedFirst, bestFirstHeading) || !placement.Clear(groundedSecond, bestSecondHeading)) return false;
|
||||
+ first = groundedFirst; second = groundedSecond;
|
||||
+ return true;
|
||||
+ }
|
||||
}
|
||||
--- /dev/null
|
||||
+++ b/tests/test_hq_mounted_intercept_pair.py
|
||||
@@ -0,0 +1,133 @@
|
||||
+"""Actual paired road geometry and bounded native survey; native boundaries are doubled."""
|
||||
+from pathlib import Path
|
||||
+import os,re
|
||||
+from policy_harness import ROOT,body,run
|
||||
+p=Path(os.environ.get('ERR_MOUNTED_INTERCEPT_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c'))
|
||||
+source=p.read_text()
|
||||
+base=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQRoadIntercept.c'
|
||||
+source='class ERR_HQRoadIntercept\n{\n'+'\n'.join(body(base,x) for x in ('static bool Eligible(', 'static bool Unreserved(', 'static bool Better('))+'\n}\n'+source
|
||||
+for forbidden in ('GetPlayer','GetControlledEntity','GetOrigin','Spawn(', 'AddWaypoint','SetOrigin','CallLater'):
|
||||
+ assert forbidden not in source,forbidden
|
||||
+rules='\n'.join(body(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c', x) for x in ('static bool Finite(', 'static bool PositionValid(', 'static bool Actionable(', 'static bool Heading(', 'static ERR_HQPursuitRole Role(', 'static vector Direction(', 'static vector Objective('))
|
||||
+rules=rules.replace('Math.','Math::').replace('vector.','vector::').replace('ERR_HQPursuitRole.','ERR_HQPursuitRole::').replace('vector offset;','vector offset{};')
|
||||
+s=re.sub(r'//[^\n]*','',source).replace('class ERR_HQRoadIntercept','struct ERR_HQRoadIntercept').replace('class ERR_HQMountedInterceptRules','struct ERR_HQMountedInterceptRules').replace('class ERR_ConvoyInterceptOrder','struct ERR_ConvoyInterceptOrder')
|
||||
+s=s.replace('out vector','vector&').replace('out float','float&').replace('array<vector>','Array<vector>').replace('array<BaseRoad>','Array<BaseRoad*>').replace('foreach (','for (')
|
||||
+s=s.replace('SCR_AIWorld.Cast(GetGame().GetAIWorld())','GetGame()->GetAIWorld()')
|
||||
+for name in ('ERR_HQBelief','SCR_AIWorld','BaseRoad','TraceBox','SCR_AIGroup','ERR_ConvoyInterceptOrder','ERR_SettlementSafety','ERR_ConvoyPlacement'):
|
||||
+ s=re.sub(r'\b'+name+r' (\w+)',name+r'* \1',s)
|
||||
+for name in ('belief','world','road','body','order','settlements','placement'):
|
||||
+ s=s.replace(name+'.',name+'->')
|
||||
+s=s.replace('GetGame().','GetGame()->').replace('GetRoadNetworkManager().','GetRoadNetworkManager()->').replace('GetWorld().','GetWorld()->')
|
||||
+for name in ('ERR_ForceRules','ERR_HQPursuitRules','ERR_HQPursuitRole','ERR_HQMemoryKind','vector','Math','ERR_Actors','ChimeraWorldUtils','TraceFlags','Replication','ERR_HQRoadIntercept'):
|
||||
+ s=s.replace(name+'.',name+'::')
|
||||
+s=s.replace('null','nullptr').replace('string SourceId','std::string SourceId').replace('.IsEmpty()', '.empty()')
|
||||
+s=re.sub(r'\b(bool|int|float) (\w+);',r'\1 \2{};',s).replace('float score, heading;', 'float score=0, heading=0;').replace('\n}\n','\n};\n')
|
||||
+run(r'''
|
||||
+#include <cassert>
|
||||
+#include <algorithm>
|
||||
+#include <cmath>
|
||||
+#include <cstdio>
|
||||
+#include <limits>
|
||||
+#include <vector>
|
||||
+#include <string>
|
||||
+enum class ERR_HQPursuitRole{SEARCH,INTERCEPT,CONTAIN};enum class ERR_HQMemoryKind{SIGHTING,DISTRESS,BODY};
|
||||
+struct SCR_AIGroup{};
|
||||
+struct Math{static constexpr float RAD2DEG=57.2957795131f;static float Atan2(float x,float y){return std::atan2(x,y);}static float Clamp(float x,float a,float b){return std::clamp(x,a,b);}static float AbsFloat(float x){return std::abs(x);}};
|
||||
+struct vector{float x=0,y=0,z=0;vector()=default;vector(float a,float b,float c):x(a),y(b),z(c){}vector(const char*s){sscanf(s,"%f %f %f",&x,&y,&z);}float&operator[](int i){return i==0?x:i==1?y:z;}float operator[](int i)const{return i==0?x:i==1?y:z;}
|
||||
+ vector operator+(vector b)const{return{x+b.x,y+b.y,z+b.z};}vector operator-(vector b)const{return{x-b.x,y-b.y,z-b.z};}vector operator*(float n)const{return{x*n,y*n,z*n};}bool operator==(vector b)const{return x==b.x&&y==b.y&&z==b.z;}
|
||||
+ float Length()const{return std::sqrt(x*x+y*y+z*z);}vector Normalized()const{float n=Length();return{x/n,y/n,z/n};}static float Dot(vector a,vector b){return a.x*b.x+a.y*b.y+a.z*b.z;}static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}static vector Lerp(vector a,vector b,float n){return a+(b-a)*n;}static const vector Zero;};
|
||||
+const vector vector::Zero{};vector Vector(float a,float b,float c){return{a,b,c};}
|
||||
+template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;using std::vector<T>::operator=;int Count(){return this->size();}void Insert(T v){this->push_back(v);}explicit operator bool(){return true;}};
|
||||
+struct ERR_ForceRules{static bool Finite(float n){return std::isfinite(n);}static bool FiniteVector(vector p){return Finite(p.x)&&Finite(p.y)&&Finite(p.z);}};
|
||||
+struct ERR_HQPursuitRules{''' + rules + r'''};
|
||||
+struct ERR_HQBelief{ERR_HQMemoryKind Kind=ERR_HQMemoryKind::SIGHTING;int SampleCount=2,MessageId=1,BeliefId=3,BeliefRevision=2;vector Position={1000,10,1000};float ObservedAt=5000,LastDeliveredAt=7000,Confidence=.9,Radius=40;std::string TravelMode="on foot";vector PredictedPosition={1000,10,1000},Velocity={0,0,4};bool HasVelocity=true;};
|
||||
+struct TraceFlags{static constexpr int WORLD=1,ENTS=2;};struct TraceBox{vector Start,End,Mins,Maxs;int Flags=0;};
|
||||
+struct NativeWorld{int traces=0;float fraction=1;float TraceMove(TraceBox* t,void*){traces++;assert(t->Flags==3&&t->Mins.y==-.9f);return fraction;}};
|
||||
+struct BaseRoad{static inline int reads=0;float width=6;Array<vector>points={{900,10,1300},{1100,10,1300}};float GetWidth(){return width;}void GetPoints(Array<vector>&p){reads++;p=points;}};
|
||||
+struct RoadManager{Array<BaseRoad*>roads;int queries=0;void GetRoadsInAABB(vector low,vector high,Array<BaseRoad*>&out){queries++;assert(high.x-low.x==200&&high.z-low.z==200&&high.y-low.y==2000);out=roads;}};
|
||||
+struct SCR_AIWorld{RoadManager manager;bool available=true;RoadManager*GetRoadNetworkManager(){return available?&manager:nullptr;}};
|
||||
+struct Game{NativeWorld world;SCR_AIWorld ai;NativeWorld*GetWorld(){return &world;}SCR_AIWorld*GetAIWorld(){return &ai;}}game;Game*GetGame(){return &game;}
|
||||
+struct Replication{static inline bool server=true;static bool IsServer(){return server;}};
|
||||
+struct ERR_Actors{static inline float rightHeight=10;static vector Ground(vector p){p.y=p.x>1000?rightHeight:10;return p;}};
|
||||
+struct ChimeraWorldUtils{static inline bool water=false;static bool TryGetWaterSurfaceSimple(NativeWorld*,vector){return water;}};
|
||||
+
|
||||
+struct ERR_SettlementSafety{static inline bool clear=true,rightClear=true;static inline int calls=0;bool OutsideSettlements(vector p){calls++;return clear&&(p.x<=1000||rightClear);}};
|
||||
+struct ERR_ConvoyPlacement{static inline bool clear=true,rightClear=true,water=false;static inline int calls=0;static inline float heading=0;bool Clear(vector p,float h){calls++;heading=h;assert(p.y==10);return clear&&!water&&(p.x<=1000||rightClear);}};
|
||||
+''' + s + r'''
|
||||
+int main(){
|
||||
+ using Rules=ERR_HQMountedInterceptRules;
|
||||
+ ERR_HQBelief belief;Array<vector> reserved;vector a={1,2,3},b={4,5,6};float score=0,ha=0,hb=0;
|
||||
+ vector origin={1000,10,1000},velocity={0,0,4},seed={1000,10,1300};
|
||||
+ auto pair=[&](Array<vector> p){return Rules::PairFromPolyline(p,origin,velocity,seed,reserved,a,b,score,ha,hb);};
|
||||
+ auto empty=[&](){assert(a==vector::Zero&&b==vector::Zero);};
|
||||
+ // Real arc interpolation over a90-degree bend, not straight interpolation
|
||||
+ // between the road's endpoints (which would put both escorts off the road).
|
||||
+ assert(pair({{900,10,1300},{1000,10,1300},{1000,10,1400}}));
|
||||
+ assert(a==vector(970,10,1300)&&b==vector(1000,10,1330));
|
||||
+ assert(std::abs(ha-90)<.001&&std::abs(hb)<.001);
|
||||
+ assert(vector::DistanceXZ(a,b)>42&&vector::DistanceXZ(a,b)<43);
|
||||
+ // Arc span alone is insufficient on a bend-back/hairpin.
|
||||
+ assert(!pair({{970,10,1300},{1000,10,1300},{1000,10,1301},{970,10,1301}}));empty();
|
||||
+ assert(!pair({{980,10,1300},{1019.9f,10,1300}}));empty();
|
||||
+ assert(pair({{980,10,1300},{1020,10,1300}})&&vector::DistanceXZ(a,b)==40);
|
||||
+ // A road aligned with travel must not push a partner behind the300m floor.
|
||||
+ assert(pair({{1000,10,1250},{1000,10,1450}}));
|
||||
+ assert(a==vector(1000,10,1300)&&b==vector(1000,10,1360));
|
||||
+ assert(pair({{1000,10,1450},{1000,10,1250}}));
|
||||
+ assert(a==vector(1000,10,1300)&&b==vector(1000,10,1360));
|
||||
+ // Actual sloped segment retains height for subsequent bridge validation.
|
||||
+ assert(pair({{900,5,1300},{1100,15,1300}}));
|
||||
+ assert(a==vector(970,8.5,1300)&&b==vector(1030,11.5,1300));
|
||||
+ // Degenerate segments are harmless but a wholly degenerate road has no span.
|
||||
+ assert(pair({{900,10,1300},{900,10,1300},{1100,10,1300}}));
|
||||
+ assert(!pair({{1000,10,1300},{1000,10,1300}}));empty();
|
||||
+ assert(!pair({{900,10,1300},{NAN,10,1300},{1100,10,1300}}));empty();
|
||||
+ assert(!pair(Array<vector>(1025,{1000,10,1300})));empty();
|
||||
+ assert(!pair({}));empty();assert(!pair({{1000,10,1300}}));empty();
|
||||
+ auto good=Array<vector>{{900,10,1300},{1100,10,1300}};
|
||||
+ velocity={0,0,0};assert(!pair(good));empty();velocity={0,0,4};
|
||||
+ seed.x=NAN;assert(!pair(good));empty();seed={1000,10,1300};
|
||||
+ origin.x=NAN;assert(!pair(good));empty();origin={1000,10,1000};
|
||||
+ reserved={{970,10,1380}};assert(pair(good));
|
||||
+ reserved={{970,10,1379.99f}};assert(!pair(good));empty();
|
||||
+ reserved={{NAN,10,1380}};assert(!pair(good));empty();
|
||||
+ reserved=Array<vector>(129,{10000,10,10000});assert(!pair(good));empty();reserved.clear();
|
||||
+ for(float z:{1290.f,1901.f,700.f}){seed.z=z;assert(!pair({{900,10,z},{1100,10,z}}));empty();}seed.z=1300;
|
||||
+ BaseRoad road;road.points=good;game.ai.manager.roads={&road};
|
||||
+ int q=game.ai.manager.queries;
|
||||
+ assert(Rules::SelectPair(&belief,10000,reserved,a,b));assert(game.ai.manager.queries==q+1);
|
||||
+ assert(a==vector(970,10,1300)&&b==vector(1030,10,1300));
|
||||
+ assert(ERR_SettlementSafety::calls==2&&ERR_ConvoyPlacement::calls==2);
|
||||
+ auto select=[&](){a={1,2,3};b={4,5,6};bool ok=Rules::SelectPair(&belief,10000,reserved,a,b);if(!ok)empty();return ok;};
|
||||
+ // Second position cannot escape settlement, water/collision or bridge checks.
|
||||
+ int pc=ERR_ConvoyPlacement::calls;ERR_SettlementSafety::rightClear=false;assert(!select());assert(ERR_ConvoyPlacement::calls==pc);ERR_SettlementSafety::rightClear=true;
|
||||
+ pc=ERR_ConvoyPlacement::calls;ERR_ConvoyPlacement::rightClear=false;assert(!select());assert(ERR_ConvoyPlacement::calls==pc+2);ERR_ConvoyPlacement::rightClear=true;
|
||||
+ pc=ERR_ConvoyPlacement::calls;ERR_ConvoyPlacement::water=true;assert(!select());assert(ERR_ConvoyPlacement::calls==pc+1);ERR_ConvoyPlacement::water=false;
|
||||
+ pc=ERR_ConvoyPlacement::calls;ERR_Actors::rightHeight=7;assert(!select());assert(ERR_ConvoyPlacement::calls==pc);ERR_Actors::rightHeight=10;
|
||||
+ road.points={{900,20,1300},{1100,20,1300}};assert(!select());road.points=good;
|
||||
+ // Reject a pair assembled from two short unrelated roads, even when their
|
||||
+ // endpoints could have formed a geometrically spaced chord.
|
||||
+ BaseRoad left,right;left.points={{960,10,1300},{980,10,1300}};right.points={{1020,10,1300},{1040,10,1300}};game.ai.manager.roads={&left,&right};assert(!select());
|
||||
+ game.ai.manager.roads={&road};road.width=5.99;assert(!select());road.width=NAN;assert(!select());road.width=6;
|
||||
+ // All pre-query gates remain cheap; one permitted pair call is one query.
|
||||
+ q=game.ai.manager.queries;belief.HasVelocity=false;assert(!select());belief.HasVelocity=true;
|
||||
+ Replication::server=false;assert(!select());Replication::server=true;
|
||||
+ reserved.resize(129);assert(!select());reserved.clear();
|
||||
+ assert(game.ai.manager.queries==q);
|
||||
+ game.ai.available=false;assert(!select());game.ai.available=true;assert(game.ai.manager.queries==q);
|
||||
+ // Road count bound refuses before any point arrays are read.
|
||||
+ game.ai.manager.roads=Array<BaseRoad*>(129,&road);int reads=BaseRoad::reads;assert(!select());assert(BaseRoad::reads==reads);
|
||||
+ game.ai.manager.roads=Array<BaseRoad*>(128,&road);assert(select());assert(BaseRoad::reads==reads+128);
|
||||
+ // Total4096 segment allowance includes irrelevant/degenerate vertices; an
|
||||
+ // earlier valid pair cannot leak through once later roads exhaust the cap.
|
||||
+ BaseRoad big,tail;big.points=Array<vector>(1024,{900,10,1300});big.points.back()={1100,10,1300};tail.points=Array<vector>(5,{900,10,1300});tail.points.back()={1100,10,1300};
|
||||
+ game.ai.manager.roads={&big,&big,&big,&big,&tail};assert(select());
|
||||
+ tail.points.insert(tail.points.begin(),{900,10,1300});pc=ERR_ConvoyPlacement::calls;assert(!select());assert(ERR_ConvoyPlacement::calls==pc);
|
||||
+ // Pair failure still permits unchanged singleton selection in a separate
|
||||
+ // call; this explicitly consumes the caller's second survey budget.
|
||||
+ road.points={{995,10,1300},{1005,10,1300}};game.ai.manager.roads={&road};q=game.ai.manager.queries;
|
||||
+ assert(!select());vector singleton;assert(Rules::Select(&belief,10000,reserved,singleton));assert(singleton==vector(1000,10,1300)&&game.ai.manager.queries==q+2);
|
||||
+}
|
||||
+
|
||||
+''')
|
||||
+print('PASS: actual paired arc geometry,40m/80m spacing, separate original polylines, both-position validation and128road/4096segment budget; no native driving proof')
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/tmp/err-policy-oiypbgum/check.cpp: In static member function ‘static bool ERR_HQMountedInterceptRules::SelectPair(ERR_HQBelief*, float, Array<vector>, vector&, vector&)’:
|
||||
/tmp/err-policy-oiypbgum/check.cpp:335:35: error: variable ‘bestSecondHeading’ set but not used [-Werror=unused-but-set-variable=]
|
||||
335 | float bestFirstHeading{}; float bestSecondHeading{};
|
||||
| ^~~~~~~~~~~~~~~~~
|
||||
cc1plus: all warnings being treated as errors
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-pair-geometry-0054/tests/test_hq_mounted_intercept_pair.py", line 25, in <module>
|
||||
run(r'''
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<104 lines>...
|
||||
|
||||
|
||||
''')
|
||||
^^^^
|
||||
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 13, in run
|
||||
subprocess.run(['g++','-std=c++17','-Wall','-Werror',str(p/'check.cpp'),'-o',str(p/'check')],check=True)
|
||||
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/usr/lib/python3.14/subprocess.py", line 578, in run
|
||||
raise CalledProcessError(retcode, process.args,
|
||||
output=stdout, stderr=stderr)
|
||||
subprocess.CalledProcessError: Command '['g++', '-std=c++17', '-Wall', '-Werror', '/tmp/err-policy-oiypbgum/check.cpp', '-o', '/tmp/err-policy-oiypbgum/check']' returned non-zero exit status 1.
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"verdict": "PASS: integrated three-source proposal identity and complete294-program suite bindings verified read-only. Native normal-route run still pending and not inspected by this review.",
|
||||
"integrated_proposal_hashes": {
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c": "a37edcf66e43945f331db47fe81cb2e0e767a7aaf8115244dcf9e6295932be98",
|
||||
"tests/test_hq_mounted_intercept_pair.py": "a8d758b1ff20bb6d1a95a9ff1d38f687e43c819c279f5ea9692e84f766dc4fb9",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": "60d0fef83f43eea0501bf5fe46f369556e22acda6d034908b5965d8820b28893",
|
||||
"tests/test_hq_mounted_dispatch.py": "50e8973b730c34a54775e7ba5ae2a72c9a6a101bc9b14a1521c4b5c6d401552e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c": "b59016d83cb9d40136b98b4e0067ea490bc553574fc53603593efd5522dbed20",
|
||||
"tests/test_convoy_intercept_pair.py": "52b55727b7c2441d43530efa701045b9439b9175c71fbd2ae7577f851d2c98ec"
|
||||
},
|
||||
"proposal_manifest_hashes": {
|
||||
"/tmp/erri-pair-geometry-0054/source-manifest.json": "b439a06d5826ebae5681b330d8edc078f7156c02a9a16f451eb50e542fd298e1",
|
||||
"/tmp/erri-pair-hq-0054/source-manifest.json": "2664094d357c679efdd195016d662846797cc90423ae74332ee85e852baf413d",
|
||||
"/tmp/erri-pair-service-0054/source-manifest.json": "e27b7654dd458683e8689f1621149b589070ea0177588c08c7328d7d2604cf86"
|
||||
},
|
||||
"all_installed_paths_match": true,
|
||||
"installed_path_count": 9,
|
||||
"authored_source_files": 420,
|
||||
"suite": {
|
||||
"exit": 0,
|
||||
"program_count": 294,
|
||||
"unique_program_count": 294,
|
||||
"all_program_exits_zero": true,
|
||||
"inputs_unchanged_at_run": true,
|
||||
"current_full_input_inventory_equals_frozen": true,
|
||||
"input_files": 976,
|
||||
"difference": {},
|
||||
"seconds": 207.57,
|
||||
"new_pair_and_existing_dispatch_column_tests_registered_and_passed": true
|
||||
},
|
||||
"evidence_sha256": {
|
||||
"/tmp/erri-pair-root-0054/suite-inputs.json": "4eddea7702b382301403bc616a04082965eb1410be893e23b1abf76d1bc0d2cb",
|
||||
"/tmp/erri-pair-root-0054/suite-result.json": "87844f9f6d95ab2e22388e3ef4e8fef5807aaf605fee44745409dfa0f53bafc6",
|
||||
"/tmp/erri-pair-root-0054/installed.json": "15a2b934f6885611a26e5a0d4b98056e6fb3f2747ff2e9265f42a1bb69cb8a5b",
|
||||
"/tmp/erri-pair-root-0054/run-suite.py": "b609a9132f93339d09e9b12cc233f6c1d1984a4df1e9819e55008590dd262646"
|
||||
},
|
||||
"limits": [
|
||||
"Offline actual-method tests do not prove native paired driving/arrival/hold/resume or traffic blockage.",
|
||||
"No claim for running normal-route result until its final archive is reviewed.",
|
||||
"No process inspection, compiler/test execution or native action performed by reviewer."
|
||||
],
|
||||
"actions": {
|
||||
"source_or_target_edits": false,
|
||||
"original_edits": false,
|
||||
"compilers_or_tests": false,
|
||||
"native_or_process_inspection": false,
|
||||
"scratch_output": "/tmp/erri-pair-geometry-0054/integrated-review.json"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"spacing-removed": {
|
||||
"exit": 1,
|
||||
"runtime_assertion_failure": true
|
||||
},
|
||||
"pair-second-clear-checks-first": {
|
||||
"exit": 1,
|
||||
"runtime_assertion_failure": true
|
||||
},
|
||||
"pair-budget-removed": {
|
||||
"exit": 1,
|
||||
"runtime_assertion_failure": true
|
||||
},
|
||||
"pair-chord-instead-of-arc": {
|
||||
"exit": 1,
|
||||
"runtime_assertion_failure": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"verdict": "PASS narrow semantic-preserving field rename and matching harness adapter. Corrected native compilation/full suite remain pending.",
|
||||
"fix": {
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": {
|
||||
"before": "60d0fef83f43eea0501bf5fe46f369556e22acda6d034908b5965d8820b28893",
|
||||
"after": "cee3ae301ac2f211c290450b644a7942c9786a2a40d06f16e2a47efa96fd15b6"
|
||||
},
|
||||
"tests/test_hq_mounted_dispatch.py": {
|
||||
"before": "50e8973b730c34a54775e7ba5ae2a72c9a6a101bc9b14a1521c4b5c6d401552e",
|
||||
"after": "3eb793719b21d508ffc294f5a1bf5c5153f5717a1408a8f779f0a4c1ddefe2a6"
|
||||
}
|
||||
},
|
||||
"delta_exact": "Only choice field Vehicle\u2192Escort declaration and its2producer/consumer uses, plus2matching harness adapter/schema changes. No selection, allocation, receipt, cap, cleanup or geometry behavior changed.",
|
||||
"initial_suite_preserved": {
|
||||
"programs": 294,
|
||||
"original_report_and_inputs_hashes_match_pre_native_name_fix_archive": true,
|
||||
"native_result": "Initial source failed native compile; offline294 pass does not qualify native compilation."
|
||||
},
|
||||
"failed_native_report_sha256": "8a72524342ac2a239d394b2a217d0b6905fee0409867775fb906b05cd1aa1b1f",
|
||||
"new_source_native_or_fullsuite_claim": false,
|
||||
"actions": {
|
||||
"read_only_target": true,
|
||||
"no_compilers_tests_native_process_inspection": true,
|
||||
"only_output": "/tmp/erri-pair-geometry-0054/name-fix-review.json"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"verdict": "PASS corrected native compilation and scripted normal-escape-route regression; strict native report still fails on13 retained known engine errors. This is not physical paired-interception qualification.",
|
||||
"archive": "/home/jakub/devel/escapistsrereintroduced/docs/evidence/native-2026-10-03-mounted-pair-normal",
|
||||
"raw_session": "/tmp/erri-pair-normal-fixed-0054/logs/logs_2026-10-03_01-12-25",
|
||||
"raw_report_recomputed_exact": true,
|
||||
"assertions_passed": 63,
|
||||
"completions": 1,
|
||||
"terminal": 5,
|
||||
"script_or_runtime_failures": [],
|
||||
"engine_error_count": 13,
|
||||
"known_error_rows_match_previous_active_restart_after_timestamp_removal": true,
|
||||
"strict_native_clean": false,
|
||||
"actual_scope": "One local Workbench Chat normal-escape-route: prepared original sites/prison, carried clues, radio eligibility/occupied timer/reset/completion, extraction occupancy and scripted victory. Native script module compiles after Vehicle\u2192Escort correction.",
|
||||
"source_bindings": {
|
||||
"files": 420,
|
||||
"launch_end_current_inventory_and_hashes_equal": true,
|
||||
"manifest_sha256": "d7b2a8a4c7bdca7cd78a23747c3190b0f1c41a36c16bb9d14490bdc40792e648",
|
||||
"console_sha256": "6e8f18f79fc5a2835fe76046981374a85a87e6f9d21ab565875209c68b3dc5eb",
|
||||
"archive_raw_files_equal": {
|
||||
"external-addons-inputs.json.txt": true,
|
||||
"console.log.txt": true,
|
||||
"script.log.txt": true,
|
||||
"error.log.txt": true,
|
||||
"rereintroduced-hq.log.txt": true,
|
||||
"headless-launch.log.txt": true,
|
||||
"runner.json.txt": true,
|
||||
"err-prison.png": true,
|
||||
"err-result.png": true,
|
||||
"launch-source-sha256.json": true,
|
||||
"launch-source.json": true
|
||||
},
|
||||
"only_source_delta_from_failed_compile": {
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": {
|
||||
"failed_compile": "60d0fef83f43eea0501bf5fe46f369556e22acda6d034908b5965d8820b28893",
|
||||
"corrected": "cee3ae301ac2f211c290450b644a7942c9786a2a40d06f16e2a47efa96fd15b6"
|
||||
}
|
||||
}
|
||||
},
|
||||
"cleanup": {
|
||||
"runner_result": "smoke-completed",
|
||||
"confirmed": true,
|
||||
"idle_verified": true,
|
||||
"errors": [],
|
||||
"external_unchanged": true,
|
||||
"external_count": 7,
|
||||
"seconds": 183.304,
|
||||
"scope": "Verified sealed runner metadata against raw original; no process inspection or new native actions."
|
||||
},
|
||||
"initial_failure_retained": {
|
||||
"archive": "/home/jakub/devel/escapistsrereintroduced/docs/evidence/native-2026-10-03-mounted-pair-compile",
|
||||
"assertions": 0,
|
||||
"completions": 0,
|
||||
"failures": [
|
||||
"01:10:56.406 SCRIPT (E): @\"Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c,6\": Variable name 'Vehicle' is already used as type name",
|
||||
"01:10:56.853 SCRIPT (E): Can't compile \"Game\" script module!"
|
||||
],
|
||||
"report_sha256": "8a72524342ac2a239d394b2a217d0b6905fee0409867775fb906b05cd1aa1b1f",
|
||||
"source_manifest_sha256": "7248005290380f3f2c7353fbda78240d267ad1d7a57bd2956f48a468dfb0f319"
|
||||
},
|
||||
"final_offline_suite": "Fresh corrected294-program suite is running per parent; this review deliberately makes no claim about its completion. Earlier294 result belongs to pre-name-fix source and is archived separately.",
|
||||
"limitations": [
|
||||
"No paired or singleton physical interception travel/arrival/hold/resume demonstrated by normal-route fixture.",
|
||||
"No manual human escape, remote multiplayer/JIP, performance/FPS, package or release qualification.",
|
||||
"Native engine/resource errors retained rather than suppressed.",
|
||||
"Screenshots archived but not interpreted as GUI/visual evidence here."
|
||||
],
|
||||
"review_actions": {
|
||||
"target_original_edits": false,
|
||||
"tests_compilers": false,
|
||||
"native_launch": false,
|
||||
"process_inspection": false,
|
||||
"only_output": "/tmp/erri-pair-geometry-0054/native-review.json"
|
||||
},
|
||||
"evidence_sha256": {
|
||||
"/home/jakub/devel/escapistsrereintroduced/docs/evidence/native-2026-10-03-mounted-pair-normal/report.json": "4818f6010191eb86ace85f7c980a5bb1d4832aedd9e90065eecb00b7ba3b42ec",
|
||||
"/home/jakub/devel/escapistsrereintroduced/docs/evidence/native-2026-10-03-mounted-pair-normal/runner.json.txt": "00bbc4fc36b99f7c0c6adba6c873db6f7744d8ac3256e7dd40b3873ffa51f1ab",
|
||||
"/home/jakub/devel/escapistsrereintroduced/docs/evidence/native-2026-10-03-mounted-pair-normal/launch-source.json": "62e6931574e6c0ee66546dc1ab0e55b33c74fcaf73db94b98ad5cc77d5915d32",
|
||||
"/home/jakub/devel/escapistsrereintroduced/docs/evidence/native-2026-10-03-mounted-pair-normal/source-sha256.json": "d7b2a8a4c7bdca7cd78a23747c3190b0f1c41a36c16bb9d14490bdc40792e648",
|
||||
"/tmp/erri-pair-root-0054/name-fix.json": "59de64aef61389f09606469862bde7868e9ed7937e7b4af7c826073aa6f38902"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
check: /tmp/err-policy-0g0moutr/check.cpp:432: int main(): Assertion `!select()' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-pair-geometry-0054/tests/test_hq_mounted_intercept_pair.py", line 25, in <module>
|
||||
run(r'''
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<104 lines>...
|
||||
|
||||
|
||||
''')
|
||||
^^^^
|
||||
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
|
||||
subprocess.run([str(p/'check')],check=True)
|
||||
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/usr/lib/python3.14/subprocess.py", line 578, in run
|
||||
raise CalledProcessError(retcode, process.args,
|
||||
output=stdout, stderr=stderr)
|
||||
subprocess.CalledProcessError: Command '['/tmp/err-policy-0g0moutr/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1,229 @@
|
||||
// Detached radio order. Only its issuer chooses Position and the fixed deadline.
|
||||
class ERR_ConvoyInterceptOrder
|
||||
{
|
||||
int Token;
|
||||
SCR_AIGroup Group;
|
||||
string SourceId;
|
||||
int VehicleIndex;
|
||||
vector Position;
|
||||
float IssuedAt;
|
||||
float ExpiresAt;
|
||||
float ObservedAt;
|
||||
int BeliefId;
|
||||
int BeliefRevision;
|
||||
int ReportId;
|
||||
}
|
||||
|
||||
// Delivered ground history only; no target entity reads, force creation or orders.
|
||||
class ERR_HQMountedInterceptRules
|
||||
{
|
||||
static bool Eligible(ERR_HQBelief belief, float now)
|
||||
{
|
||||
if (!ERR_HQRoadIntercept.Eligible(belief, now)) return false;
|
||||
if (belief.BeliefId <= 0 || belief.BeliefRevision <= 0 || belief.ObservedAt < 0) return false;
|
||||
return ERR_HQPursuitRules.PositionValid(belief.Position) && ERR_HQPursuitRules.PositionValid(belief.PredictedPosition);
|
||||
}
|
||||
static bool Current(ERR_ConvoyInterceptOrder order, ERR_HQBelief belief, float now)
|
||||
{
|
||||
if (!order || !Eligible(belief, now) || !order.Group || order.SourceId.IsEmpty()) return false;
|
||||
if (order.Token < 400001 || order.Token > 400256) return false;
|
||||
if (order.VehicleIndex != 0 && order.VehicleIndex != 3 && order.VehicleIndex != 5) return false;
|
||||
if (order.BeliefId != belief.BeliefId || order.BeliefRevision != belief.BeliefRevision
|
||||
|| order.ReportId != belief.MessageId || order.ObservedAt != belief.ObservedAt) return false;
|
||||
if (!ERR_ForceRules.Finite(order.IssuedAt) || !ERR_ForceRules.Finite(order.ExpiresAt)) return false;
|
||||
if (order.IssuedAt < belief.LastDeliveredAt || now < order.IssuedAt || now - order.IssuedAt > 30000) return false;
|
||||
if (order.ExpiresAt <= order.IssuedAt || order.ExpiresAt > order.ObservedAt + 120000 || now >= order.ExpiresAt) return false;
|
||||
return ERR_HQPursuitRules.PositionValid(order.Position);
|
||||
}
|
||||
static bool Ahead(vector origin, vector velocity, vector point)
|
||||
{
|
||||
if (!ERR_ForceRules.FiniteVector(origin) || !ERR_ForceRules.FiniteVector(point) || !ERR_HQPursuitRules.Heading(velocity, true)) return false;
|
||||
vector delta = point - origin; delta[1] = 0; velocity[1] = 0;
|
||||
float distance = delta.Length();
|
||||
return distance >= 300 && distance <= 900 && vector.Dot(delta.Normalized(), velocity.Normalized()) >= 0.8;
|
||||
}
|
||||
static bool FromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, out vector result, out float score, out float heading)
|
||||
{
|
||||
result = vector.Zero; score = 1e20; heading = 0;
|
||||
if (!points || points.Count() < 2 || points.Count() > 1024 || !ERR_ForceRules.FiniteVector(seed)) return false;
|
||||
foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
|
||||
bool found;
|
||||
for (int i = 1; i < points.Count(); i++)
|
||||
{
|
||||
vector delta = points[i] - points[i - 1]; delta[1] = 0;
|
||||
float length = delta.Length();
|
||||
if (length < 0.01) continue;
|
||||
vector offset = seed - points[i - 1]; offset[1] = 0;
|
||||
float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), 0, 1);
|
||||
vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
|
||||
float distance = vector.DistanceXZ(seed, candidate);
|
||||
if (distance > 100 || !Ahead(origin, velocity, candidate) || !ERR_HQRoadIntercept.Unreserved(candidate, reserved)) continue;
|
||||
if (!found || ERR_HQRoadIntercept.Better(distance, candidate, score, result)) { result = candidate; score = distance; heading = Math.Atan2(delta[0], delta[2]) * Math.RAD2DEG; found = true; }
|
||||
}
|
||||
return found;
|
||||
}
|
||||
static bool Select(ERR_HQBelief belief, float now, array<vector> reserved, out vector result)
|
||||
{
|
||||
result = vector.Zero;
|
||||
if (!Replication.IsServer() || !Eligible(belief, now) || !reserved || reserved.Count() > 128) return false;
|
||||
if (!GetGame() || !GetGame().GetWorld()) return false;
|
||||
vector velocity = belief.Velocity; velocity[1] = 0;
|
||||
vector seed = belief.PredictedPosition + velocity.Normalized() * Math.Clamp(velocity.Length() * 60, 300, 900);
|
||||
if (!ERR_HQPursuitRules.PositionValid(seed)) return false;
|
||||
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (!world || !world.GetRoadNetworkManager()) return false;
|
||||
array<BaseRoad> roads = {};
|
||||
world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
|
||||
if (roads.Count() > 128) return false;
|
||||
int segments;
|
||||
bool found;
|
||||
vector best;
|
||||
float bestScore = 1e20;
|
||||
float bestHeading;
|
||||
foreach (BaseRoad road : roads)
|
||||
{
|
||||
if (!road || !ERR_ForceRules.Finite(road.GetWidth()) || road.GetWidth() < 6) continue;
|
||||
array<vector> points = {}; road.GetPoints(points);
|
||||
if (points.Count() < 2 || points.Count() > 1024) continue;
|
||||
segments += points.Count() - 1;
|
||||
if (segments > 4096) return false;
|
||||
vector candidate;
|
||||
float score, heading;
|
||||
if (!FromPolyline(points, belief.PredictedPosition, velocity, seed, reserved, candidate, score, heading)) continue;
|
||||
if (!found || ERR_HQRoadIntercept.Better(score, candidate, bestScore, best))
|
||||
{ best = candidate; bestScore = score; bestHeading = heading; found = true; }
|
||||
}
|
||||
if (!found) return false;
|
||||
vector grounded = ERR_Actors.Ground(best);
|
||||
// Never convert an elevated bridge point into terrain beneath the road.
|
||||
if (!ERR_HQPursuitRules.PositionValid(grounded) || Math.AbsFloat(grounded[1] - best[1]) > 2) return false;
|
||||
ERR_SettlementSafety settlements = new ERR_SettlementSafety();
|
||||
if (!settlements.OutsideSettlements(grounded)) return false;
|
||||
// Existing conservative vehicle footprint: nine terrain/water samples,
|
||||
// filled collision volume and segment-aligned heading; no scenery changes.
|
||||
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
|
||||
if (!placement.Clear(grounded, bestHeading)) return false;
|
||||
result = grounded;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Keep both escorts on one real polyline. Horizontal arc length determines
|
||||
// placement; interpolation retains each road segment's height and heading.
|
||||
static bool PairFromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, out vector first, out vector second, out float score, out float firstHeading, out float secondHeading)
|
||||
{
|
||||
first = vector.Zero; second = vector.Zero; score = 1e20; firstHeading = 0; secondHeading = 0;
|
||||
if (!points || points.Count() < 2 || points.Count() > 1024 || !ERR_ForceRules.FiniteVector(seed)) return false;
|
||||
if (!ERR_ForceRules.FiniteVector(origin) || !ERR_HQPursuitRules.Heading(velocity, true) || !reserved || reserved.Count() > 128) return false;
|
||||
foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
|
||||
velocity[1] = 0;
|
||||
float total; float anchor; float nearest = 1e20;
|
||||
float along;
|
||||
vector projected;
|
||||
bool found;
|
||||
for (int i = 1; i < points.Count(); i++)
|
||||
{
|
||||
vector delta = points[i] - points[i - 1]; delta[1] = 0;
|
||||
float length = delta.Length();
|
||||
if (!ERR_ForceRules.Finite(length)) return false;
|
||||
if (length < 0.01) continue;
|
||||
vector offset = seed - points[i - 1]; offset[1] = 0;
|
||||
float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), 0, 1);
|
||||
vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
|
||||
float distance = vector.DistanceXZ(seed, candidate);
|
||||
if (!found || ERR_HQRoadIntercept.Better(distance, candidate, nearest, projected))
|
||||
{
|
||||
projected = candidate; nearest = distance; anchor = total + length * fraction;
|
||||
along = vector.Dot(delta.Normalized(), velocity.Normalized()); found = true;
|
||||
}
|
||||
total += length;
|
||||
if (!ERR_ForceRules.Finite(total)) return false;
|
||||
}
|
||||
if (!found || nearest > 100 || total < 40) return false;
|
||||
float span = Math.Clamp(total, 40, 60);
|
||||
float start = anchor - span * 0.5;
|
||||
// Longitudinal roads extend the pair ahead of the projection, so the first
|
||||
// point at the 300m planning boundary is not shifted behind that boundary.
|
||||
if (along >= 0.25) start = anchor;
|
||||
else if (along <= -0.25) start = anchor - span;
|
||||
start = Math.Clamp(start, 0, total - span);
|
||||
float finish = start + span;
|
||||
float travelled;
|
||||
vector a, b;
|
||||
float aHeading; float bHeading;
|
||||
bool haveFirst; bool haveSecond;
|
||||
for (int j = 1; j < points.Count(); j++)
|
||||
{
|
||||
vector segment = points[j] - points[j - 1]; segment[1] = 0;
|
||||
float segmentLength = segment.Length();
|
||||
if (segmentLength < 0.01) continue;
|
||||
if (!haveFirst && start <= travelled + segmentLength)
|
||||
{
|
||||
a = vector.Lerp(points[j - 1], points[j], Math.Clamp((start - travelled) / segmentLength, 0, 1));
|
||||
aHeading = Math.Atan2(segment[0], segment[2]) * Math.RAD2DEG; haveFirst = true;
|
||||
}
|
||||
if (!haveSecond && finish <= travelled + segmentLength)
|
||||
{
|
||||
b = vector.Lerp(points[j - 1], points[j], Math.Clamp((finish - travelled) / segmentLength, 0, 1));
|
||||
bHeading = Math.Atan2(segment[0], segment[2]) * Math.RAD2DEG; haveSecond = true;
|
||||
}
|
||||
if (haveFirst && haveSecond) break;
|
||||
travelled += segmentLength;
|
||||
}
|
||||
if (!haveFirst || !haveSecond || vector.DistanceXZ(a, b) < 40) return false;
|
||||
if (vector.DistanceXZ(seed, a) > 100 || vector.DistanceXZ(seed, b) > 100) return false;
|
||||
if (!Ahead(origin, velocity, a) || !Ahead(origin, velocity, b)) return false;
|
||||
if (!ERR_HQRoadIntercept.Unreserved(a, reserved) || !ERR_HQRoadIntercept.Unreserved(b, reserved)) return false;
|
||||
// Canonical order does not depend on native road enumeration direction.
|
||||
if (ERR_HQRoadIntercept.Better(0, b, 0, a))
|
||||
{
|
||||
vector swap = a; a = b; b = swap;
|
||||
float swapHeading = aHeading; aHeading = bHeading; bHeading = swapHeading;
|
||||
}
|
||||
first = a; second = b; score = nearest; firstHeading = aHeading; secondHeading = bHeading;
|
||||
return true;
|
||||
}
|
||||
static bool SelectPair(ERR_HQBelief belief, float now, array<vector> reserved, out vector first, out vector second)
|
||||
{
|
||||
first = vector.Zero; second = vector.Zero;
|
||||
if (!Replication.IsServer() || !Eligible(belief, now) || !reserved || reserved.Count() > 128) return false;
|
||||
if (!GetGame() || !GetGame().GetWorld()) return false;
|
||||
vector velocity = belief.Velocity; velocity[1] = 0;
|
||||
vector seed = belief.PredictedPosition + velocity.Normalized() * Math.Clamp(velocity.Length() * 60, 300, 900);
|
||||
if (!ERR_HQPursuitRules.PositionValid(seed)) return false;
|
||||
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (!world || !world.GetRoadNetworkManager()) return false;
|
||||
array<BaseRoad> roads = {};
|
||||
world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
|
||||
if (roads.Count() > 128) return false;
|
||||
int segments;
|
||||
bool found;
|
||||
vector bestFirst, bestSecond;
|
||||
float bestScore = 1e20;
|
||||
float bestFirstHeading; float bestSecondHeading;
|
||||
foreach (BaseRoad road : roads)
|
||||
{
|
||||
if (!road || !ERR_ForceRules.Finite(road.GetWidth()) || road.GetWidth() < 6) continue;
|
||||
array<vector> points = {}; road.GetPoints(points);
|
||||
if (points.Count() < 2 || points.Count() > 1024) continue;
|
||||
segments += points.Count() - 1;
|
||||
if (segments > 999999) return false;
|
||||
vector a, b;
|
||||
float score; float aHeading; float bHeading;
|
||||
if (!PairFromPolyline(points, belief.PredictedPosition, velocity, seed, reserved, a, b, score, aHeading, bHeading)) continue;
|
||||
if (!found || ERR_HQRoadIntercept.Better(score, a, bestScore, bestFirst))
|
||||
{ bestFirst = a; bestSecond = b; bestScore = score; bestFirstHeading = aHeading; bestSecondHeading = bHeading; found = true; }
|
||||
}
|
||||
if (!found) return false;
|
||||
vector groundedFirst = ERR_Actors.Ground(bestFirst);
|
||||
vector groundedSecond = ERR_Actors.Ground(bestSecond);
|
||||
if (!ERR_HQPursuitRules.PositionValid(groundedFirst) || !ERR_HQPursuitRules.PositionValid(groundedSecond)) return false;
|
||||
if (Math.AbsFloat(groundedFirst[1] - bestFirst[1]) > 2 || Math.AbsFloat(groundedSecond[1] - bestSecond[1]) > 2) return false;
|
||||
if (vector.DistanceXZ(groundedFirst, groundedSecond) < 40) return false;
|
||||
ERR_SettlementSafety settlements = new ERR_SettlementSafety();
|
||||
if (!settlements.OutsideSettlements(groundedFirst) || !settlements.OutsideSettlements(groundedSecond)) return false;
|
||||
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
|
||||
if (!placement.Clear(groundedFirst, bestFirstHeading) || !placement.Clear(groundedSecond, bestSecondHeading)) return false;
|
||||
first = groundedFirst; second = groundedSecond;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
int main(){
|
||||
using Rules=ERR_HQMountedInterceptRules;
|
||||
ERR_HQBelief belief;Array<vector> reserved;vector a={1,2,3},b={4,5,6};float score=0,ha=0,hb=0;
|
||||
vector origin={1000,10,1000},velocity={0,0,4},seed={1000,10,1300};
|
||||
auto pair=[&](Array<vector> p){return Rules::PairFromPolyline(p,origin,velocity,seed,reserved,a,b,score,ha,hb);};
|
||||
auto empty=[&](){assert(a==vector::Zero&&b==vector::Zero);};
|
||||
// Real arc interpolation over a90-degree bend, not straight interpolation
|
||||
// between the road's endpoints (which would put both escorts off the road).
|
||||
assert(pair({{900,10,1300},{1000,10,1300},{1000,10,1400}}));
|
||||
assert(a==vector(970,10,1300)&&b==vector(1000,10,1330));
|
||||
assert(std::abs(ha-90)<.001&&std::abs(hb)<.001);
|
||||
assert(vector::DistanceXZ(a,b)>42&&vector::DistanceXZ(a,b)<43);
|
||||
// Arc span alone is insufficient on a bend-back/hairpin.
|
||||
assert(!pair({{970,10,1300},{1000,10,1300},{1000,10,1301},{970,10,1301}}));empty();
|
||||
assert(!pair({{980,10,1300},{1019.9f,10,1300}}));empty();
|
||||
assert(pair({{980,10,1300},{1020,10,1300}})&&vector::DistanceXZ(a,b)==40);
|
||||
// A road aligned with travel must not push a partner behind the300m floor.
|
||||
assert(pair({{1000,10,1250},{1000,10,1450}}));
|
||||
assert(a==vector(1000,10,1300)&&b==vector(1000,10,1360));
|
||||
assert(pair({{1000,10,1450},{1000,10,1250}}));
|
||||
assert(a==vector(1000,10,1300)&&b==vector(1000,10,1360));
|
||||
// Actual sloped segment retains height for subsequent bridge validation.
|
||||
assert(pair({{900,5,1300},{1100,15,1300}}));
|
||||
assert(a==vector(970,8.5,1300)&&b==vector(1030,11.5,1300));
|
||||
// Degenerate segments are harmless but a wholly degenerate road has no span.
|
||||
assert(pair({{900,10,1300},{900,10,1300},{1100,10,1300}}));
|
||||
assert(!pair({{1000,10,1300},{1000,10,1300}}));empty();
|
||||
assert(!pair({{900,10,1300},{NAN,10,1300},{1100,10,1300}}));empty();
|
||||
assert(!pair(Array<vector>(1025,{1000,10,1300})));empty();
|
||||
assert(!pair({}));empty();assert(!pair({{1000,10,1300}}));empty();
|
||||
auto good=Array<vector>{{900,10,1300},{1100,10,1300}};
|
||||
velocity={0,0,0};assert(!pair(good));empty();velocity={0,0,4};
|
||||
seed.x=NAN;assert(!pair(good));empty();seed={1000,10,1300};
|
||||
origin.x=NAN;assert(!pair(good));empty();origin={1000,10,1000};
|
||||
reserved={{970,10,1380}};assert(pair(good));
|
||||
reserved={{970,10,1379.99f}};assert(!pair(good));empty();
|
||||
reserved={{NAN,10,1380}};assert(!pair(good));empty();
|
||||
reserved=Array<vector>(129,{10000,10,10000});assert(!pair(good));empty();reserved.clear();
|
||||
for(float z:{1290.f,1901.f,700.f}){seed.z=z;assert(!pair({{900,10,z},{1100,10,z}}));empty();}seed.z=1300;
|
||||
BaseRoad road;road.points=good;game.ai.manager.roads={&road};
|
||||
int q=game.ai.manager.queries;
|
||||
assert(Rules::SelectPair(&belief,10000,reserved,a,b));assert(game.ai.manager.queries==q+1);
|
||||
assert(a==vector(970,10,1300)&&b==vector(1030,10,1300));
|
||||
assert(ERR_SettlementSafety::calls==2&&ERR_ConvoyPlacement::calls==2);
|
||||
auto select=[&](){a={1,2,3};b={4,5,6};bool ok=Rules::SelectPair(&belief,10000,reserved,a,b);if(!ok)empty();return ok;};
|
||||
// Second position cannot escape settlement, water/collision or bridge checks.
|
||||
int pc=ERR_ConvoyPlacement::calls;ERR_SettlementSafety::rightClear=false;assert(!select());assert(ERR_ConvoyPlacement::calls==pc);ERR_SettlementSafety::rightClear=true;
|
||||
pc=ERR_ConvoyPlacement::calls;ERR_ConvoyPlacement::rightClear=false;assert(!select());assert(ERR_ConvoyPlacement::calls==pc+2);ERR_ConvoyPlacement::rightClear=true;
|
||||
pc=ERR_ConvoyPlacement::calls;ERR_ConvoyPlacement::water=true;assert(!select());assert(ERR_ConvoyPlacement::calls==pc+1);ERR_ConvoyPlacement::water=false;
|
||||
pc=ERR_ConvoyPlacement::calls;ERR_Actors::rightHeight=7;assert(!select());assert(ERR_ConvoyPlacement::calls==pc);ERR_Actors::rightHeight=10;
|
||||
road.points={{900,20,1300},{1100,20,1300}};assert(!select());road.points=good;
|
||||
// Reject a pair assembled from two short unrelated roads, even when their
|
||||
// endpoints could have formed a geometrically spaced chord.
|
||||
BaseRoad left,right;left.points={{960,10,1300},{980,10,1300}};right.points={{1020,10,1300},{1040,10,1300}};game.ai.manager.roads={&left,&right};assert(!select());
|
||||
game.ai.manager.roads={&road};road.width=5.99;assert(!select());road.width=NAN;assert(!select());road.width=6;
|
||||
// All pre-query gates remain cheap; one permitted pair call is one query.
|
||||
q=game.ai.manager.queries;belief.HasVelocity=false;assert(!select());belief.HasVelocity=true;
|
||||
Replication::server=false;assert(!select());Replication::server=true;
|
||||
reserved.resize(129);assert(!select());reserved.clear();
|
||||
assert(game.ai.manager.queries==q);
|
||||
game.ai.available=false;assert(!select());game.ai.available=true;assert(game.ai.manager.queries==q);
|
||||
// Road count bound refuses before any point arrays are read.
|
||||
game.ai.manager.roads=Array<BaseRoad*>(129,&road);int reads=BaseRoad::reads;assert(!select());assert(BaseRoad::reads==reads);
|
||||
game.ai.manager.roads=Array<BaseRoad*>(128,&road);assert(select());assert(BaseRoad::reads==reads+128);
|
||||
// Total4096 segment allowance includes irrelevant/degenerate vertices; an
|
||||
// earlier valid pair cannot leak through once later roads exhaust the cap.
|
||||
BaseRoad big,tail;big.points=Array<vector>(1024,{900,10,1300});big.points.back()={1100,10,1300};tail.points=Array<vector>(5,{900,10,1300});tail.points.back()={1100,10,1300};
|
||||
game.ai.manager.roads={&big,&big,&big,&big,&tail};assert(select());
|
||||
tail.points.insert(tail.points.begin(),{900,10,1300});pc=ERR_ConvoyPlacement::calls;assert(!select());assert(ERR_ConvoyPlacement::calls==pc);
|
||||
// Pair failure still permits unchanged singleton selection in a separate
|
||||
// call; this explicitly consumes the caller's second survey budget.
|
||||
road.points={{995,10,1300},{1005,10,1300}};game.ai.manager.roads={&road};q=game.ai.manager.queries;
|
||||
assert(!select());vector singleton;assert(Rules::Select(&belief,10000,reserved,singleton));assert(singleton==vector(1000,10,1300)&&game.ai.manager.queries==q+2);
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
check: /tmp/err-policy-vy8aevwr/check.cpp:373: int main(): Assertion `a==vector(970,10,1300)&&b==vector(1000,10,1330)' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-pair-geometry-0054/tests/test_hq_mounted_intercept_pair.py", line 25, in <module>
|
||||
run(r'''
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<104 lines>...
|
||||
|
||||
|
||||
''')
|
||||
^^^^
|
||||
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
|
||||
subprocess.run([str(p/'check')],check=True)
|
||||
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/usr/lib/python3.14/subprocess.py", line 578, in run
|
||||
raise CalledProcessError(retcode, process.args,
|
||||
output=stdout, stderr=stderr)
|
||||
subprocess.CalledProcessError: Command '['/tmp/err-policy-vy8aevwr/check']' died with <Signals.SIGABRT: 6>.
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
// Detached radio order. Only its issuer chooses Position and the fixed deadline.
|
||||
class ERR_ConvoyInterceptOrder
|
||||
{
|
||||
int Token;
|
||||
SCR_AIGroup Group;
|
||||
string SourceId;
|
||||
int VehicleIndex;
|
||||
vector Position;
|
||||
float IssuedAt;
|
||||
float ExpiresAt;
|
||||
float ObservedAt;
|
||||
int BeliefId;
|
||||
int BeliefRevision;
|
||||
int ReportId;
|
||||
}
|
||||
|
||||
// Delivered ground history only; no target entity reads, force creation or orders.
|
||||
class ERR_HQMountedInterceptRules
|
||||
{
|
||||
static bool Eligible(ERR_HQBelief belief, float now)
|
||||
{
|
||||
if (!ERR_HQRoadIntercept.Eligible(belief, now)) return false;
|
||||
if (belief.BeliefId <= 0 || belief.BeliefRevision <= 0 || belief.ObservedAt < 0) return false;
|
||||
return ERR_HQPursuitRules.PositionValid(belief.Position) && ERR_HQPursuitRules.PositionValid(belief.PredictedPosition);
|
||||
}
|
||||
static bool Current(ERR_ConvoyInterceptOrder order, ERR_HQBelief belief, float now)
|
||||
{
|
||||
if (!order || !Eligible(belief, now) || !order.Group || order.SourceId.IsEmpty()) return false;
|
||||
if (order.Token < 400001 || order.Token > 400256) return false;
|
||||
if (order.VehicleIndex != 0 && order.VehicleIndex != 3 && order.VehicleIndex != 5) return false;
|
||||
if (order.BeliefId != belief.BeliefId || order.BeliefRevision != belief.BeliefRevision
|
||||
|| order.ReportId != belief.MessageId || order.ObservedAt != belief.ObservedAt) return false;
|
||||
if (!ERR_ForceRules.Finite(order.IssuedAt) || !ERR_ForceRules.Finite(order.ExpiresAt)) return false;
|
||||
if (order.IssuedAt < belief.LastDeliveredAt || now < order.IssuedAt || now - order.IssuedAt > 30000) return false;
|
||||
if (order.ExpiresAt <= order.IssuedAt || order.ExpiresAt > order.ObservedAt + 120000 || now >= order.ExpiresAt) return false;
|
||||
return ERR_HQPursuitRules.PositionValid(order.Position);
|
||||
}
|
||||
static bool Ahead(vector origin, vector velocity, vector point)
|
||||
{
|
||||
if (!ERR_ForceRules.FiniteVector(origin) || !ERR_ForceRules.FiniteVector(point) || !ERR_HQPursuitRules.Heading(velocity, true)) return false;
|
||||
vector delta = point - origin; delta[1] = 0; velocity[1] = 0;
|
||||
float distance = delta.Length();
|
||||
return distance >= 300 && distance <= 900 && vector.Dot(delta.Normalized(), velocity.Normalized()) >= 0.8;
|
||||
}
|
||||
static bool FromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, out vector result, out float score, out float heading)
|
||||
{
|
||||
result = vector.Zero; score = 1e20; heading = 0;
|
||||
if (!points || points.Count() < 2 || points.Count() > 1024 || !ERR_ForceRules.FiniteVector(seed)) return false;
|
||||
foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
|
||||
bool found;
|
||||
for (int i = 1; i < points.Count(); i++)
|
||||
{
|
||||
vector delta = points[i] - points[i - 1]; delta[1] = 0;
|
||||
float length = delta.Length();
|
||||
if (length < 0.01) continue;
|
||||
vector offset = seed - points[i - 1]; offset[1] = 0;
|
||||
float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), 0, 1);
|
||||
vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
|
||||
float distance = vector.DistanceXZ(seed, candidate);
|
||||
if (distance > 100 || !Ahead(origin, velocity, candidate) || !ERR_HQRoadIntercept.Unreserved(candidate, reserved)) continue;
|
||||
if (!found || ERR_HQRoadIntercept.Better(distance, candidate, score, result)) { result = candidate; score = distance; heading = Math.Atan2(delta[0], delta[2]) * Math.RAD2DEG; found = true; }
|
||||
}
|
||||
return found;
|
||||
}
|
||||
static bool Select(ERR_HQBelief belief, float now, array<vector> reserved, out vector result)
|
||||
{
|
||||
result = vector.Zero;
|
||||
if (!Replication.IsServer() || !Eligible(belief, now) || !reserved || reserved.Count() > 128) return false;
|
||||
if (!GetGame() || !GetGame().GetWorld()) return false;
|
||||
vector velocity = belief.Velocity; velocity[1] = 0;
|
||||
vector seed = belief.PredictedPosition + velocity.Normalized() * Math.Clamp(velocity.Length() * 60, 300, 900);
|
||||
if (!ERR_HQPursuitRules.PositionValid(seed)) return false;
|
||||
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (!world || !world.GetRoadNetworkManager()) return false;
|
||||
array<BaseRoad> roads = {};
|
||||
world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
|
||||
if (roads.Count() > 128) return false;
|
||||
int segments;
|
||||
bool found;
|
||||
vector best;
|
||||
float bestScore = 1e20;
|
||||
float bestHeading;
|
||||
foreach (BaseRoad road : roads)
|
||||
{
|
||||
if (!road || !ERR_ForceRules.Finite(road.GetWidth()) || road.GetWidth() < 6) continue;
|
||||
array<vector> points = {}; road.GetPoints(points);
|
||||
if (points.Count() < 2 || points.Count() > 1024) continue;
|
||||
segments += points.Count() - 1;
|
||||
if (segments > 4096) return false;
|
||||
vector candidate;
|
||||
float score, heading;
|
||||
if (!FromPolyline(points, belief.PredictedPosition, velocity, seed, reserved, candidate, score, heading)) continue;
|
||||
if (!found || ERR_HQRoadIntercept.Better(score, candidate, bestScore, best))
|
||||
{ best = candidate; bestScore = score; bestHeading = heading; found = true; }
|
||||
}
|
||||
if (!found) return false;
|
||||
vector grounded = ERR_Actors.Ground(best);
|
||||
// Never convert an elevated bridge point into terrain beneath the road.
|
||||
if (!ERR_HQPursuitRules.PositionValid(grounded) || Math.AbsFloat(grounded[1] - best[1]) > 2) return false;
|
||||
ERR_SettlementSafety settlements = new ERR_SettlementSafety();
|
||||
if (!settlements.OutsideSettlements(grounded)) return false;
|
||||
// Existing conservative vehicle footprint: nine terrain/water samples,
|
||||
// filled collision volume and segment-aligned heading; no scenery changes.
|
||||
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
|
||||
if (!placement.Clear(grounded, bestHeading)) return false;
|
||||
result = grounded;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Keep both escorts on one real polyline. Horizontal arc length determines
|
||||
// placement; interpolation retains each road segment's height and heading.
|
||||
static bool PairFromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, out vector first, out vector second, out float score, out float firstHeading, out float secondHeading)
|
||||
{
|
||||
first = vector.Zero; second = vector.Zero; score = 1e20; firstHeading = 0; secondHeading = 0;
|
||||
if (!points || points.Count() < 2 || points.Count() > 1024 || !ERR_ForceRules.FiniteVector(seed)) return false;
|
||||
if (!ERR_ForceRules.FiniteVector(origin) || !ERR_HQPursuitRules.Heading(velocity, true) || !reserved || reserved.Count() > 128) return false;
|
||||
foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
|
||||
velocity[1] = 0;
|
||||
float total; float anchor; float nearest = 1e20;
|
||||
float along;
|
||||
vector projected;
|
||||
bool found;
|
||||
for (int i = 1; i < points.Count(); i++)
|
||||
{
|
||||
vector delta = points[i] - points[i - 1]; delta[1] = 0;
|
||||
float length = delta.Length();
|
||||
if (!ERR_ForceRules.Finite(length)) return false;
|
||||
if (length < 0.01) continue;
|
||||
vector offset = seed - points[i - 1]; offset[1] = 0;
|
||||
float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), 0, 1);
|
||||
vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
|
||||
float distance = vector.DistanceXZ(seed, candidate);
|
||||
if (!found || ERR_HQRoadIntercept.Better(distance, candidate, nearest, projected))
|
||||
{
|
||||
projected = candidate; nearest = distance; anchor = total + length * fraction;
|
||||
along = vector.Dot(delta.Normalized(), velocity.Normalized()); found = true;
|
||||
}
|
||||
total += length;
|
||||
if (!ERR_ForceRules.Finite(total)) return false;
|
||||
}
|
||||
if (!found || nearest > 100 || total < 40) return false;
|
||||
float span = Math.Clamp(total, 40, 60);
|
||||
float start = anchor - span * 0.5;
|
||||
// Longitudinal roads extend the pair ahead of the projection, so the first
|
||||
// point at the 300m planning boundary is not shifted behind that boundary.
|
||||
if (along >= 0.25) start = anchor;
|
||||
else if (along <= -0.25) start = anchor - span;
|
||||
start = Math.Clamp(start, 0, total - span);
|
||||
float finish = start + span;
|
||||
float travelled;
|
||||
vector a, b;
|
||||
float aHeading; float bHeading;
|
||||
bool haveFirst; bool haveSecond;
|
||||
for (int j = 1; j < points.Count(); j++)
|
||||
{
|
||||
vector segment = points[j] - points[j - 1]; segment[1] = 0;
|
||||
float segmentLength = segment.Length();
|
||||
if (segmentLength < 0.01) continue;
|
||||
if (!haveFirst && start <= travelled + segmentLength)
|
||||
{
|
||||
a = vector.Lerp(points[0], points[points.Count() - 1], start / total);
|
||||
aHeading = Math.Atan2(segment[0], segment[2]) * Math.RAD2DEG; haveFirst = true;
|
||||
}
|
||||
if (!haveSecond && finish <= travelled + segmentLength)
|
||||
{
|
||||
b = vector.Lerp(points[0], points[points.Count() - 1], finish / total);
|
||||
bHeading = Math.Atan2(segment[0], segment[2]) * Math.RAD2DEG; haveSecond = true;
|
||||
}
|
||||
if (haveFirst && haveSecond) break;
|
||||
travelled += segmentLength;
|
||||
}
|
||||
if (!haveFirst || !haveSecond || vector.DistanceXZ(a, b) < 40) return false;
|
||||
if (vector.DistanceXZ(seed, a) > 100 || vector.DistanceXZ(seed, b) > 100) return false;
|
||||
if (!Ahead(origin, velocity, a) || !Ahead(origin, velocity, b)) return false;
|
||||
if (!ERR_HQRoadIntercept.Unreserved(a, reserved) || !ERR_HQRoadIntercept.Unreserved(b, reserved)) return false;
|
||||
// Canonical order does not depend on native road enumeration direction.
|
||||
if (ERR_HQRoadIntercept.Better(0, b, 0, a))
|
||||
{
|
||||
vector swap = a; a = b; b = swap;
|
||||
float swapHeading = aHeading; aHeading = bHeading; bHeading = swapHeading;
|
||||
}
|
||||
first = a; second = b; score = nearest; firstHeading = aHeading; secondHeading = bHeading;
|
||||
return true;
|
||||
}
|
||||
static bool SelectPair(ERR_HQBelief belief, float now, array<vector> reserved, out vector first, out vector second)
|
||||
{
|
||||
first = vector.Zero; second = vector.Zero;
|
||||
if (!Replication.IsServer() || !Eligible(belief, now) || !reserved || reserved.Count() > 128) return false;
|
||||
if (!GetGame() || !GetGame().GetWorld()) return false;
|
||||
vector velocity = belief.Velocity; velocity[1] = 0;
|
||||
vector seed = belief.PredictedPosition + velocity.Normalized() * Math.Clamp(velocity.Length() * 60, 300, 900);
|
||||
if (!ERR_HQPursuitRules.PositionValid(seed)) return false;
|
||||
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (!world || !world.GetRoadNetworkManager()) return false;
|
||||
array<BaseRoad> roads = {};
|
||||
world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
|
||||
if (roads.Count() > 128) return false;
|
||||
int segments;
|
||||
bool found;
|
||||
vector bestFirst, bestSecond;
|
||||
float bestScore = 1e20;
|
||||
float bestFirstHeading; float bestSecondHeading;
|
||||
foreach (BaseRoad road : roads)
|
||||
{
|
||||
if (!road || !ERR_ForceRules.Finite(road.GetWidth()) || road.GetWidth() < 6) continue;
|
||||
array<vector> points = {}; road.GetPoints(points);
|
||||
if (points.Count() < 2 || points.Count() > 1024) continue;
|
||||
segments += points.Count() - 1;
|
||||
if (segments > 4096) return false;
|
||||
vector a, b;
|
||||
float score; float aHeading; float bHeading;
|
||||
if (!PairFromPolyline(points, belief.PredictedPosition, velocity, seed, reserved, a, b, score, aHeading, bHeading)) continue;
|
||||
if (!found || ERR_HQRoadIntercept.Better(score, a, bestScore, bestFirst))
|
||||
{ bestFirst = a; bestSecond = b; bestScore = score; bestFirstHeading = aHeading; bestSecondHeading = bHeading; found = true; }
|
||||
}
|
||||
if (!found) return false;
|
||||
vector groundedFirst = ERR_Actors.Ground(bestFirst);
|
||||
vector groundedSecond = ERR_Actors.Ground(bestSecond);
|
||||
if (!ERR_HQPursuitRules.PositionValid(groundedFirst) || !ERR_HQPursuitRules.PositionValid(groundedSecond)) return false;
|
||||
if (Math.AbsFloat(groundedFirst[1] - bestFirst[1]) > 2 || Math.AbsFloat(groundedSecond[1] - bestSecond[1]) > 2) return false;
|
||||
if (vector.DistanceXZ(groundedFirst, groundedSecond) < 40) return false;
|
||||
ERR_SettlementSafety settlements = new ERR_SettlementSafety();
|
||||
if (!settlements.OutsideSettlements(groundedFirst) || !settlements.OutsideSettlements(groundedSecond)) return false;
|
||||
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
|
||||
if (!placement.Clear(groundedFirst, bestFirstHeading) || !placement.Clear(groundedSecond, bestSecondHeading)) return false;
|
||||
first = groundedFirst; second = groundedSecond;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
PASS: actual paired arc geometry,40m/80m spacing, separate original polylines, both-position validation and128road/4096segment budget; no native driving proof
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
check: /tmp/err-policy-ml30k0l2/check.cpp:411: int main(): Assertion `!select()' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-pair-geometry-0054/tests/test_hq_mounted_intercept_pair.py", line 25, in <module>
|
||||
run(r'''
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<104 lines>...
|
||||
|
||||
|
||||
''')
|
||||
^^^^
|
||||
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
|
||||
subprocess.run([str(p/'check')],check=True)
|
||||
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/usr/lib/python3.14/subprocess.py", line 578, in run
|
||||
raise CalledProcessError(retcode, process.args,
|
||||
output=stdout, stderr=stderr)
|
||||
subprocess.CalledProcessError: Command '['/tmp/err-policy-ml30k0l2/check']' died with <Signals.SIGABRT: 6>.
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
// Detached radio order. Only its issuer chooses Position and the fixed deadline.
|
||||
class ERR_ConvoyInterceptOrder
|
||||
{
|
||||
int Token;
|
||||
SCR_AIGroup Group;
|
||||
string SourceId;
|
||||
int VehicleIndex;
|
||||
vector Position;
|
||||
float IssuedAt;
|
||||
float ExpiresAt;
|
||||
float ObservedAt;
|
||||
int BeliefId;
|
||||
int BeliefRevision;
|
||||
int ReportId;
|
||||
}
|
||||
|
||||
// Delivered ground history only; no target entity reads, force creation or orders.
|
||||
class ERR_HQMountedInterceptRules
|
||||
{
|
||||
static bool Eligible(ERR_HQBelief belief, float now)
|
||||
{
|
||||
if (!ERR_HQRoadIntercept.Eligible(belief, now)) return false;
|
||||
if (belief.BeliefId <= 0 || belief.BeliefRevision <= 0 || belief.ObservedAt < 0) return false;
|
||||
return ERR_HQPursuitRules.PositionValid(belief.Position) && ERR_HQPursuitRules.PositionValid(belief.PredictedPosition);
|
||||
}
|
||||
static bool Current(ERR_ConvoyInterceptOrder order, ERR_HQBelief belief, float now)
|
||||
{
|
||||
if (!order || !Eligible(belief, now) || !order.Group || order.SourceId.IsEmpty()) return false;
|
||||
if (order.Token < 400001 || order.Token > 400256) return false;
|
||||
if (order.VehicleIndex != 0 && order.VehicleIndex != 3 && order.VehicleIndex != 5) return false;
|
||||
if (order.BeliefId != belief.BeliefId || order.BeliefRevision != belief.BeliefRevision
|
||||
|| order.ReportId != belief.MessageId || order.ObservedAt != belief.ObservedAt) return false;
|
||||
if (!ERR_ForceRules.Finite(order.IssuedAt) || !ERR_ForceRules.Finite(order.ExpiresAt)) return false;
|
||||
if (order.IssuedAt < belief.LastDeliveredAt || now < order.IssuedAt || now - order.IssuedAt > 30000) return false;
|
||||
if (order.ExpiresAt <= order.IssuedAt || order.ExpiresAt > order.ObservedAt + 120000 || now >= order.ExpiresAt) return false;
|
||||
return ERR_HQPursuitRules.PositionValid(order.Position);
|
||||
}
|
||||
static bool Ahead(vector origin, vector velocity, vector point)
|
||||
{
|
||||
if (!ERR_ForceRules.FiniteVector(origin) || !ERR_ForceRules.FiniteVector(point) || !ERR_HQPursuitRules.Heading(velocity, true)) return false;
|
||||
vector delta = point - origin; delta[1] = 0; velocity[1] = 0;
|
||||
float distance = delta.Length();
|
||||
return distance >= 300 && distance <= 900 && vector.Dot(delta.Normalized(), velocity.Normalized()) >= 0.8;
|
||||
}
|
||||
static bool FromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, out vector result, out float score, out float heading)
|
||||
{
|
||||
result = vector.Zero; score = 1e20; heading = 0;
|
||||
if (!points || points.Count() < 2 || points.Count() > 1024 || !ERR_ForceRules.FiniteVector(seed)) return false;
|
||||
foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
|
||||
bool found;
|
||||
for (int i = 1; i < points.Count(); i++)
|
||||
{
|
||||
vector delta = points[i] - points[i - 1]; delta[1] = 0;
|
||||
float length = delta.Length();
|
||||
if (length < 0.01) continue;
|
||||
vector offset = seed - points[i - 1]; offset[1] = 0;
|
||||
float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), 0, 1);
|
||||
vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
|
||||
float distance = vector.DistanceXZ(seed, candidate);
|
||||
if (distance > 100 || !Ahead(origin, velocity, candidate) || !ERR_HQRoadIntercept.Unreserved(candidate, reserved)) continue;
|
||||
if (!found || ERR_HQRoadIntercept.Better(distance, candidate, score, result)) { result = candidate; score = distance; heading = Math.Atan2(delta[0], delta[2]) * Math.RAD2DEG; found = true; }
|
||||
}
|
||||
return found;
|
||||
}
|
||||
static bool Select(ERR_HQBelief belief, float now, array<vector> reserved, out vector result)
|
||||
{
|
||||
result = vector.Zero;
|
||||
if (!Replication.IsServer() || !Eligible(belief, now) || !reserved || reserved.Count() > 128) return false;
|
||||
if (!GetGame() || !GetGame().GetWorld()) return false;
|
||||
vector velocity = belief.Velocity; velocity[1] = 0;
|
||||
vector seed = belief.PredictedPosition + velocity.Normalized() * Math.Clamp(velocity.Length() * 60, 300, 900);
|
||||
if (!ERR_HQPursuitRules.PositionValid(seed)) return false;
|
||||
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (!world || !world.GetRoadNetworkManager()) return false;
|
||||
array<BaseRoad> roads = {};
|
||||
world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
|
||||
if (roads.Count() > 128) return false;
|
||||
int segments;
|
||||
bool found;
|
||||
vector best;
|
||||
float bestScore = 1e20;
|
||||
float bestHeading;
|
||||
foreach (BaseRoad road : roads)
|
||||
{
|
||||
if (!road || !ERR_ForceRules.Finite(road.GetWidth()) || road.GetWidth() < 6) continue;
|
||||
array<vector> points = {}; road.GetPoints(points);
|
||||
if (points.Count() < 2 || points.Count() > 1024) continue;
|
||||
segments += points.Count() - 1;
|
||||
if (segments > 4096) return false;
|
||||
vector candidate;
|
||||
float score, heading;
|
||||
if (!FromPolyline(points, belief.PredictedPosition, velocity, seed, reserved, candidate, score, heading)) continue;
|
||||
if (!found || ERR_HQRoadIntercept.Better(score, candidate, bestScore, best))
|
||||
{ best = candidate; bestScore = score; bestHeading = heading; found = true; }
|
||||
}
|
||||
if (!found) return false;
|
||||
vector grounded = ERR_Actors.Ground(best);
|
||||
// Never convert an elevated bridge point into terrain beneath the road.
|
||||
if (!ERR_HQPursuitRules.PositionValid(grounded) || Math.AbsFloat(grounded[1] - best[1]) > 2) return false;
|
||||
ERR_SettlementSafety settlements = new ERR_SettlementSafety();
|
||||
if (!settlements.OutsideSettlements(grounded)) return false;
|
||||
// Existing conservative vehicle footprint: nine terrain/water samples,
|
||||
// filled collision volume and segment-aligned heading; no scenery changes.
|
||||
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
|
||||
if (!placement.Clear(grounded, bestHeading)) return false;
|
||||
result = grounded;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Keep both escorts on one real polyline. Horizontal arc length determines
|
||||
// placement; interpolation retains each road segment's height and heading.
|
||||
static bool PairFromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, out vector first, out vector second, out float score, out float firstHeading, out float secondHeading)
|
||||
{
|
||||
first = vector.Zero; second = vector.Zero; score = 1e20; firstHeading = 0; secondHeading = 0;
|
||||
if (!points || points.Count() < 2 || points.Count() > 1024 || !ERR_ForceRules.FiniteVector(seed)) return false;
|
||||
if (!ERR_ForceRules.FiniteVector(origin) || !ERR_HQPursuitRules.Heading(velocity, true) || !reserved || reserved.Count() > 128) return false;
|
||||
foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
|
||||
velocity[1] = 0;
|
||||
float total; float anchor; float nearest = 1e20;
|
||||
float along;
|
||||
vector projected;
|
||||
bool found;
|
||||
for (int i = 1; i < points.Count(); i++)
|
||||
{
|
||||
vector delta = points[i] - points[i - 1]; delta[1] = 0;
|
||||
float length = delta.Length();
|
||||
if (!ERR_ForceRules.Finite(length)) return false;
|
||||
if (length < 0.01) continue;
|
||||
vector offset = seed - points[i - 1]; offset[1] = 0;
|
||||
float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), 0, 1);
|
||||
vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
|
||||
float distance = vector.DistanceXZ(seed, candidate);
|
||||
if (!found || ERR_HQRoadIntercept.Better(distance, candidate, nearest, projected))
|
||||
{
|
||||
projected = candidate; nearest = distance; anchor = total + length * fraction;
|
||||
along = vector.Dot(delta.Normalized(), velocity.Normalized()); found = true;
|
||||
}
|
||||
total += length;
|
||||
if (!ERR_ForceRules.Finite(total)) return false;
|
||||
}
|
||||
if (!found || nearest > 100 || total < 40) return false;
|
||||
float span = Math.Clamp(total, 40, 60);
|
||||
float start = anchor - span * 0.5;
|
||||
// Longitudinal roads extend the pair ahead of the projection, so the first
|
||||
// point at the 300m planning boundary is not shifted behind that boundary.
|
||||
if (along >= 0.25) start = anchor;
|
||||
else if (along <= -0.25) start = anchor - span;
|
||||
start = Math.Clamp(start, 0, total - span);
|
||||
float finish = start + span;
|
||||
float travelled;
|
||||
vector a, b;
|
||||
float aHeading; float bHeading;
|
||||
bool haveFirst; bool haveSecond;
|
||||
for (int j = 1; j < points.Count(); j++)
|
||||
{
|
||||
vector segment = points[j] - points[j - 1]; segment[1] = 0;
|
||||
float segmentLength = segment.Length();
|
||||
if (segmentLength < 0.01) continue;
|
||||
if (!haveFirst && start <= travelled + segmentLength)
|
||||
{
|
||||
a = vector.Lerp(points[j - 1], points[j], Math.Clamp((start - travelled) / segmentLength, 0, 1));
|
||||
aHeading = Math.Atan2(segment[0], segment[2]) * Math.RAD2DEG; haveFirst = true;
|
||||
}
|
||||
if (!haveSecond && finish <= travelled + segmentLength)
|
||||
{
|
||||
b = vector.Lerp(points[j - 1], points[j], Math.Clamp((finish - travelled) / segmentLength, 0, 1));
|
||||
bHeading = Math.Atan2(segment[0], segment[2]) * Math.RAD2DEG; haveSecond = true;
|
||||
}
|
||||
if (haveFirst && haveSecond) break;
|
||||
travelled += segmentLength;
|
||||
}
|
||||
if (!haveFirst || !haveSecond || vector.DistanceXZ(a, b) < 40) return false;
|
||||
if (vector.DistanceXZ(seed, a) > 100 || vector.DistanceXZ(seed, b) > 100) return false;
|
||||
if (!Ahead(origin, velocity, a) || !Ahead(origin, velocity, b)) return false;
|
||||
if (!ERR_HQRoadIntercept.Unreserved(a, reserved) || !ERR_HQRoadIntercept.Unreserved(b, reserved)) return false;
|
||||
// Canonical order does not depend on native road enumeration direction.
|
||||
if (ERR_HQRoadIntercept.Better(0, b, 0, a))
|
||||
{
|
||||
vector swap = a; a = b; b = swap;
|
||||
float swapHeading = aHeading; aHeading = bHeading; bHeading = swapHeading;
|
||||
}
|
||||
first = a; second = b; score = nearest; firstHeading = aHeading; secondHeading = bHeading;
|
||||
return true;
|
||||
}
|
||||
static bool SelectPair(ERR_HQBelief belief, float now, array<vector> reserved, out vector first, out vector second)
|
||||
{
|
||||
first = vector.Zero; second = vector.Zero;
|
||||
if (!Replication.IsServer() || !Eligible(belief, now) || !reserved || reserved.Count() > 128) return false;
|
||||
if (!GetGame() || !GetGame().GetWorld()) return false;
|
||||
vector velocity = belief.Velocity; velocity[1] = 0;
|
||||
vector seed = belief.PredictedPosition + velocity.Normalized() * Math.Clamp(velocity.Length() * 60, 300, 900);
|
||||
if (!ERR_HQPursuitRules.PositionValid(seed)) return false;
|
||||
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (!world || !world.GetRoadNetworkManager()) return false;
|
||||
array<BaseRoad> roads = {};
|
||||
world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
|
||||
if (roads.Count() > 128) return false;
|
||||
int segments;
|
||||
bool found;
|
||||
vector bestFirst, bestSecond;
|
||||
float bestScore = 1e20;
|
||||
float bestFirstHeading; float bestSecondHeading;
|
||||
foreach (BaseRoad road : roads)
|
||||
{
|
||||
if (!road || !ERR_ForceRules.Finite(road.GetWidth()) || road.GetWidth() < 6) continue;
|
||||
array<vector> points = {}; road.GetPoints(points);
|
||||
if (points.Count() < 2 || points.Count() > 1024) continue;
|
||||
segments += points.Count() - 1;
|
||||
if (segments > 4096) return false;
|
||||
vector a, b;
|
||||
float score; float aHeading; float bHeading;
|
||||
if (!PairFromPolyline(points, belief.PredictedPosition, velocity, seed, reserved, a, b, score, aHeading, bHeading)) continue;
|
||||
if (!found || ERR_HQRoadIntercept.Better(score, a, bestScore, bestFirst))
|
||||
{ bestFirst = a; bestSecond = b; bestScore = score; bestFirstHeading = aHeading; bestSecondHeading = bHeading; found = true; }
|
||||
}
|
||||
if (!found) return false;
|
||||
vector groundedFirst = ERR_Actors.Ground(bestFirst);
|
||||
vector groundedSecond = ERR_Actors.Ground(bestSecond);
|
||||
if (!ERR_HQPursuitRules.PositionValid(groundedFirst) || !ERR_HQPursuitRules.PositionValid(groundedSecond)) return false;
|
||||
if (Math.AbsFloat(groundedFirst[1] - bestFirst[1]) > 2 || Math.AbsFloat(groundedSecond[1] - bestSecond[1]) > 2) return false;
|
||||
if (vector.DistanceXZ(groundedFirst, groundedSecond) < 40) return false;
|
||||
ERR_SettlementSafety settlements = new ERR_SettlementSafety();
|
||||
if (!settlements.OutsideSettlements(groundedFirst) || !settlements.OutsideSettlements(groundedSecond)) return false;
|
||||
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
|
||||
if (!placement.Clear(groundedFirst, bestFirstHeading) || !placement.Clear(groundedFirst, bestSecondHeading)) return false;
|
||||
first = groundedFirst; second = groundedSecond;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
// Detached radio order. Only its issuer chooses Position and the fixed deadline.
|
||||
class ERR_ConvoyInterceptOrder
|
||||
{
|
||||
int Token;
|
||||
SCR_AIGroup Group;
|
||||
string SourceId;
|
||||
int VehicleIndex;
|
||||
vector Position;
|
||||
float IssuedAt;
|
||||
float ExpiresAt;
|
||||
float ObservedAt;
|
||||
int BeliefId;
|
||||
int BeliefRevision;
|
||||
int ReportId;
|
||||
}
|
||||
|
||||
// Delivered ground history only; no target entity reads, force creation or orders.
|
||||
class ERR_HQMountedInterceptRules
|
||||
{
|
||||
static bool Eligible(ERR_HQBelief belief, float now)
|
||||
{
|
||||
if (!ERR_HQRoadIntercept.Eligible(belief, now)) return false;
|
||||
if (belief.BeliefId <= 0 || belief.BeliefRevision <= 0 || belief.ObservedAt < 0) return false;
|
||||
return ERR_HQPursuitRules.PositionValid(belief.Position) && ERR_HQPursuitRules.PositionValid(belief.PredictedPosition);
|
||||
}
|
||||
static bool Current(ERR_ConvoyInterceptOrder order, ERR_HQBelief belief, float now)
|
||||
{
|
||||
if (!order || !Eligible(belief, now) || !order.Group || order.SourceId.IsEmpty()) return false;
|
||||
if (order.Token < 400001 || order.Token > 400256) return false;
|
||||
if (order.VehicleIndex != 0 && order.VehicleIndex != 3 && order.VehicleIndex != 5) return false;
|
||||
if (order.BeliefId != belief.BeliefId || order.BeliefRevision != belief.BeliefRevision
|
||||
|| order.ReportId != belief.MessageId || order.ObservedAt != belief.ObservedAt) return false;
|
||||
if (!ERR_ForceRules.Finite(order.IssuedAt) || !ERR_ForceRules.Finite(order.ExpiresAt)) return false;
|
||||
if (order.IssuedAt < belief.LastDeliveredAt || now < order.IssuedAt || now - order.IssuedAt > 30000) return false;
|
||||
if (order.ExpiresAt <= order.IssuedAt || order.ExpiresAt > order.ObservedAt + 120000 || now >= order.ExpiresAt) return false;
|
||||
return ERR_HQPursuitRules.PositionValid(order.Position);
|
||||
}
|
||||
static bool Ahead(vector origin, vector velocity, vector point)
|
||||
{
|
||||
if (!ERR_ForceRules.FiniteVector(origin) || !ERR_ForceRules.FiniteVector(point) || !ERR_HQPursuitRules.Heading(velocity, true)) return false;
|
||||
vector delta = point - origin; delta[1] = 0; velocity[1] = 0;
|
||||
float distance = delta.Length();
|
||||
return distance >= 300 && distance <= 900 && vector.Dot(delta.Normalized(), velocity.Normalized()) >= 0.8;
|
||||
}
|
||||
static bool FromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, out vector result, out float score, out float heading)
|
||||
{
|
||||
result = vector.Zero; score = 1e20; heading = 0;
|
||||
if (!points || points.Count() < 2 || points.Count() > 1024 || !ERR_ForceRules.FiniteVector(seed)) return false;
|
||||
foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
|
||||
bool found;
|
||||
for (int i = 1; i < points.Count(); i++)
|
||||
{
|
||||
vector delta = points[i] - points[i - 1]; delta[1] = 0;
|
||||
float length = delta.Length();
|
||||
if (length < 0.01) continue;
|
||||
vector offset = seed - points[i - 1]; offset[1] = 0;
|
||||
float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), 0, 1);
|
||||
vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
|
||||
float distance = vector.DistanceXZ(seed, candidate);
|
||||
if (distance > 100 || !Ahead(origin, velocity, candidate) || !ERR_HQRoadIntercept.Unreserved(candidate, reserved)) continue;
|
||||
if (!found || ERR_HQRoadIntercept.Better(distance, candidate, score, result)) { result = candidate; score = distance; heading = Math.Atan2(delta[0], delta[2]) * Math.RAD2DEG; found = true; }
|
||||
}
|
||||
return found;
|
||||
}
|
||||
static bool Select(ERR_HQBelief belief, float now, array<vector> reserved, out vector result)
|
||||
{
|
||||
result = vector.Zero;
|
||||
if (!Replication.IsServer() || !Eligible(belief, now) || !reserved || reserved.Count() > 128) return false;
|
||||
if (!GetGame() || !GetGame().GetWorld()) return false;
|
||||
vector velocity = belief.Velocity; velocity[1] = 0;
|
||||
vector seed = belief.PredictedPosition + velocity.Normalized() * Math.Clamp(velocity.Length() * 60, 300, 900);
|
||||
if (!ERR_HQPursuitRules.PositionValid(seed)) return false;
|
||||
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (!world || !world.GetRoadNetworkManager()) return false;
|
||||
array<BaseRoad> roads = {};
|
||||
world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
|
||||
if (roads.Count() > 128) return false;
|
||||
int segments;
|
||||
bool found;
|
||||
vector best;
|
||||
float bestScore = 1e20;
|
||||
float bestHeading;
|
||||
foreach (BaseRoad road : roads)
|
||||
{
|
||||
if (!road || !ERR_ForceRules.Finite(road.GetWidth()) || road.GetWidth() < 6) continue;
|
||||
array<vector> points = {}; road.GetPoints(points);
|
||||
if (points.Count() < 2 || points.Count() > 1024) continue;
|
||||
segments += points.Count() - 1;
|
||||
if (segments > 4096) return false;
|
||||
vector candidate;
|
||||
float score, heading;
|
||||
if (!FromPolyline(points, belief.PredictedPosition, velocity, seed, reserved, candidate, score, heading)) continue;
|
||||
if (!found || ERR_HQRoadIntercept.Better(score, candidate, bestScore, best))
|
||||
{ best = candidate; bestScore = score; bestHeading = heading; found = true; }
|
||||
}
|
||||
if (!found) return false;
|
||||
vector grounded = ERR_Actors.Ground(best);
|
||||
// Never convert an elevated bridge point into terrain beneath the road.
|
||||
if (!ERR_HQPursuitRules.PositionValid(grounded) || Math.AbsFloat(grounded[1] - best[1]) > 2) return false;
|
||||
ERR_SettlementSafety settlements = new ERR_SettlementSafety();
|
||||
if (!settlements.OutsideSettlements(grounded)) return false;
|
||||
// Existing conservative vehicle footprint: nine terrain/water samples,
|
||||
// filled collision volume and segment-aligned heading; no scenery changes.
|
||||
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
|
||||
if (!placement.Clear(grounded, bestHeading)) return false;
|
||||
result = grounded;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Keep both escorts on one real polyline. Horizontal arc length determines
|
||||
// placement; interpolation retains each road segment's height and heading.
|
||||
static bool PairFromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, out vector first, out vector second, out float score, out float firstHeading, out float secondHeading)
|
||||
{
|
||||
first = vector.Zero; second = vector.Zero; score = 1e20; firstHeading = 0; secondHeading = 0;
|
||||
if (!points || points.Count() < 2 || points.Count() > 1024 || !ERR_ForceRules.FiniteVector(seed)) return false;
|
||||
if (!ERR_ForceRules.FiniteVector(origin) || !ERR_HQPursuitRules.Heading(velocity, true) || !reserved || reserved.Count() > 128) return false;
|
||||
foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
|
||||
velocity[1] = 0;
|
||||
float total; float anchor; float nearest = 1e20;
|
||||
float along;
|
||||
vector projected;
|
||||
bool found;
|
||||
for (int i = 1; i < points.Count(); i++)
|
||||
{
|
||||
vector delta = points[i] - points[i - 1]; delta[1] = 0;
|
||||
float length = delta.Length();
|
||||
if (!ERR_ForceRules.Finite(length)) return false;
|
||||
if (length < 0.01) continue;
|
||||
vector offset = seed - points[i - 1]; offset[1] = 0;
|
||||
float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), 0, 1);
|
||||
vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
|
||||
float distance = vector.DistanceXZ(seed, candidate);
|
||||
if (!found || ERR_HQRoadIntercept.Better(distance, candidate, nearest, projected))
|
||||
{
|
||||
projected = candidate; nearest = distance; anchor = total + length * fraction;
|
||||
along = vector.Dot(delta.Normalized(), velocity.Normalized()); found = true;
|
||||
}
|
||||
total += length;
|
||||
if (!ERR_ForceRules.Finite(total)) return false;
|
||||
}
|
||||
if (!found || nearest > 100 || total < 40) return false;
|
||||
float span = Math.Clamp(total, 40, 60);
|
||||
float start = anchor - span * 0.5;
|
||||
// Longitudinal roads extend the pair ahead of the projection, so the first
|
||||
// point at the 300m planning boundary is not shifted behind that boundary.
|
||||
if (along >= 0.25) start = anchor;
|
||||
else if (along <= -0.25) start = anchor - span;
|
||||
start = Math.Clamp(start, 0, total - span);
|
||||
float finish = start + span;
|
||||
float travelled;
|
||||
vector a, b;
|
||||
float aHeading; float bHeading;
|
||||
bool haveFirst; bool haveSecond;
|
||||
for (int j = 1; j < points.Count(); j++)
|
||||
{
|
||||
vector segment = points[j] - points[j - 1]; segment[1] = 0;
|
||||
float segmentLength = segment.Length();
|
||||
if (segmentLength < 0.01) continue;
|
||||
if (!haveFirst && start <= travelled + segmentLength)
|
||||
{
|
||||
a = vector.Lerp(points[j - 1], points[j], Math.Clamp((start - travelled) / segmentLength, 0, 1));
|
||||
aHeading = Math.Atan2(segment[0], segment[2]) * Math.RAD2DEG; haveFirst = true;
|
||||
}
|
||||
if (!haveSecond && finish <= travelled + segmentLength)
|
||||
{
|
||||
b = vector.Lerp(points[j - 1], points[j], Math.Clamp((finish - travelled) / segmentLength, 0, 1));
|
||||
bHeading = Math.Atan2(segment[0], segment[2]) * Math.RAD2DEG; haveSecond = true;
|
||||
}
|
||||
if (haveFirst && haveSecond) break;
|
||||
travelled += segmentLength;
|
||||
}
|
||||
if (!haveFirst || !haveSecond || vector.DistanceXZ(a, b) < 40) return false;
|
||||
if (vector.DistanceXZ(seed, a) > 100 || vector.DistanceXZ(seed, b) > 100) return false;
|
||||
if (!Ahead(origin, velocity, a) || !Ahead(origin, velocity, b)) return false;
|
||||
if (!ERR_HQRoadIntercept.Unreserved(a, reserved) || !ERR_HQRoadIntercept.Unreserved(b, reserved)) return false;
|
||||
// Canonical order does not depend on native road enumeration direction.
|
||||
if (ERR_HQRoadIntercept.Better(0, b, 0, a))
|
||||
{
|
||||
vector swap = a; a = b; b = swap;
|
||||
float swapHeading = aHeading; aHeading = bHeading; bHeading = swapHeading;
|
||||
}
|
||||
first = a; second = b; score = nearest; firstHeading = aHeading; secondHeading = bHeading;
|
||||
return true;
|
||||
}
|
||||
static bool SelectPair(ERR_HQBelief belief, float now, array<vector> reserved, out vector first, out vector second)
|
||||
{
|
||||
first = vector.Zero; second = vector.Zero;
|
||||
if (!Replication.IsServer() || !Eligible(belief, now) || !reserved || reserved.Count() > 128) return false;
|
||||
if (!GetGame() || !GetGame().GetWorld()) return false;
|
||||
vector velocity = belief.Velocity; velocity[1] = 0;
|
||||
vector seed = belief.PredictedPosition + velocity.Normalized() * Math.Clamp(velocity.Length() * 60, 300, 900);
|
||||
if (!ERR_HQPursuitRules.PositionValid(seed)) return false;
|
||||
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (!world || !world.GetRoadNetworkManager()) return false;
|
||||
array<BaseRoad> roads = {};
|
||||
world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
|
||||
if (roads.Count() > 128) return false;
|
||||
int segments;
|
||||
bool found;
|
||||
vector bestFirst, bestSecond;
|
||||
float bestScore = 1e20;
|
||||
float bestFirstHeading; float bestSecondHeading;
|
||||
foreach (BaseRoad road : roads)
|
||||
{
|
||||
if (!road || !ERR_ForceRules.Finite(road.GetWidth()) || road.GetWidth() < 6) continue;
|
||||
array<vector> points = {}; road.GetPoints(points);
|
||||
if (points.Count() < 2 || points.Count() > 1024) continue;
|
||||
segments += points.Count() - 1;
|
||||
if (segments > 4096) return false;
|
||||
vector a, b;
|
||||
float score; float aHeading; float bHeading;
|
||||
if (!PairFromPolyline(points, belief.PredictedPosition, velocity, seed, reserved, a, b, score, aHeading, bHeading)) continue;
|
||||
if (!found || ERR_HQRoadIntercept.Better(score, a, bestScore, bestFirst))
|
||||
{ bestFirst = a; bestSecond = b; bestScore = score; bestFirstHeading = aHeading; bestSecondHeading = bHeading; found = true; }
|
||||
}
|
||||
if (!found) return false;
|
||||
vector groundedFirst = ERR_Actors.Ground(bestFirst);
|
||||
vector groundedSecond = ERR_Actors.Ground(bestSecond);
|
||||
if (!ERR_HQPursuitRules.PositionValid(groundedFirst) || !ERR_HQPursuitRules.PositionValid(groundedSecond)) return false;
|
||||
if (Math.AbsFloat(groundedFirst[1] - bestFirst[1]) > 2 || Math.AbsFloat(groundedSecond[1] - bestSecond[1]) > 2) return false;
|
||||
if (vector.DistanceXZ(groundedFirst, groundedSecond) < 40) return false;
|
||||
ERR_SettlementSafety settlements = new ERR_SettlementSafety();
|
||||
if (!settlements.OutsideSettlements(groundedFirst) || !settlements.OutsideSettlements(groundedSecond)) return false;
|
||||
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
|
||||
if (!placement.Clear(groundedFirst, bestFirstHeading)) return false;
|
||||
first = groundedFirst; second = groundedSecond;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"scope": "Scratch-only pair geometry; parent registers new test and integrates dispatcher/service.",
|
||||
"api": "static bool SelectPair(ERR_HQBelief belief, float now, array<vector> reserved, out vector first, out vector second)",
|
||||
"singleton_bodies_unchanged": true,
|
||||
"geometry": "One real polyline,40..60m horizontal arc span; retain segment heights/headings; longitudinal placement extends forward, transverse centers. Both ahead300..900m/cosine0.8, within100m seed, at least40m apart and80m from unrelated reservations. Outputs zero on any failure.",
|
||||
"bounds": "One query;128roads/4096accepted segments;1024points perroad; finite scan plus2geometry passes. Later overbudget rejects earlier candidate.",
|
||||
"native_checks": "Both road-ground heights differ<=2m; both outside settlements and Clear footprints; no output publication until both pass.",
|
||||
"tests": {
|
||||
"existing": "existing-green.log PASS with no adapter changes",
|
||||
"new": "pair-green.log PASS actual methods with native boundary doubles",
|
||||
"mutations": {
|
||||
"spacing-removed": {
|
||||
"exit": 1,
|
||||
"runtime_assertion_failure": true
|
||||
},
|
||||
"pair-second-clear-checks-first": {
|
||||
"exit": 1,
|
||||
"runtime_assertion_failure": true
|
||||
},
|
||||
"pair-budget-removed": {
|
||||
"exit": 1,
|
||||
"runtime_assertion_failure": true
|
||||
},
|
||||
"pair-chord-instead-of-arc": {
|
||||
"exit": 1,
|
||||
"runtime_assertion_failure": true
|
||||
}
|
||||
},
|
||||
"apply_check": true,
|
||||
"initial_mutation_note": "Deleting second Clear caused unused heading compilefailure; retained log. Final mutation checks first point twice and fails at runtime."
|
||||
},
|
||||
"baseline": {
|
||||
"working_sha256": "c239aaad0ac556744f76b94286c6f204be51b51acdf44e788ca794c2ae967eae",
|
||||
"index_present": false
|
||||
},
|
||||
"proposed": {
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c": "a37edcf66e43945f331db47fe81cb2e0e767a7aaf8115244dcf9e6295932be98",
|
||||
"tests/test_hq_mounted_intercept_pair.py": "a8d758b1ff20bb6d1a95a9ff1d38f687e43c819c279f5ea9692e84f766dc4fb9"
|
||||
},
|
||||
"patch_sha256": "184363078545edc1fc505dfaf90e51631cb476143f96ade3dc0ef269d91d99b6",
|
||||
"command": "ERR_MOUNTED_INTERCEPT_SOURCE=/tmp/erri-pair-geometry-0054/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c PYTHONPATH=/home/jakub/devel/escapistsrereintroduced/tests python3 /tmp/erri-pair-geometry-0054/tests/test_hq_mounted_intercept_pair.py",
|
||||
"limits": [
|
||||
"No native compilation/driving/physical checkpoint proof.",
|
||||
"Terrain/settlement/clearance doubled; water/collision tests delegation and output atomicity, not native implementation.",
|
||||
"Conservative selection, not exhaustive placement search; singleton fallback costs second query."
|
||||
],
|
||||
"target_writes": false,
|
||||
"original_writes": false,
|
||||
"native": false
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"verdict": "No concrete blocker found in final paired service addition. Read-only source/test review; no independent compiler/native execution in this review.",
|
||||
"source_manifest": {
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c": "b59016d83cb9d40136b98b4e0067ea490bc553574fc53603593efd5522dbed20",
|
||||
"tests/test_convoy_intercept_pair.py": "52b55727b7c2441d43530efa701045b9439b9175c71fbd2ae7577f851d2c98ec",
|
||||
"fix.patch": "dd034f9837703ba3cbf157490999152ef1d00fae7878c5fe8073d808e3abe1c4"
|
||||
},
|
||||
"source_manifest_sha256": "e27b7654dd458683e8689f1621149b589070ea0177588c08c7328d7d2604cf86",
|
||||
"baseline_still_matches_target": true,
|
||||
"addition_only_existing_singleton_and_release_paths_unchanged": true,
|
||||
"reviewed_invariants": [
|
||||
"Reserve pair requires distinct original groups/vehicle slots/tokens/entities in exact admitted task and consumed receipt; real ledger records and original crew ownership pass existing InterceptEligible for each. Same delivered belief/report/revision/times and >=40m spacing; global existing interception prevents other-task adoption.",
|
||||
"First claim failure leaves both untouched; second claim failure clears only first exact owned claim and preserves original route handles/deadline. Tokens remain spent rather than replayable.",
|
||||
"First or second waypoint allocation failure precedes any ClearOrder. Second failure deletes only first prepared waypoint. No physical actor, inventory, stock, pool receipt or ledger registration changes in added methods.",
|
||||
"Both allocations followed by exact receipt/record/route/entity/crew/claim/destination revalidation before route mutation. Invalid precommit ownership deletes prepared handles only.",
|
||||
"ClearOrder/AddWaypoint are native commit boundaries, not atomic transactions. Callback ownership loss prevents later partner installation and releases exact owned claims. No assertion that original native orders can be transactionally restored after removal.",
|
||||
"Public replacement waypoint handles are not adopted/deleted by exact private cleanup; original actors/drivers retained. Shared fixed expiry and existing per-member route progression/resume retained."
|
||||
],
|
||||
"test_evidence_inspected": {
|
||||
"path": "/tmp/erri-pair-service-0054/tests/test_convoy_intercept_pair.py",
|
||||
"binding": "Reuses existing full actual vehicle/service C++ translation through ERR_CONVOY_SERVICE, then inserts actual pair methods. Native allocation/removal/addition are callback doubles.",
|
||||
"cases": [
|
||||
"19 malformed/foreign/invalid pair refusals",
|
||||
"first and second allocation failure retain both original handle and Add/Remove counters",
|
||||
"crew loss during allocation leaves routes intact and deletes both prepared handles",
|
||||
"second claim rollback and unrelated global claim preservation",
|
||||
"mutated claimed destinations refused",
|
||||
"ClearOrder/first Add/second Add cancellation",
|
||||
"both physical-position hold/expiry and exact per-member foreign-public-handle preservation"
|
||||
],
|
||||
"author_logs": {
|
||||
"apply-check.log": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"mutation-spacing-red.log": "d11417b94e4ddf6357314e03e6e8560e145e685d094dfaf5a4681171c02c39c7",
|
||||
"mutation-rollback-red.log": "810fb2ce2337fe3f25ee3404b12a49a263e0310318e54cc2e7b3fe21119e9df7",
|
||||
"baseline-red.log": "3282ef3cdcb9b5f5557227a37a795e310240d6d598dce23e261bc7fbe8dd308b",
|
||||
"rules.log": "20410d8a7171f3e140f415186697a442452d6eeea8564d622ed093d2259bbc8d",
|
||||
"lifecycle.log": "6e7f1708a5e9c0af68ffd0ace1edc33e99edff479cee2e6750bcaca1c94317ff",
|
||||
"green.log": "376c9f1c0e0fa25eee0d283e57561cd2ed6d1b826a8c2fb9486d681e82ef0f8b"
|
||||
},
|
||||
"executed_by_reviewer": false
|
||||
},
|
||||
"hq_previous_finding": "Author corrected greedy roleA crew selection by bounded enumeration of each original escort as first choice. Independently inspected current loop; new test was reported green by author, not rerun here.",
|
||||
"retained_limitations": [
|
||||
"Legacy ClearIntercept calls native RemoveWaypoint before clearing token. No concrete direct recursive native callback demonstrated in this feature slice; behavior unchanged and not claimed newly qualified.",
|
||||
"Native waypoint Add/Remove cannot guarantee transactional movement of two separate groups.",
|
||||
"No actual native driving, paired hold/traffic blockage, radio timing, multiplayer or FPS evidence from source-level doubles."
|
||||
],
|
||||
"actions": {
|
||||
"target_writes": false,
|
||||
"original_writes": false,
|
||||
"source_edits": false,
|
||||
"tests_compilers": false,
|
||||
"native": false
|
||||
},
|
||||
"reviewed_dispatch_sha256": "60d0fef83f43eea0501bf5fe46f369556e22acda6d034908b5965d8820b28893"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c": "a37edcf66e43945f331db47fe81cb2e0e767a7aaf8115244dcf9e6295932be98",
|
||||
"tests/test_hq_mounted_intercept_pair.py": "a8d758b1ff20bb6d1a95a9ff1d38f687e43c819c279f5ea9692e84f766dc4fb9"
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
check: /tmp/err-policy-zgu986xs/check.cpp:377: int main(): Assertion `!pair({{970,10,1300},{1000,10,1300},{1000,10,1301},{970,10,1301}})' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-pair-geometry-0054/tests/test_hq_mounted_intercept_pair.py", line 25, in <module>
|
||||
run(r'''
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<104 lines>...
|
||||
|
||||
|
||||
''')
|
||||
^^^^
|
||||
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
|
||||
subprocess.run([str(p/'check')],check=True)
|
||||
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/usr/lib/python3.14/subprocess.py", line 578, in run
|
||||
raise CalledProcessError(retcode, process.args,
|
||||
output=stdout, stderr=stderr)
|
||||
subprocess.CalledProcessError: Command '['/tmp/err-policy-zgu986xs/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1,229 @@
|
||||
// Detached radio order. Only its issuer chooses Position and the fixed deadline.
|
||||
class ERR_ConvoyInterceptOrder
|
||||
{
|
||||
int Token;
|
||||
SCR_AIGroup Group;
|
||||
string SourceId;
|
||||
int VehicleIndex;
|
||||
vector Position;
|
||||
float IssuedAt;
|
||||
float ExpiresAt;
|
||||
float ObservedAt;
|
||||
int BeliefId;
|
||||
int BeliefRevision;
|
||||
int ReportId;
|
||||
}
|
||||
|
||||
// Delivered ground history only; no target entity reads, force creation or orders.
|
||||
class ERR_HQMountedInterceptRules
|
||||
{
|
||||
static bool Eligible(ERR_HQBelief belief, float now)
|
||||
{
|
||||
if (!ERR_HQRoadIntercept.Eligible(belief, now)) return false;
|
||||
if (belief.BeliefId <= 0 || belief.BeliefRevision <= 0 || belief.ObservedAt < 0) return false;
|
||||
return ERR_HQPursuitRules.PositionValid(belief.Position) && ERR_HQPursuitRules.PositionValid(belief.PredictedPosition);
|
||||
}
|
||||
static bool Current(ERR_ConvoyInterceptOrder order, ERR_HQBelief belief, float now)
|
||||
{
|
||||
if (!order || !Eligible(belief, now) || !order.Group || order.SourceId.IsEmpty()) return false;
|
||||
if (order.Token < 400001 || order.Token > 400256) return false;
|
||||
if (order.VehicleIndex != 0 && order.VehicleIndex != 3 && order.VehicleIndex != 5) return false;
|
||||
if (order.BeliefId != belief.BeliefId || order.BeliefRevision != belief.BeliefRevision
|
||||
|| order.ReportId != belief.MessageId || order.ObservedAt != belief.ObservedAt) return false;
|
||||
if (!ERR_ForceRules.Finite(order.IssuedAt) || !ERR_ForceRules.Finite(order.ExpiresAt)) return false;
|
||||
if (order.IssuedAt < belief.LastDeliveredAt || now < order.IssuedAt || now - order.IssuedAt > 30000) return false;
|
||||
if (order.ExpiresAt <= order.IssuedAt || order.ExpiresAt > order.ObservedAt + 120000 || now >= order.ExpiresAt) return false;
|
||||
return ERR_HQPursuitRules.PositionValid(order.Position);
|
||||
}
|
||||
static bool Ahead(vector origin, vector velocity, vector point)
|
||||
{
|
||||
if (!ERR_ForceRules.FiniteVector(origin) || !ERR_ForceRules.FiniteVector(point) || !ERR_HQPursuitRules.Heading(velocity, true)) return false;
|
||||
vector delta = point - origin; delta[1] = 0; velocity[1] = 0;
|
||||
float distance = delta.Length();
|
||||
return distance >= 300 && distance <= 900 && vector.Dot(delta.Normalized(), velocity.Normalized()) >= 0.8;
|
||||
}
|
||||
static bool FromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, out vector result, out float score, out float heading)
|
||||
{
|
||||
result = vector.Zero; score = 1e20; heading = 0;
|
||||
if (!points || points.Count() < 2 || points.Count() > 1024 || !ERR_ForceRules.FiniteVector(seed)) return false;
|
||||
foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
|
||||
bool found;
|
||||
for (int i = 1; i < points.Count(); i++)
|
||||
{
|
||||
vector delta = points[i] - points[i - 1]; delta[1] = 0;
|
||||
float length = delta.Length();
|
||||
if (length < 0.01) continue;
|
||||
vector offset = seed - points[i - 1]; offset[1] = 0;
|
||||
float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), 0, 1);
|
||||
vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
|
||||
float distance = vector.DistanceXZ(seed, candidate);
|
||||
if (distance > 100 || !Ahead(origin, velocity, candidate) || !ERR_HQRoadIntercept.Unreserved(candidate, reserved)) continue;
|
||||
if (!found || ERR_HQRoadIntercept.Better(distance, candidate, score, result)) { result = candidate; score = distance; heading = Math.Atan2(delta[0], delta[2]) * Math.RAD2DEG; found = true; }
|
||||
}
|
||||
return found;
|
||||
}
|
||||
static bool Select(ERR_HQBelief belief, float now, array<vector> reserved, out vector result)
|
||||
{
|
||||
result = vector.Zero;
|
||||
if (!Replication.IsServer() || !Eligible(belief, now) || !reserved || reserved.Count() > 128) return false;
|
||||
if (!GetGame() || !GetGame().GetWorld()) return false;
|
||||
vector velocity = belief.Velocity; velocity[1] = 0;
|
||||
vector seed = belief.PredictedPosition + velocity.Normalized() * Math.Clamp(velocity.Length() * 60, 300, 900);
|
||||
if (!ERR_HQPursuitRules.PositionValid(seed)) return false;
|
||||
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (!world || !world.GetRoadNetworkManager()) return false;
|
||||
array<BaseRoad> roads = {};
|
||||
world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
|
||||
if (roads.Count() > 128) return false;
|
||||
int segments;
|
||||
bool found;
|
||||
vector best;
|
||||
float bestScore = 1e20;
|
||||
float bestHeading;
|
||||
foreach (BaseRoad road : roads)
|
||||
{
|
||||
if (!road || !ERR_ForceRules.Finite(road.GetWidth()) || road.GetWidth() < 6) continue;
|
||||
array<vector> points = {}; road.GetPoints(points);
|
||||
if (points.Count() < 2 || points.Count() > 1024) continue;
|
||||
segments += points.Count() - 1;
|
||||
if (segments > 4096) return false;
|
||||
vector candidate;
|
||||
float score, heading;
|
||||
if (!FromPolyline(points, belief.PredictedPosition, velocity, seed, reserved, candidate, score, heading)) continue;
|
||||
if (!found || ERR_HQRoadIntercept.Better(score, candidate, bestScore, best))
|
||||
{ best = candidate; bestScore = score; bestHeading = heading; found = true; }
|
||||
}
|
||||
if (!found) return false;
|
||||
vector grounded = ERR_Actors.Ground(best);
|
||||
// Never convert an elevated bridge point into terrain beneath the road.
|
||||
if (!ERR_HQPursuitRules.PositionValid(grounded) || Math.AbsFloat(grounded[1] - best[1]) > 2) return false;
|
||||
ERR_SettlementSafety settlements = new ERR_SettlementSafety();
|
||||
if (!settlements.OutsideSettlements(grounded)) return false;
|
||||
// Existing conservative vehicle footprint: nine terrain/water samples,
|
||||
// filled collision volume and segment-aligned heading; no scenery changes.
|
||||
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
|
||||
if (!placement.Clear(grounded, bestHeading)) return false;
|
||||
result = grounded;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Keep both escorts on one real polyline. Horizontal arc length determines
|
||||
// placement; interpolation retains each road segment's height and heading.
|
||||
static bool PairFromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, out vector first, out vector second, out float score, out float firstHeading, out float secondHeading)
|
||||
{
|
||||
first = vector.Zero; second = vector.Zero; score = 1e20; firstHeading = 0; secondHeading = 0;
|
||||
if (!points || points.Count() < 2 || points.Count() > 1024 || !ERR_ForceRules.FiniteVector(seed)) return false;
|
||||
if (!ERR_ForceRules.FiniteVector(origin) || !ERR_HQPursuitRules.Heading(velocity, true) || !reserved || reserved.Count() > 128) return false;
|
||||
foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
|
||||
velocity[1] = 0;
|
||||
float total; float anchor; float nearest = 1e20;
|
||||
float along;
|
||||
vector projected;
|
||||
bool found;
|
||||
for (int i = 1; i < points.Count(); i++)
|
||||
{
|
||||
vector delta = points[i] - points[i - 1]; delta[1] = 0;
|
||||
float length = delta.Length();
|
||||
if (!ERR_ForceRules.Finite(length)) return false;
|
||||
if (length < 0.01) continue;
|
||||
vector offset = seed - points[i - 1]; offset[1] = 0;
|
||||
float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), 0, 1);
|
||||
vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
|
||||
float distance = vector.DistanceXZ(seed, candidate);
|
||||
if (!found || ERR_HQRoadIntercept.Better(distance, candidate, nearest, projected))
|
||||
{
|
||||
projected = candidate; nearest = distance; anchor = total + length * fraction;
|
||||
along = vector.Dot(delta.Normalized(), velocity.Normalized()); found = true;
|
||||
}
|
||||
total += length;
|
||||
if (!ERR_ForceRules.Finite(total)) return false;
|
||||
}
|
||||
if (!found || nearest > 100 || total < 40) return false;
|
||||
float span = Math.Clamp(total, 40, 60);
|
||||
float start = anchor - span * 0.5;
|
||||
// Longitudinal roads extend the pair ahead of the projection, so the first
|
||||
// point at the 300m planning boundary is not shifted behind that boundary.
|
||||
if (along >= 0.25) start = anchor;
|
||||
else if (along <= -0.25) start = anchor - span;
|
||||
start = Math.Clamp(start, 0, total - span);
|
||||
float finish = start + span;
|
||||
float travelled;
|
||||
vector a, b;
|
||||
float aHeading; float bHeading;
|
||||
bool haveFirst; bool haveSecond;
|
||||
for (int j = 1; j < points.Count(); j++)
|
||||
{
|
||||
vector segment = points[j] - points[j - 1]; segment[1] = 0;
|
||||
float segmentLength = segment.Length();
|
||||
if (segmentLength < 0.01) continue;
|
||||
if (!haveFirst && start <= travelled + segmentLength)
|
||||
{
|
||||
a = vector.Lerp(points[j - 1], points[j], Math.Clamp((start - travelled) / segmentLength, 0, 1));
|
||||
aHeading = Math.Atan2(segment[0], segment[2]) * Math.RAD2DEG; haveFirst = true;
|
||||
}
|
||||
if (!haveSecond && finish <= travelled + segmentLength)
|
||||
{
|
||||
b = vector.Lerp(points[j - 1], points[j], Math.Clamp((finish - travelled) / segmentLength, 0, 1));
|
||||
bHeading = Math.Atan2(segment[0], segment[2]) * Math.RAD2DEG; haveSecond = true;
|
||||
}
|
||||
if (haveFirst && haveSecond) break;
|
||||
travelled += segmentLength;
|
||||
}
|
||||
if (!haveFirst || !haveSecond) return false;
|
||||
if (vector.DistanceXZ(seed, a) > 100 || vector.DistanceXZ(seed, b) > 100) return false;
|
||||
if (!Ahead(origin, velocity, a) || !Ahead(origin, velocity, b)) return false;
|
||||
if (!ERR_HQRoadIntercept.Unreserved(a, reserved) || !ERR_HQRoadIntercept.Unreserved(b, reserved)) return false;
|
||||
// Canonical order does not depend on native road enumeration direction.
|
||||
if (ERR_HQRoadIntercept.Better(0, b, 0, a))
|
||||
{
|
||||
vector swap = a; a = b; b = swap;
|
||||
float swapHeading = aHeading; aHeading = bHeading; bHeading = swapHeading;
|
||||
}
|
||||
first = a; second = b; score = nearest; firstHeading = aHeading; secondHeading = bHeading;
|
||||
return true;
|
||||
}
|
||||
static bool SelectPair(ERR_HQBelief belief, float now, array<vector> reserved, out vector first, out vector second)
|
||||
{
|
||||
first = vector.Zero; second = vector.Zero;
|
||||
if (!Replication.IsServer() || !Eligible(belief, now) || !reserved || reserved.Count() > 128) return false;
|
||||
if (!GetGame() || !GetGame().GetWorld()) return false;
|
||||
vector velocity = belief.Velocity; velocity[1] = 0;
|
||||
vector seed = belief.PredictedPosition + velocity.Normalized() * Math.Clamp(velocity.Length() * 60, 300, 900);
|
||||
if (!ERR_HQPursuitRules.PositionValid(seed)) return false;
|
||||
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (!world || !world.GetRoadNetworkManager()) return false;
|
||||
array<BaseRoad> roads = {};
|
||||
world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
|
||||
if (roads.Count() > 128) return false;
|
||||
int segments;
|
||||
bool found;
|
||||
vector bestFirst, bestSecond;
|
||||
float bestScore = 1e20;
|
||||
float bestFirstHeading; float bestSecondHeading;
|
||||
foreach (BaseRoad road : roads)
|
||||
{
|
||||
if (!road || !ERR_ForceRules.Finite(road.GetWidth()) || road.GetWidth() < 6) continue;
|
||||
array<vector> points = {}; road.GetPoints(points);
|
||||
if (points.Count() < 2 || points.Count() > 1024) continue;
|
||||
segments += points.Count() - 1;
|
||||
if (segments > 4096) return false;
|
||||
vector a, b;
|
||||
float score; float aHeading; float bHeading;
|
||||
if (!PairFromPolyline(points, belief.PredictedPosition, velocity, seed, reserved, a, b, score, aHeading, bHeading)) continue;
|
||||
if (!found || ERR_HQRoadIntercept.Better(score, a, bestScore, bestFirst))
|
||||
{ bestFirst = a; bestSecond = b; bestScore = score; bestFirstHeading = aHeading; bestSecondHeading = bHeading; found = true; }
|
||||
}
|
||||
if (!found) return false;
|
||||
vector groundedFirst = ERR_Actors.Ground(bestFirst);
|
||||
vector groundedSecond = ERR_Actors.Ground(bestSecond);
|
||||
if (!ERR_HQPursuitRules.PositionValid(groundedFirst) || !ERR_HQPursuitRules.PositionValid(groundedSecond)) return false;
|
||||
if (Math.AbsFloat(groundedFirst[1] - bestFirst[1]) > 2 || Math.AbsFloat(groundedSecond[1] - bestSecond[1]) > 2) return false;
|
||||
if (vector.DistanceXZ(groundedFirst, groundedSecond) < 40) return false;
|
||||
ERR_SettlementSafety settlements = new ERR_SettlementSafety();
|
||||
if (!settlements.OutsideSettlements(groundedFirst) || !settlements.OutsideSettlements(groundedSecond)) return false;
|
||||
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
|
||||
if (!placement.Clear(groundedFirst, bestFirstHeading) || !placement.Clear(groundedSecond, bestSecondHeading)) return false;
|
||||
first = groundedFirst; second = groundedSecond;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
"""Actual paired road geometry and bounded native survey; native boundaries are doubled."""
|
||||
from pathlib import Path
|
||||
import os,re
|
||||
from policy_harness import ROOT,body,run
|
||||
p=Path(os.environ.get('ERR_MOUNTED_INTERCEPT_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c'))
|
||||
source=p.read_text()
|
||||
base=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQRoadIntercept.c'
|
||||
source='class ERR_HQRoadIntercept\n{\n'+'\n'.join(body(base,x) for x in ('static bool Eligible(', 'static bool Unreserved(', 'static bool Better('))+'\n}\n'+source
|
||||
for forbidden in ('GetPlayer','GetControlledEntity','GetOrigin','Spawn(', 'AddWaypoint','SetOrigin','CallLater'):
|
||||
assert forbidden not in source,forbidden
|
||||
rules='\n'.join(body(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c', x) for x in ('static bool Finite(', 'static bool PositionValid(', 'static bool Actionable(', 'static bool Heading(', 'static ERR_HQPursuitRole Role(', 'static vector Direction(', 'static vector Objective('))
|
||||
rules=rules.replace('Math.','Math::').replace('vector.','vector::').replace('ERR_HQPursuitRole.','ERR_HQPursuitRole::').replace('vector offset;','vector offset{};')
|
||||
s=re.sub(r'//[^\n]*','',source).replace('class ERR_HQRoadIntercept','struct ERR_HQRoadIntercept').replace('class ERR_HQMountedInterceptRules','struct ERR_HQMountedInterceptRules').replace('class ERR_ConvoyInterceptOrder','struct ERR_ConvoyInterceptOrder')
|
||||
s=s.replace('out vector','vector&').replace('out float','float&').replace('array<vector>','Array<vector>').replace('array<BaseRoad>','Array<BaseRoad*>').replace('foreach (','for (')
|
||||
s=s.replace('SCR_AIWorld.Cast(GetGame().GetAIWorld())','GetGame()->GetAIWorld()')
|
||||
for name in ('ERR_HQBelief','SCR_AIWorld','BaseRoad','TraceBox','SCR_AIGroup','ERR_ConvoyInterceptOrder','ERR_SettlementSafety','ERR_ConvoyPlacement'):
|
||||
s=re.sub(r'\b'+name+r' (\w+)',name+r'* \1',s)
|
||||
for name in ('belief','world','road','body','order','settlements','placement'):
|
||||
s=s.replace(name+'.',name+'->')
|
||||
s=s.replace('GetGame().','GetGame()->').replace('GetRoadNetworkManager().','GetRoadNetworkManager()->').replace('GetWorld().','GetWorld()->')
|
||||
for name in ('ERR_ForceRules','ERR_HQPursuitRules','ERR_HQPursuitRole','ERR_HQMemoryKind','vector','Math','ERR_Actors','ChimeraWorldUtils','TraceFlags','Replication','ERR_HQRoadIntercept'):
|
||||
s=s.replace(name+'.',name+'::')
|
||||
s=s.replace('null','nullptr').replace('string SourceId','std::string SourceId').replace('.IsEmpty()', '.empty()')
|
||||
s=re.sub(r'\b(bool|int|float) (\w+);',r'\1 \2{};',s).replace('float score, heading;', 'float score=0, heading=0;').replace('\n}\n','\n};\n')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
enum class ERR_HQPursuitRole{SEARCH,INTERCEPT,CONTAIN};enum class ERR_HQMemoryKind{SIGHTING,DISTRESS,BODY};
|
||||
struct SCR_AIGroup{};
|
||||
struct Math{static constexpr float RAD2DEG=57.2957795131f;static float Atan2(float x,float y){return std::atan2(x,y);}static float Clamp(float x,float a,float b){return std::clamp(x,a,b);}static float AbsFloat(float x){return std::abs(x);}};
|
||||
struct vector{float x=0,y=0,z=0;vector()=default;vector(float a,float b,float c):x(a),y(b),z(c){}vector(const char*s){sscanf(s,"%f %f %f",&x,&y,&z);}float&operator[](int i){return i==0?x:i==1?y:z;}float operator[](int i)const{return i==0?x:i==1?y:z;}
|
||||
vector operator+(vector b)const{return{x+b.x,y+b.y,z+b.z};}vector operator-(vector b)const{return{x-b.x,y-b.y,z-b.z};}vector operator*(float n)const{return{x*n,y*n,z*n};}bool operator==(vector b)const{return x==b.x&&y==b.y&&z==b.z;}
|
||||
float Length()const{return std::sqrt(x*x+y*y+z*z);}vector Normalized()const{float n=Length();return{x/n,y/n,z/n};}static float Dot(vector a,vector b){return a.x*b.x+a.y*b.y+a.z*b.z;}static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}static vector Lerp(vector a,vector b,float n){return a+(b-a)*n;}static const vector Zero;};
|
||||
const vector vector::Zero{};vector Vector(float a,float b,float c){return{a,b,c};}
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;using std::vector<T>::operator=;int Count(){return this->size();}void Insert(T v){this->push_back(v);}explicit operator bool(){return true;}};
|
||||
struct ERR_ForceRules{static bool Finite(float n){return std::isfinite(n);}static bool FiniteVector(vector p){return Finite(p.x)&&Finite(p.y)&&Finite(p.z);}};
|
||||
struct ERR_HQPursuitRules{''' + rules + r'''};
|
||||
struct ERR_HQBelief{ERR_HQMemoryKind Kind=ERR_HQMemoryKind::SIGHTING;int SampleCount=2,MessageId=1,BeliefId=3,BeliefRevision=2;vector Position={1000,10,1000};float ObservedAt=5000,LastDeliveredAt=7000,Confidence=.9,Radius=40;std::string TravelMode="on foot";vector PredictedPosition={1000,10,1000},Velocity={0,0,4};bool HasVelocity=true;};
|
||||
struct TraceFlags{static constexpr int WORLD=1,ENTS=2;};struct TraceBox{vector Start,End,Mins,Maxs;int Flags=0;};
|
||||
struct NativeWorld{int traces=0;float fraction=1;float TraceMove(TraceBox* t,void*){traces++;assert(t->Flags==3&&t->Mins.y==-.9f);return fraction;}};
|
||||
struct BaseRoad{static inline int reads=0;float width=6;Array<vector>points={{900,10,1300},{1100,10,1300}};float GetWidth(){return width;}void GetPoints(Array<vector>&p){reads++;p=points;}};
|
||||
struct RoadManager{Array<BaseRoad*>roads;int queries=0;void GetRoadsInAABB(vector low,vector high,Array<BaseRoad*>&out){queries++;assert(high.x-low.x==200&&high.z-low.z==200&&high.y-low.y==2000);out=roads;}};
|
||||
struct SCR_AIWorld{RoadManager manager;bool available=true;RoadManager*GetRoadNetworkManager(){return available?&manager:nullptr;}};
|
||||
struct Game{NativeWorld world;SCR_AIWorld ai;NativeWorld*GetWorld(){return &world;}SCR_AIWorld*GetAIWorld(){return &ai;}}game;Game*GetGame(){return &game;}
|
||||
struct Replication{static inline bool server=true;static bool IsServer(){return server;}};
|
||||
struct ERR_Actors{static inline float rightHeight=10;static vector Ground(vector p){p.y=p.x>1000?rightHeight:10;return p;}};
|
||||
struct ChimeraWorldUtils{static inline bool water=false;static bool TryGetWaterSurfaceSimple(NativeWorld*,vector){return water;}};
|
||||
|
||||
struct ERR_SettlementSafety{static inline bool clear=true,rightClear=true;static inline int calls=0;bool OutsideSettlements(vector p){calls++;return clear&&(p.x<=1000||rightClear);}};
|
||||
struct ERR_ConvoyPlacement{static inline bool clear=true,rightClear=true,water=false;static inline int calls=0;static inline float heading=0;bool Clear(vector p,float h){calls++;heading=h;assert(p.y==10);return clear&&!water&&(p.x<=1000||rightClear);}};
|
||||
''' + s + r'''
|
||||
int main(){
|
||||
using Rules=ERR_HQMountedInterceptRules;
|
||||
ERR_HQBelief belief;Array<vector> reserved;vector a={1,2,3},b={4,5,6};float score=0,ha=0,hb=0;
|
||||
vector origin={1000,10,1000},velocity={0,0,4},seed={1000,10,1300};
|
||||
auto pair=[&](Array<vector> p){return Rules::PairFromPolyline(p,origin,velocity,seed,reserved,a,b,score,ha,hb);};
|
||||
auto empty=[&](){assert(a==vector::Zero&&b==vector::Zero);};
|
||||
// Real arc interpolation over a90-degree bend, not straight interpolation
|
||||
// between the road's endpoints (which would put both escorts off the road).
|
||||
assert(pair({{900,10,1300},{1000,10,1300},{1000,10,1400}}));
|
||||
assert(a==vector(970,10,1300)&&b==vector(1000,10,1330));
|
||||
assert(std::abs(ha-90)<.001&&std::abs(hb)<.001);
|
||||
assert(vector::DistanceXZ(a,b)>42&&vector::DistanceXZ(a,b)<43);
|
||||
// Arc span alone is insufficient on a bend-back/hairpin.
|
||||
assert(!pair({{970,10,1300},{1000,10,1300},{1000,10,1301},{970,10,1301}}));empty();
|
||||
assert(!pair({{980,10,1300},{1019.9f,10,1300}}));empty();
|
||||
assert(pair({{980,10,1300},{1020,10,1300}})&&vector::DistanceXZ(a,b)==40);
|
||||
// A road aligned with travel must not push a partner behind the300m floor.
|
||||
assert(pair({{1000,10,1250},{1000,10,1450}}));
|
||||
assert(a==vector(1000,10,1300)&&b==vector(1000,10,1360));
|
||||
assert(pair({{1000,10,1450},{1000,10,1250}}));
|
||||
assert(a==vector(1000,10,1300)&&b==vector(1000,10,1360));
|
||||
// Actual sloped segment retains height for subsequent bridge validation.
|
||||
assert(pair({{900,5,1300},{1100,15,1300}}));
|
||||
assert(a==vector(970,8.5,1300)&&b==vector(1030,11.5,1300));
|
||||
// Degenerate segments are harmless but a wholly degenerate road has no span.
|
||||
assert(pair({{900,10,1300},{900,10,1300},{1100,10,1300}}));
|
||||
assert(!pair({{1000,10,1300},{1000,10,1300}}));empty();
|
||||
assert(!pair({{900,10,1300},{NAN,10,1300},{1100,10,1300}}));empty();
|
||||
assert(!pair(Array<vector>(1025,{1000,10,1300})));empty();
|
||||
assert(!pair({}));empty();assert(!pair({{1000,10,1300}}));empty();
|
||||
auto good=Array<vector>{{900,10,1300},{1100,10,1300}};
|
||||
velocity={0,0,0};assert(!pair(good));empty();velocity={0,0,4};
|
||||
seed.x=NAN;assert(!pair(good));empty();seed={1000,10,1300};
|
||||
origin.x=NAN;assert(!pair(good));empty();origin={1000,10,1000};
|
||||
reserved={{970,10,1380}};assert(pair(good));
|
||||
reserved={{970,10,1379.99f}};assert(!pair(good));empty();
|
||||
reserved={{NAN,10,1380}};assert(!pair(good));empty();
|
||||
reserved=Array<vector>(129,{10000,10,10000});assert(!pair(good));empty();reserved.clear();
|
||||
for(float z:{1290.f,1901.f,700.f}){seed.z=z;assert(!pair({{900,10,z},{1100,10,z}}));empty();}seed.z=1300;
|
||||
BaseRoad road;road.points=good;game.ai.manager.roads={&road};
|
||||
int q=game.ai.manager.queries;
|
||||
assert(Rules::SelectPair(&belief,10000,reserved,a,b));assert(game.ai.manager.queries==q+1);
|
||||
assert(a==vector(970,10,1300)&&b==vector(1030,10,1300));
|
||||
assert(ERR_SettlementSafety::calls==2&&ERR_ConvoyPlacement::calls==2);
|
||||
auto select=[&](){a={1,2,3};b={4,5,6};bool ok=Rules::SelectPair(&belief,10000,reserved,a,b);if(!ok)empty();return ok;};
|
||||
// Second position cannot escape settlement, water/collision or bridge checks.
|
||||
int pc=ERR_ConvoyPlacement::calls;ERR_SettlementSafety::rightClear=false;assert(!select());assert(ERR_ConvoyPlacement::calls==pc);ERR_SettlementSafety::rightClear=true;
|
||||
pc=ERR_ConvoyPlacement::calls;ERR_ConvoyPlacement::rightClear=false;assert(!select());assert(ERR_ConvoyPlacement::calls==pc+2);ERR_ConvoyPlacement::rightClear=true;
|
||||
pc=ERR_ConvoyPlacement::calls;ERR_ConvoyPlacement::water=true;assert(!select());assert(ERR_ConvoyPlacement::calls==pc+1);ERR_ConvoyPlacement::water=false;
|
||||
pc=ERR_ConvoyPlacement::calls;ERR_Actors::rightHeight=7;assert(!select());assert(ERR_ConvoyPlacement::calls==pc);ERR_Actors::rightHeight=10;
|
||||
road.points={{900,20,1300},{1100,20,1300}};assert(!select());road.points=good;
|
||||
// Reject a pair assembled from two short unrelated roads, even when their
|
||||
// endpoints could have formed a geometrically spaced chord.
|
||||
BaseRoad left,right;left.points={{960,10,1300},{980,10,1300}};right.points={{1020,10,1300},{1040,10,1300}};game.ai.manager.roads={&left,&right};assert(!select());
|
||||
game.ai.manager.roads={&road};road.width=5.99;assert(!select());road.width=NAN;assert(!select());road.width=6;
|
||||
// All pre-query gates remain cheap; one permitted pair call is one query.
|
||||
q=game.ai.manager.queries;belief.HasVelocity=false;assert(!select());belief.HasVelocity=true;
|
||||
Replication::server=false;assert(!select());Replication::server=true;
|
||||
reserved.resize(129);assert(!select());reserved.clear();
|
||||
assert(game.ai.manager.queries==q);
|
||||
game.ai.available=false;assert(!select());game.ai.available=true;assert(game.ai.manager.queries==q);
|
||||
// Road count bound refuses before any point arrays are read.
|
||||
game.ai.manager.roads=Array<BaseRoad*>(129,&road);int reads=BaseRoad::reads;assert(!select());assert(BaseRoad::reads==reads);
|
||||
game.ai.manager.roads=Array<BaseRoad*>(128,&road);assert(select());assert(BaseRoad::reads==reads+128);
|
||||
// Total4096 segment allowance includes irrelevant/degenerate vertices; an
|
||||
// earlier valid pair cannot leak through once later roads exhaust the cap.
|
||||
BaseRoad big,tail;big.points=Array<vector>(1024,{900,10,1300});big.points.back()={1100,10,1300};tail.points=Array<vector>(5,{900,10,1300});tail.points.back()={1100,10,1300};
|
||||
game.ai.manager.roads={&big,&big,&big,&big,&tail};assert(select());
|
||||
tail.points.insert(tail.points.begin(),{900,10,1300});pc=ERR_ConvoyPlacement::calls;assert(!select());assert(ERR_ConvoyPlacement::calls==pc);
|
||||
// Pair failure still permits unchanged singleton selection in a separate
|
||||
// call; this explicitly consumes the caller's second survey budget.
|
||||
road.points={{995,10,1300},{1005,10,1300}};game.ai.manager.roads={&road};q=game.ai.manager.queries;
|
||||
assert(!select());vector singleton;assert(Rules::Select(&belief,10000,reserved,singleton));assert(singleton==vector(1000,10,1300)&&game.ai.manager.queries==q+2);
|
||||
}
|
||||
|
||||
''')
|
||||
print('PASS: actual paired arc geometry,40m/80m spacing, separate original polylines, both-position validation and128road/4096segment budget; no native driving proof')
|
||||
@@ -0,0 +1,4 @@
|
||||
Scratch proposal only; two files, no new test registration. Requires paired geometry SelectPair and convoy ReserveInterceptPair/ApplyInterceptPair sibling proposals.
|
||||
Actual full HQ methods run with adversarial radio/physical service doubles; baseline compiles then fails new pair requirement. Five focused production mutations compile then fail assertions. Run python3 /tmp/erri-pair-hq-0054/verify.py to reproduce.
|
||||
Preserves singleton API/first-member fixture fields. Completed route handles retained until release; independent receipts prevent repeated advancement. Separate monotonic token allocator never reuses 400001..400256; two-member failures consume two IDs but one belief/revision attempt. Same-task bounded escort combinations avoid greedy partner starvation; total two native geometry calls per pass includes fallback. Both pending members count against existing quotas and reserve both destination cells. No singleton fallback after reservation begins.
|
||||
No engine launch, target write, physical pair movement/hold or multiplayer proof. Native compile and integrated suite remain parent-owned.
|
||||
@@ -0,0 +1,63 @@
|
||||
from pathlib import Path
|
||||
p=Path('/tmp/erri-pair-hq-0054/tests/test_hq_mounted_dispatch.py')
|
||||
s=p.read_text()
|
||||
s=s.replace("names=['override", "names=['protected bool MountedMemberCurrent(', 'protected bool MountedMemberReady(', 'protected ERR_HQMountedChoice MountedChoice(', 'protected bool HasMountedPairCandidate(', 'protected ERR_ConvoyInterceptOrder MountedOrder(', 'protected void QueueMountedIntercept(', 'override")
|
||||
s=s.replace("'ERR_HQBelief']:","'ERR_HQBelief','ERR_HQMountedChoice']:")
|
||||
s=s.replace("'m_MountedInterceptRoute']:","'m_MountedInterceptRoute','partner','partnerUnit','firstUnit','excluded','choice','partnerChoice','bestA','bestB','candidateA','candidateB','candidate','m_MountedInterceptPartner','m_MountedInterceptPartnerUnit','m_MountedInterceptPartnerRoute']:")
|
||||
s=s.replace("['StatusSampler','MountedAmmo','Combat','Receipt']:","['StatusSampler','MountedAmmo','Combat','Receipt','Unit','Task','Vehicle','Route']:")
|
||||
s=s.replace("s=s.replace('int queries;'", "s=s.replace('bool applied;', 'bool applied=false;').replace('bool admitted;', 'bool admitted=false;')\n s=s.replace('int queries;'")
|
||||
s=s.replace('struct Network{ERR_HQRoute*route=nullptr;', 'struct Network{std::map<Entity*,ERR_HQRoute*>routes;ERR_HQRoute*route=nullptr;')
|
||||
s=s.replace('return radio==denied?nullptr:route;', 'return radio==denied?nullptr:(routes.count(radio)?routes[radio]:route);')
|
||||
s=s.replace('bool current=true,reserve=true,apply=true;', 'bool current=true,reserve=true,apply=true;int pairReserves=0,pairApplies=0;std::function<void()>onReserve,onApply;')
|
||||
s=s.replace(' int Count(){return tasks.Count();}', ''' bool ReserveInterceptPair(ERR_ConvoyTask*,ERR_ConvoyInterceptOrder*a,ERR_ConvoyInterceptOrder*b,float){pairReserves++;assert(a->Token!=b->Token&&a->Group!=b->Group);if(onReserve)onReserve();return reserve;}
|
||||
bool ApplyInterceptPair(ERR_ConvoyTask*,ERR_ConvoyInterceptOrder*,ERR_ConvoyInterceptOrder*,float){pairApplies++;if(onApply)onApply();return apply;}
|
||||
int Count(){return tasks.Count();}''')
|
||||
s=s.replace('inline static int selects=0;', 'inline static int selects=0,pairSelects=0;inline static bool pairGeometry=true;')
|
||||
s=s.replace(' static bool Select(ERR_HQBelief', ' static bool SelectPair(ERR_HQBelief*b,float,Array<vector>&,vector&a,vector&c){pairSelects++;a=b->destination;c={a.x+60,a.z};return b->select&&pairGeometry;}\n static bool Select(ERR_HQBelief')
|
||||
s=s.replace('struct HQ:Base{', '''struct ERR_HQMountedChoice{ERR_HQUnit*Unit=nullptr;ERR_ConvoyTask*Task=nullptr;ERR_ConvoyVehicle*Vehicle=nullptr;ERR_HQRoute*Route=nullptr;vector Destination;float Distance=0;};
|
||||
struct HQ:Base{''')
|
||||
s=s.replace(' int m_MountedSurveyCursor=0;', ''' ERR_ConvoyInterceptOrder*m_MountedInterceptPartner=nullptr;ERR_HQUnit*m_MountedInterceptPartnerUnit=nullptr;ERR_HQRoute*m_MountedInterceptPartnerRoute=nullptr;
|
||||
bool m_MountedInterceptReceipt=false,m_MountedInterceptPartnerReceipt=false;int m_MountedTokenCount=0;
|
||||
int m_MountedSurveyCursor=0;''')
|
||||
# single legacy radio count control now pre-pair candidate may ask extra route in viable-two-roster case
|
||||
s=s.replace('&&f.network.calls==2', '&&f.network.calls>=2')
|
||||
# compatible same source baseline test conditional extra methods
|
||||
s=s.replace("for n in names if n not in ('protected bool HasMountedInterceptCandidate(', 'protected bool MountedInterceptTaskAvailable(') or n in P.read_text()", "for n in names if n in P.read_text()")
|
||||
pos=s.index('int main(){')
|
||||
s=s[:pos]+'''struct PairFixture:Fixture{Group group2;Entity entity2,radio2;Sampler sampler2;ERR_HQUnit unit2;ERR_ConvoyVehicle vehicle2;ERR_HQWatchEntry entry2;ERR_HQRoute route2;
|
||||
PairFixture(){unit2=unit;unit2.Group=&group2;unit2.Radio=&radio2;unit2.StatusSampler=&sampler2;unit2.ForceSourceId="partner";entry2=entry;entry2.SourceId="partner";entry2.Position={200,0};watch.second=&entry2;vehicle2=vehicle;vehicle2.Index=3;vehicle2.Group=&group2;vehicle2.Entity=&entity2;task.Vehicles[3]=&vehicle2;h.m_Units.Insert(&unit2);network.routes[&radio2]=&route2;ERR_HQMountedInterceptRules::pairGeometry=true;}
|
||||
void pair(){plan();assert(h.m_MountedIntercept&&h.m_MountedInterceptPartner);assert(h.m_MountedInterceptTask==&task);assert(unit.LogisticsReservation==400001&&unit2.LogisticsReservation==400002);assert(service.pairReserves==1&&service.reserves==0&&service.pairApplies==0);}
|
||||
};
|
||||
'''+s[pos:]
|
||||
pos=s.index('\n}\n\'\'\')')
|
||||
s=s[:pos]+r'''
|
||||
// Paired admission counts and reserves both original slots; one receipt never detours either.
|
||||
{PairFixture f;f.pair();assert(f.h.Assigned()==2&&f.h.HuntingAssigned()==2);Array<vector>r;int a=-1,b=-1;f.h.ReserveUnit(&f.unit,r,a,b);f.h.ReserveUnit(&f.unit2,r,a,b);assert(r.Count()==2&&vector::DistanceXZ(r[0],r[1])>=40);assert(f.h.Status().find("Paired")!=string::npos);
|
||||
f.route.result=1;f.h.TickMountedIntercept(4000);assert(f.h.m_MountedInterceptReceipt&&!f.h.m_MountedInterceptPartnerReceipt&&!f.h.m_MountedInterceptDelivered&&f.service.pairApplies==0&&f.service.applies==0);f.h.TickMountedIntercept(4500);assert(f.route.calls==1&&f.route2.calls==2);
|
||||
f.route2.result=1;f.h.TickMountedIntercept(5000);assert(f.service.pairApplies==1&&f.h.m_MountedInterceptDelivered&&f.h.m_MountedIntercept->ExpiresAt==121000&&f.h.m_MountedInterceptPartner->ExpiresAt==121000);f.h.TickMountedIntercept(6000);assert(f.service.pairApplies==1);f.h.TickMountedIntercept(121000);assert(!f.h.m_MountedIntercept&&!f.h.m_MountedInterceptPartner&&!f.unit.LogisticsReservation&&!f.unit2.LogisticsReservation&&f.service.releases==2);}
|
||||
// Either receipt may arrive first; interrupted/expired/revised/changed ownership never creates half a pair.
|
||||
for(int kind=0;kind<8;kind++){PairFixture f;f.pair();f.route2.result=1;f.h.TickMountedIntercept(4000);assert(f.h.m_MountedInterceptPartnerReceipt&&f.service.pairApplies==0);float now=5000;
|
||||
if(kind==0)f.route.result=-1;
|
||||
if(kind==1)now=121000;
|
||||
if(kind==2)f.belief.BeliefRevision++;
|
||||
if(kind==3)f.unit2.LogisticsReservation=999;
|
||||
if(kind==4)f.unit2.contact=true;
|
||||
if(kind==5)f.entry2.Fit--;
|
||||
if(kind==6)f.sampler2.bound=false;
|
||||
if(kind==7)f.h.assigned=2;
|
||||
if(kind!=0)f.route.result=1;
|
||||
f.h.TickMountedIntercept(now);assert(!f.h.m_MountedIntercept&&!f.h.m_MountedInterceptPartner&&f.service.pairApplies==0&&f.service.applies==0&&!f.unit.LogisticsReservation);assert(f.unit2.LogisticsReservation==(kind==3?999:0));}
|
||||
// Native preparation failure settles BOTH claims. No retry or singleton replacement after commitment.
|
||||
{PairFixture f;f.pair();f.route.result=f.route2.result=1;f.service.apply=false;f.h.TickMountedIntercept(5000);assert(f.service.pairApplies==1&&!f.h.m_MountedIntercept&&!f.unit.LogisticsReservation&&!f.unit2.LogisticsReservation);f.plan();assert(f.service.pairReserves==1&&f.service.reserves==0);}
|
||||
{PairFixture f;f.service.reserve=false;f.plan();assert(f.service.pairReserves==1&&f.service.reserves==0&&!f.unit.LogisticsReservation&&!f.unit2.LogisticsReservation&&f.h.m_MountedTokenCount==2);f.service.reserve=true;f.plan();assert(f.service.pairReserves==1);f.belief.BeliefRevision++;f.plan();assert(f.h.m_MountedIntercept->Token==400003&&f.h.m_MountedInterceptPartner->Token==400004);}
|
||||
// Stop/reentrant unregister detach both owned handles before external release; unrelated claims survive.
|
||||
{PairFixture f;f.pair();f.unit2.LogisticsReservation=999;f.service.onRelease=[&]{assert(!f.h.m_MountedIntercept&&!f.h.m_MountedInterceptPartner);f.h.ReleaseUnit(&f.unit2,5000);};f.h.Stop();f.h.Stop();assert(f.service.releases==2&&!f.unit.LogisticsReservation&&f.unit2.LogisticsReservation==999);}
|
||||
{PairFixture f;f.pair();f.route.result=f.route2.result=1;f.service.onApply=[&]{f.h.Stop();};f.h.TickMountedIntercept(5000);assert(!f.h.m_MountedIntercept&&!f.h.m_MountedInterceptDelivered&&!f.unit.LogisticsReservation&&!f.unit2.LogisticsReservation);}
|
||||
// Two free slots and two IDs are required for pair. Singleton fallback occurs only before claims.
|
||||
for(int kind=0;kind<5;kind++){PairFixture f;if(kind==0)f.h.assigned=2;if(kind==1)f.h.hunting=2;if(kind==2)f.h.m_MountedTokenCount=255;if(kind==3)f.network.denied=&f.radio2;if(kind==4)ERR_HQMountedInterceptRules::pairGeometry=false;int before=ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects;f.plan();assert(f.h.m_MountedIntercept&&!f.h.m_MountedInterceptPartner&&f.service.reserves==1&&f.service.pairReserves==0);assert(ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects-before<=2);}
|
||||
{PairFixture f;f.h.m_MountedTokenCount=256;int before=ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects;f.plan();assert(!f.h.m_MountedIntercept&&ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects==before);}
|
||||
// No mixed-convoy pair; both geometric failures still cost at most two queries, no attempts/tokens.
|
||||
{PairFixture f;ERR_ConvoyTask other=f.task;other.Vehicles[0]=&f.unused;f.task.Vehicles[3]=&f.unused;f.service.tasks.Insert(&other);f.plan();assert(f.h.m_MountedIntercept&&!f.h.m_MountedInterceptPartner&&f.service.pairReserves==0);}
|
||||
{PairFixture f;ERR_HQBelief b2;b2.BeliefId=2;f.belief.select=b2.select=false;f.memory.entries.Insert(&b2);int before=ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects;f.plan();assert(ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects-before==2&&!f.h.m_MountedIntercept&&f.h.m_MountedAttemptBeliefs.empty()&&f.h.m_MountedTokenCount==0);}
|
||||
''' +s[pos:]
|
||||
p.write_text(s)
|
||||
+353
@@ -0,0 +1,353 @@
|
||||
// Detached planning choice; the route remains strongly owned before admission.
|
||||
class ERR_HQMountedChoice
|
||||
{
|
||||
ERR_HQUnit Unit;
|
||||
ERR_ConvoyTask Task;
|
||||
ERR_ConvoyVehicle Vehicle;
|
||||
ref ERR_HQRoute Route;
|
||||
vector Destination;
|
||||
float Distance;
|
||||
}
|
||||
|
||||
// One or two original escorts may intercept a road ahead of delivered travel history.
|
||||
// This HQ-owned transport uses the ordinary radio graph and never reads players.
|
||||
modded class ERR_HQ
|
||||
{
|
||||
protected ref ERR_ConvoyInterceptOrder m_MountedIntercept;
|
||||
protected ERR_HQUnit m_MountedInterceptUnit;
|
||||
protected ERR_ConvoyTask m_MountedInterceptTask;
|
||||
protected ref ERR_HQRoute m_MountedInterceptRoute;
|
||||
protected bool m_MountedInterceptDelivered;
|
||||
protected ref ERR_ConvoyInterceptOrder m_MountedInterceptPartner;
|
||||
protected ERR_HQUnit m_MountedInterceptPartnerUnit;
|
||||
protected ref ERR_HQRoute m_MountedInterceptPartnerRoute;
|
||||
protected bool m_MountedInterceptReceipt;
|
||||
protected bool m_MountedInterceptPartnerReceipt;
|
||||
protected int m_MountedTokenCount;
|
||||
protected int m_MountedSurveyCursor;
|
||||
protected ref array<int> m_MountedAttemptBeliefs = {};
|
||||
protected ref array<int> m_MountedAttemptRevisions = {};
|
||||
|
||||
override string Status()
|
||||
{
|
||||
string result = super.Status();
|
||||
if (!m_MountedIntercept) return result;
|
||||
string phase = "queued";
|
||||
if (m_MountedInterceptDelivered) phase = "delivered";
|
||||
if (m_MountedInterceptPartner) result += "\nPaired mounted interception (two original escorts)";
|
||||
return result + "\n" + string.Format("Mounted interception: escort %1; %2; report M%3; road %4", m_MountedIntercept.VehicleIndex, phase, m_MountedIntercept.ReportId, m_MountedIntercept.Position);
|
||||
}
|
||||
|
||||
protected override int Assigned()
|
||||
{
|
||||
int count = super.Assigned();
|
||||
if (m_MountedIntercept) count++;
|
||||
if (m_MountedInterceptPartner) count++;
|
||||
return count;
|
||||
}
|
||||
protected override int HuntingAssigned()
|
||||
{
|
||||
int count = super.HuntingAssigned();
|
||||
if (m_MountedIntercept) count++;
|
||||
if (m_MountedInterceptPartner) count++;
|
||||
return count;
|
||||
}
|
||||
protected override void ReserveUnit(ERR_HQUnit unit, array<vector> reserved, out int first, out int second)
|
||||
{
|
||||
super.ReserveUnit(unit, reserved, first, second);
|
||||
if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);
|
||||
if (m_MountedInterceptPartner && unit == m_MountedInterceptPartnerUnit) reserved.Insert(m_MountedInterceptPartner.Position);
|
||||
}
|
||||
protected bool MountedInterceptAttempted(int beliefId, int revision)
|
||||
{
|
||||
for (int i = 0; i < m_MountedAttemptBeliefs.Count(); i++)
|
||||
if (m_MountedAttemptBeliefs[i] == beliefId && m_MountedAttemptRevisions[i] == revision) return true;
|
||||
return false;
|
||||
}
|
||||
protected bool MountedInterceptStatus(ERR_HQUnit unit, ERR_ConvoyVehicle vehicle, float now)
|
||||
{
|
||||
if (!unit || !vehicle || !unit.StatusSampler || vehicle.Group != unit.Group || !vehicle.Entity
|
||||
|| !unit.StatusSampler.OwnsRoster(unit.Group) || !unit.StatusSampler.ValidateBoundVehicle(vehicle.Entity)) return false;
|
||||
ERR_HQWatchEntry entry = m_Watch.Find(unit.ForceSourceId);
|
||||
if (!entry || entry.Closed || entry.SourceId != unit.ForceSourceId || entry.MessageId <= 0
|
||||
|| !ERR_HQMemoryRules.DeliveryFresh(entry.ObservedAt, now) || !ERR_ForceRules.Finite(entry.LastDeliveredAt)
|
||||
|| entry.LastDeliveredAt < entry.ObservedAt || entry.LastDeliveredAt > now
|
||||
|| !ERR_ForceRules.FiniteVector(entry.Position) || entry.Count < 1 || entry.Count != entry.Strength
|
||||
|| entry.Fit != entry.Count || !entry.MountedAmmo || !entry.MountedAmmo.Complete || !entry.MountedAmmo.Valid()) return false;
|
||||
return entry.MountedAmmo.Weapons > 0 && entry.MountedAmmo.Rounds + entry.MountedAmmo.Chambers > 0;
|
||||
}
|
||||
protected void ReleaseMountedIntercept(float now, string reason)
|
||||
{
|
||||
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
|
||||
ref ERR_ConvoyInterceptOrder partner = m_MountedInterceptPartner;
|
||||
ERR_HQUnit unit = m_MountedInterceptUnit;
|
||||
ERR_HQUnit partnerUnit = m_MountedInterceptPartnerUnit;
|
||||
ERR_ConvoyTask task = m_MountedInterceptTask;
|
||||
// Detach BOTH first: physical release may synchronously unregister a group.
|
||||
m_MountedIntercept = null; m_MountedInterceptUnit = null; m_MountedInterceptTask = null;
|
||||
m_MountedInterceptPartner = null; m_MountedInterceptPartnerUnit = null;
|
||||
m_MountedInterceptRoute = null; m_MountedInterceptPartnerRoute = null;
|
||||
m_MountedInterceptDelivered = false; m_MountedInterceptReceipt = false; m_MountedInterceptPartnerReceipt = false;
|
||||
if (!order) return;
|
||||
if (m_Convoys) m_Convoys.ReleaseIntercept(task, order, now);
|
||||
if (partner && m_Convoys) m_Convoys.ReleaseIntercept(task, partner, now);
|
||||
if (unit && unit.LogisticsReservation == order.Token) unit.LogisticsReservation = 0;
|
||||
if (partner && partnerUnit && partnerUnit.LogisticsReservation == partner.Token) partnerUnit.LogisticsReservation = 0;
|
||||
m_Journal.Write("MOUNTED INTERCEPT END", reason, order.ReportId);
|
||||
}
|
||||
protected override void ReleaseUnit(ERR_HQUnit unit, float now)
|
||||
{
|
||||
if (m_MountedIntercept && (unit == m_MountedInterceptUnit || unit == m_MountedInterceptPartnerUnit)) ReleaseMountedIntercept(now, "original formation released");
|
||||
super.ReleaseUnit(unit, now);
|
||||
}
|
||||
protected bool MountedMemberCurrent(ERR_HQUnit unit, ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
if (!order || !unit || !m_Units.Contains(unit) || unit.Group != order.Group) return false;
|
||||
if (unit.LogisticsReservation != order.Token || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)) return false;
|
||||
return m_Convoys && m_Convoys.InterceptCurrent(m_MountedInterceptTask, order);
|
||||
}
|
||||
protected bool MountedMemberReady(ERR_HQUnit unit, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!MountedMemberCurrent(unit, order)) return false;
|
||||
ERR_ConvoyVehicle vehicle = m_MountedInterceptTask.Vehicles[order.VehicleIndex];
|
||||
return MountedInterceptStatus(unit, vehicle, now) && !ConvoyContact(unit, now);
|
||||
}
|
||||
protected override void TickMountedIntercept(float now)
|
||||
{
|
||||
if (!m_MountedIntercept || !ERR_ForceRules.Finite(now)) return;
|
||||
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
|
||||
ref ERR_ConvoyInterceptOrder partner = m_MountedInterceptPartner;
|
||||
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
|
||||
if (now < order.IssuedAt || now >= order.ExpiresAt || !MissionActive() || m_Stopping
|
||||
|| Assigned() > globalLimit || HuntingAssigned() > ERR_HQPursuitRules.Limit(globalLimit)
|
||||
|| !MountedMemberCurrent(m_MountedInterceptUnit, order))
|
||||
{ ReleaseMountedIntercept(now, "expiry, capacity or original ownership changed"); return; }
|
||||
if (partner && !MountedMemberCurrent(m_MountedInterceptPartnerUnit, partner))
|
||||
{ ReleaseMountedIntercept(now, "partner ownership changed"); return; }
|
||||
if (m_MountedInterceptDelivered) return;
|
||||
ERR_HQBelief belief = m_Memory.GetById(order.BeliefId);
|
||||
if (!ERR_HQMountedInterceptRules.Current(order, belief, now))
|
||||
{ ReleaseMountedIntercept(now, "delivered history changed before command arrival"); return; }
|
||||
if (partner && !ERR_HQMountedInterceptRules.Current(partner, belief, now))
|
||||
{ ReleaseMountedIntercept(now, "partner command expired"); return; }
|
||||
if (!m_MountedInterceptReceipt)
|
||||
{
|
||||
if (!m_MountedInterceptRoute) { ReleaseMountedIntercept(now, "missing radio route"); return; }
|
||||
int delivered = m_MountedInterceptRoute.Advance(now, m_Journal);
|
||||
if (m_MountedIntercept != order) return;
|
||||
if (delivered < 0) { ReleaseMountedIntercept(now, "radio interrupted; authored route retained"); return; }
|
||||
if (delivered > 0) { m_MountedInterceptReceipt = true; }
|
||||
}
|
||||
if (partner && !m_MountedInterceptPartnerReceipt)
|
||||
{
|
||||
if (!m_MountedInterceptPartnerRoute) { ReleaseMountedIntercept(now, "missing partner route"); return; }
|
||||
int partnerDelivered = m_MountedInterceptPartnerRoute.Advance(now, m_Journal);
|
||||
if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
if (partnerDelivered < 0) { ReleaseMountedIntercept(now, "partner radio interrupted"); return; }
|
||||
if (partnerDelivered > 0) { m_MountedInterceptPartnerReceipt = true; }
|
||||
}
|
||||
if (!m_MountedInterceptReceipt || (partner && !m_MountedInterceptPartnerReceipt)) return;
|
||||
if (!MountedMemberReady(m_MountedInterceptUnit, order, now))
|
||||
{ ReleaseMountedIntercept(now, "command refused by original crew"); return; }
|
||||
if (partner && !MountedMemberReady(m_MountedInterceptPartnerUnit, partner, now))
|
||||
{ ReleaseMountedIntercept(now, "command refused by original partner"); return; }
|
||||
bool applied;
|
||||
if (partner) applied = m_Convoys.ApplyInterceptPair(m_MountedInterceptTask, order, partner, now);
|
||||
else applied = m_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now);
|
||||
if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
if (!applied) { ReleaseMountedIntercept(now, "physical command refused"); return; }
|
||||
m_MountedInterceptDelivered = true;
|
||||
m_Journal.Write("MOUNTED INTERCEPT DELIVERED", "original armed escorts ordered to remembered road positions", order.ReportId);
|
||||
}
|
||||
protected bool MountedInterceptTaskAvailable(ERR_ConvoyTask task)
|
||||
{
|
||||
if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) return false;
|
||||
// Service admission excludes the whole original convoy, including sibling
|
||||
// holds still in transit. Do not spend a delivered revision on a busy task.
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || vehicle.InterceptToken > 0 || vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) return false;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
if (unit.Group == vehicle.Group && (unit.PendingConvoyOrder || unit.Downlink || unit.LogisticsReservation > 0)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected bool HasMountedInterceptCandidate(float now)
|
||||
{
|
||||
// Avoid native road surveys until one original escort has usable delivered status.
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|
||||
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
if (unit.Group == vehicle.Group && MountedLogisticsEligible(unit) && !ConvoyContact(unit, now)
|
||||
&& MountedInterceptStatus(unit, vehicle, now)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected ERR_HQMountedChoice MountedChoice(ERR_ConvoyTask task, vector destination, ERR_HQUnit excluded, float now, ERR_ConvoyVehicle required = null)
|
||||
{
|
||||
if (!MountedInterceptTaskAvailable(task)) return null;
|
||||
ref ERR_HQMountedChoice best;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (required && vehicle != required) continue;
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (unit == excluded || unit.Group != vehicle.Group || !MountedLogisticsEligible(unit)
|
||||
|| ConvoyContact(unit, now) || !MountedInterceptStatus(unit, vehicle, now)) continue;
|
||||
ERR_HQWatchEntry own = m_Watch.Find(unit.ForceSourceId);
|
||||
float distance = vector.DistanceXZ(own.Position, destination);
|
||||
if (distance > m_Mode.Settings().Runtime("responseRadiusMetres") || (best && distance >= best.Distance)) continue;
|
||||
ref ERR_HQRoute route = m_Network.Route(unit.Radio, true);
|
||||
if (!route) continue;
|
||||
best = new ERR_HQMountedChoice();
|
||||
best.Unit = unit; best.Task = task; best.Vehicle = vehicle; best.Route = route;
|
||||
best.Destination = destination; best.Distance = distance;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
protected bool HasMountedPairCandidate(float now)
|
||||
{
|
||||
// Cheap original/status gate; no geometry or claims before both roles exist.
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
ERR_HQUnit firstUnit;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now)
|
||||
|| !MountedInterceptStatus(unit, vehicle, now)) continue;
|
||||
if (firstUnit && firstUnit != unit && firstUnit.Group != unit.Group) return true;
|
||||
firstUnit = unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected ERR_ConvoyInterceptOrder MountedOrder(ERR_HQMountedChoice choice, ERR_HQBelief belief, float now, int token)
|
||||
{
|
||||
ERR_ConvoyInterceptOrder order = new ERR_ConvoyInterceptOrder();
|
||||
order.Token = token; order.Group = choice.Unit.Group; order.SourceId = choice.Task.Receipt.SourceId;
|
||||
order.VehicleIndex = choice.Vehicle.Index; order.Position = choice.Destination;
|
||||
order.BeliefId = belief.BeliefId; order.BeliefRevision = belief.BeliefRevision;
|
||||
order.ReportId = belief.MessageId; order.ObservedAt = belief.ObservedAt;
|
||||
order.IssuedAt = now; order.ExpiresAt = belief.ObservedAt + 120000;
|
||||
return order;
|
||||
}
|
||||
protected void QueueMountedIntercept(ERR_HQMountedChoice choice, ERR_HQMountedChoice partnerChoice, ERR_HQBelief belief, float now)
|
||||
{
|
||||
int members = 1;
|
||||
if (partnerChoice) members = 2;
|
||||
if (m_MountedTokenCount + members > 256) return;
|
||||
ref ERR_ConvoyInterceptOrder order = MountedOrder(choice, belief, now, 400001 + m_MountedTokenCount);
|
||||
ref ERR_ConvoyInterceptOrder partner;
|
||||
if (partnerChoice) partner = MountedOrder(partnerChoice, belief, now, 400002 + m_MountedTokenCount);
|
||||
if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) return;
|
||||
if (partner && !ERR_HQMountedInterceptRules.Current(partner, belief, now)) return;
|
||||
// IDs and this revision remain spent after refusal; no post-claim fallback.
|
||||
m_MountedTokenCount += members;
|
||||
m_MountedAttemptBeliefs.Insert(order.BeliefId); m_MountedAttemptRevisions.Insert(order.BeliefRevision);
|
||||
choice.Unit.LogisticsReservation = order.Token;
|
||||
if (partnerChoice) partnerChoice.Unit.LogisticsReservation = partner.Token;
|
||||
m_MountedIntercept = order; m_MountedInterceptUnit = choice.Unit; m_MountedInterceptTask = choice.Task;
|
||||
m_MountedInterceptRoute = choice.Route; m_MountedInterceptDelivered = false; m_MountedInterceptReceipt = false;
|
||||
m_MountedInterceptPartner = partner; m_MountedInterceptPartnerReceipt = false;
|
||||
if (partnerChoice) { m_MountedInterceptPartnerUnit = partnerChoice.Unit; m_MountedInterceptPartnerRoute = partnerChoice.Route; }
|
||||
bool admitted;
|
||||
if (partner) admitted = m_Convoys.ReserveInterceptPair(choice.Task, order, partner, now);
|
||||
else admitted = m_Convoys.ReserveIntercept(choice.Task, order, now);
|
||||
if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
if (!admitted) { ReleaseMountedIntercept(now, "reservation refused"); return; }
|
||||
choice.Route.MessageId = m_Journal.Message();
|
||||
choice.Route.Payload = string.Format("original escort %1 to road %2; delivered track %3 revision %4 report M%5", order.VehicleIndex, order.Position, order.BeliefId, order.BeliefRevision, order.ReportId);
|
||||
m_Journal.Write("MOUNTED INTERCEPT QUEUED", choice.Route.Payload, choice.Route.MessageId);
|
||||
if (partnerChoice)
|
||||
{
|
||||
partnerChoice.Route.MessageId = m_Journal.Message();
|
||||
partnerChoice.Route.Payload = string.Format("original partner %1 to road %2; report M%3", partner.VehicleIndex, partner.Position, partner.ReportId);
|
||||
m_Journal.Write("MOUNTED INTERCEPT QUEUED", partnerChoice.Route.Payload, partnerChoice.Route.MessageId);
|
||||
}
|
||||
}
|
||||
protected override void PlanMountedIntercept(float now)
|
||||
{
|
||||
if (!Replication.IsServer() || m_Stopping || !MissionActive() || m_MountedIntercept
|
||||
|| m_MountedAttemptBeliefs.Count() >= 256 || m_MountedTokenCount >= 256) return;
|
||||
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
|
||||
int freeGlobal = globalLimit - Assigned();
|
||||
int freeHunt = ERR_HQPursuitRules.Limit(globalLimit) - HuntingAssigned();
|
||||
if (freeGlobal < 1 || freeHunt < 1 || !HasMountedInterceptCandidate(now)) return;
|
||||
bool pairPossible = freeGlobal >= 2 && freeHunt >= 2 && m_MountedTokenCount <= 254 && HasMountedPairCandidate(now);
|
||||
array<vector> reserved = {}; int first = -1; int second = -1;
|
||||
foreach (ERR_HQUnit known : m_Units) ReserveUnit(known, reserved, first, second);
|
||||
int count = m_Memory.Count();
|
||||
if (count == 0) return;
|
||||
int start = m_MountedSurveyCursor % count;
|
||||
int queries;
|
||||
for (int scanned = 0; scanned < count && queries < 2; scanned++)
|
||||
{
|
||||
int b = (start + scanned) % count;
|
||||
m_MountedSurveyCursor = (b + 1) % count;
|
||||
ERR_HQBelief belief = m_Memory.At(b);
|
||||
if (!ERR_HQMountedInterceptRules.Eligible(belief, now) || MountedInterceptAttempted(belief.BeliefId, belief.BeliefRevision)) continue;
|
||||
if (pairPossible)
|
||||
{
|
||||
vector positionA; vector positionB; queries++;
|
||||
if (ERR_HQMountedInterceptRules.SelectPair(belief, now, reserved, positionA, positionB))
|
||||
{
|
||||
ref ERR_HQMountedChoice bestA; ref ERR_HQMountedChoice bestB;
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
// Enumerate the bounded original escorts: a greedy nearest first crew
|
||||
// can be the only crew within range of the other road position.
|
||||
foreach (ERR_ConvoyVehicle firstVehicle : task.Vehicles)
|
||||
{
|
||||
if (!firstVehicle || (firstVehicle.Index != 0 && firstVehicle.Index != 3 && firstVehicle.Index != 5)) continue;
|
||||
ref ERR_HQMountedChoice candidateA = MountedChoice(task, positionA, null, now, firstVehicle);
|
||||
if (!candidateA) continue;
|
||||
ref ERR_HQMountedChoice candidateB = MountedChoice(task, positionB, candidateA.Unit, now);
|
||||
if (!candidateB || candidateA.Unit.Group == candidateB.Unit.Group || candidateA.Route == candidateB.Route) continue;
|
||||
if (bestA && candidateA.Distance + candidateB.Distance >= bestA.Distance + bestB.Distance) continue;
|
||||
bestA = candidateA; bestB = candidateB;
|
||||
}
|
||||
}
|
||||
if (bestA) { QueueMountedIntercept(bestA, bestB, belief, now); return; }
|
||||
}
|
||||
}
|
||||
// Only pre-admission fallback, sharing the SAME two-survey pass budget.
|
||||
if (queries >= 2) continue;
|
||||
vector destination; queries++;
|
||||
if (!ERR_HQMountedInterceptRules.Select(belief, now, reserved, destination)) continue;
|
||||
ref ERR_HQMountedChoice best;
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ref ERR_HQMountedChoice candidate = MountedChoice(m_Convoys.At(t), destination, null, now);
|
||||
if (candidate && (!best || candidate.Distance < best.Distance)) best = candidate;
|
||||
}
|
||||
if (best) { QueueMountedIntercept(best, null, belief, now); return; }
|
||||
}
|
||||
}
|
||||
override void Stop()
|
||||
{
|
||||
float now;
|
||||
if (GetGame() && GetGame().GetWorld()) now = GetGame().GetWorld().GetWorldTime();
|
||||
ReleaseMountedIntercept(now, "mission stopped");
|
||||
super.Stop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1,19 @@
|
||||
check: /tmp/err-policy-wrzwdkx6/check.cpp:265: void PairFixture::pair(): Assertion `h.m_MountedIntercept&&h.m_MountedInterceptPartner' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-pair-hq-0054/tests/test_hq_mounted_dispatch.py", line 25, in <module>
|
||||
run(r'''
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<135 lines>...
|
||||
}
|
||||
^
|
||||
''')
|
||||
^^^^
|
||||
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
|
||||
subprocess.run([str(p/'check')],check=True)
|
||||
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/usr/lib/python3.14/subprocess.py", line 578, in run
|
||||
raise CalledProcessError(retcode, process.args,
|
||||
output=stdout, stderr=stderr)
|
||||
subprocess.CalledProcessError: Command '['/tmp/err-policy-wrzwdkx6/check']' died with <Signals.SIGABRT: 6>.
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
// One physical escort may intercept a road ahead of delivered travel history.
|
||||
// This HQ-owned transport uses the ordinary radio graph and never reads players.
|
||||
modded class ERR_HQ
|
||||
{
|
||||
protected ref ERR_ConvoyInterceptOrder m_MountedIntercept;
|
||||
protected ERR_HQUnit m_MountedInterceptUnit;
|
||||
protected ERR_ConvoyTask m_MountedInterceptTask;
|
||||
protected ref ERR_HQRoute m_MountedInterceptRoute;
|
||||
protected bool m_MountedInterceptDelivered;
|
||||
protected int m_MountedSurveyCursor;
|
||||
protected ref array<int> m_MountedAttemptBeliefs = {};
|
||||
protected ref array<int> m_MountedAttemptRevisions = {};
|
||||
|
||||
override string Status()
|
||||
{
|
||||
string result = super.Status();
|
||||
if (!m_MountedIntercept) return result;
|
||||
string phase = "queued";
|
||||
if (m_MountedInterceptDelivered) phase = "delivered";
|
||||
return result + "\n" + string.Format("Mounted interception: escort %1; %2; report M%3; road %4", m_MountedIntercept.VehicleIndex, phase, m_MountedIntercept.ReportId, m_MountedIntercept.Position);
|
||||
}
|
||||
|
||||
protected override int Assigned()
|
||||
{
|
||||
int count = super.Assigned();
|
||||
if (m_MountedIntercept) count++;
|
||||
return count;
|
||||
}
|
||||
protected override int HuntingAssigned()
|
||||
{
|
||||
int count = super.HuntingAssigned();
|
||||
if (m_MountedIntercept) count++;
|
||||
return count;
|
||||
}
|
||||
protected override void ReserveUnit(ERR_HQUnit unit, array<vector> reserved, out int first, out int second)
|
||||
{
|
||||
super.ReserveUnit(unit, reserved, first, second);
|
||||
if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);
|
||||
}
|
||||
protected bool MountedInterceptAttempted(int beliefId, int revision)
|
||||
{
|
||||
for (int i = 0; i < m_MountedAttemptBeliefs.Count(); i++)
|
||||
if (m_MountedAttemptBeliefs[i] == beliefId && m_MountedAttemptRevisions[i] == revision) return true;
|
||||
return false;
|
||||
}
|
||||
protected bool MountedInterceptStatus(ERR_HQUnit unit, ERR_ConvoyVehicle vehicle, float now)
|
||||
{
|
||||
if (!unit || !vehicle || !unit.StatusSampler || vehicle.Group != unit.Group || !vehicle.Entity
|
||||
|| !unit.StatusSampler.OwnsRoster(unit.Group) || !unit.StatusSampler.ValidateBoundVehicle(vehicle.Entity)) return false;
|
||||
ERR_HQWatchEntry entry = m_Watch.Find(unit.ForceSourceId);
|
||||
if (!entry || entry.Closed || entry.SourceId != unit.ForceSourceId || entry.MessageId <= 0
|
||||
|| !ERR_HQMemoryRules.DeliveryFresh(entry.ObservedAt, now) || !ERR_ForceRules.Finite(entry.LastDeliveredAt)
|
||||
|| entry.LastDeliveredAt < entry.ObservedAt || entry.LastDeliveredAt > now
|
||||
|| !ERR_ForceRules.FiniteVector(entry.Position) || entry.Count < 1 || entry.Count != entry.Strength
|
||||
|| entry.Fit != entry.Count || !entry.MountedAmmo || !entry.MountedAmmo.Complete || !entry.MountedAmmo.Valid()) return false;
|
||||
return entry.MountedAmmo.Weapons > 0 && entry.MountedAmmo.Rounds + entry.MountedAmmo.Chambers > 0;
|
||||
}
|
||||
protected void ReleaseMountedIntercept(float now, string reason)
|
||||
{
|
||||
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
|
||||
ERR_HQUnit unit = m_MountedInterceptUnit;
|
||||
ERR_ConvoyTask task = m_MountedInterceptTask;
|
||||
// Detach first: physical release may synchronously unregister the group.
|
||||
m_MountedIntercept = null; m_MountedInterceptUnit = null; m_MountedInterceptTask = null;
|
||||
m_MountedInterceptRoute = null; m_MountedInterceptDelivered = false;
|
||||
if (!order) return;
|
||||
if (m_Convoys) m_Convoys.ReleaseIntercept(task, order, now);
|
||||
if (unit && unit.LogisticsReservation == order.Token) unit.LogisticsReservation = 0;
|
||||
m_Journal.Write("MOUNTED INTERCEPT END", reason, order.ReportId);
|
||||
}
|
||||
protected override void ReleaseUnit(ERR_HQUnit unit, float now)
|
||||
{
|
||||
if (m_MountedIntercept && unit == m_MountedInterceptUnit) ReleaseMountedIntercept(now, "original formation released");
|
||||
super.ReleaseUnit(unit, now);
|
||||
}
|
||||
protected override void TickMountedIntercept(float now)
|
||||
{
|
||||
if (!m_MountedIntercept || !ERR_ForceRules.Finite(now)) return;
|
||||
ERR_HQUnit unit = m_MountedInterceptUnit;
|
||||
ERR_ConvoyInterceptOrder order = m_MountedIntercept;
|
||||
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
|
||||
if (now < order.IssuedAt || now >= order.ExpiresAt
|
||||
|| !MissionActive() || m_Stopping || !unit || !m_Units.Contains(unit) || unit.Group != order.Group
|
||||
|| unit.LogisticsReservation != order.Token || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)
|
||||
|| Assigned() > globalLimit || HuntingAssigned() > ERR_HQPursuitRules.Limit(globalLimit)
|
||||
|| !m_Convoys.InterceptCurrent(m_MountedInterceptTask, order))
|
||||
{ ReleaseMountedIntercept(now, "expiry, capacity or original ownership changed"); return; }
|
||||
if (m_MountedInterceptDelivered) return;
|
||||
ERR_HQBelief belief = m_Memory.GetById(order.BeliefId);
|
||||
if (!ERR_HQMountedInterceptRules.Current(order, belief, now) || !m_MountedInterceptRoute)
|
||||
{ ReleaseMountedIntercept(now, "delivered history changed before command arrival"); return; }
|
||||
int delivered = m_MountedInterceptRoute.Advance(now, m_Journal);
|
||||
if (delivered == 0) return;
|
||||
if (delivered < 0)
|
||||
{ ReleaseMountedIntercept(now, "radio interrupted; authored route retained"); return; }
|
||||
ERR_ConvoyVehicle vehicle = m_MountedInterceptTask.Vehicles[order.VehicleIndex];
|
||||
if (!MountedInterceptStatus(unit, vehicle, now) || ConvoyContact(unit, now)
|
||||
|| !m_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now))
|
||||
{ ReleaseMountedIntercept(now, "command refused by original crew"); return; }
|
||||
m_MountedInterceptRoute = null; m_MountedInterceptDelivered = true;
|
||||
m_Journal.Write("MOUNTED INTERCEPT DELIVERED", "original armed escort ordered to the remembered road ahead", order.ReportId);
|
||||
}
|
||||
protected bool MountedInterceptTaskAvailable(ERR_ConvoyTask task)
|
||||
{
|
||||
if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) return false;
|
||||
// Service admission excludes the whole original convoy, including sibling
|
||||
// holds still in transit. Do not spend a delivered revision on a busy task.
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || vehicle.InterceptToken > 0 || vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) return false;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
if (unit.Group == vehicle.Group && (unit.PendingConvoyOrder || unit.Downlink || unit.LogisticsReservation > 0)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected bool HasMountedInterceptCandidate(float now)
|
||||
{
|
||||
// Avoid native road surveys until one original escort has usable delivered status.
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|
||||
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
if (unit.Group == vehicle.Group && MountedLogisticsEligible(unit) && !ConvoyContact(unit, now)
|
||||
&& MountedInterceptStatus(unit, vehicle, now)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected override void PlanMountedIntercept(float now)
|
||||
{
|
||||
if (!Replication.IsServer() || m_Stopping || !MissionActive() || m_MountedIntercept || m_MountedAttemptBeliefs.Count() >= 256) return;
|
||||
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
|
||||
if (Assigned() >= globalLimit || HuntingAssigned() >= ERR_HQPursuitRules.Limit(globalLimit)) return;
|
||||
if (!HasMountedInterceptCandidate(now)) return;
|
||||
array<vector> reserved = {}; int first = -1; int second = -1;
|
||||
foreach (ERR_HQUnit known : m_Units) ReserveUnit(known, reserved, first, second);
|
||||
// At most two bounded native road queries per normal five-second HQ pass.
|
||||
// Continue the next pass after the last surveyed track, so unsuitable roads
|
||||
// for older reports cannot indefinitely hide later delivered tracks.
|
||||
int count = m_Memory.Count();
|
||||
if (count == 0) return;
|
||||
int start = m_MountedSurveyCursor % count;
|
||||
int queries;
|
||||
for (int scanned = 0; scanned < count && queries < 2; scanned++)
|
||||
{
|
||||
int b = (start + scanned) % count;
|
||||
m_MountedSurveyCursor = (b + 1) % count;
|
||||
ERR_HQBelief belief = m_Memory.At(b);
|
||||
if (!ERR_HQMountedInterceptRules.Eligible(belief, now) || MountedInterceptAttempted(belief.BeliefId, belief.BeliefRevision)) continue;
|
||||
vector destination; queries++;
|
||||
if (!ERR_HQMountedInterceptRules.Select(belief, now, reserved, destination)) continue;
|
||||
ERR_HQUnit best;
|
||||
ERR_ConvoyTask bestTask;
|
||||
ERR_ConvoyVehicle bestVehicle;
|
||||
ERR_HQRoute bestRoute;
|
||||
float bestDistance = 1e20;
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|
||||
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now)
|
||||
|| !MountedInterceptStatus(unit, vehicle, now)) continue;
|
||||
ERR_HQWatchEntry own = m_Watch.Find(unit.ForceSourceId);
|
||||
float distance = vector.DistanceXZ(own.Position, destination);
|
||||
if (distance > m_Mode.Settings().Runtime("responseRadiusMetres") || distance >= bestDistance) continue;
|
||||
ERR_HQRoute candidateRoute = m_Network.Route(unit.Radio, true);
|
||||
if (!candidateRoute) continue;
|
||||
best = unit; bestTask = task; bestVehicle = vehicle; bestRoute = candidateRoute; bestDistance = distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!best) continue;
|
||||
ERR_HQRoute route = bestRoute;
|
||||
if (!route) continue;
|
||||
ERR_ConvoyInterceptOrder order = new ERR_ConvoyInterceptOrder();
|
||||
order.Token = 400001 + m_MountedAttemptBeliefs.Count(); order.Group = best.Group;
|
||||
order.SourceId = bestTask.Receipt.SourceId; order.VehicleIndex = bestVehicle.Index;
|
||||
order.Position = destination; order.BeliefId = belief.BeliefId; order.BeliefRevision = belief.BeliefRevision;
|
||||
order.ReportId = belief.MessageId; order.ObservedAt = belief.ObservedAt;
|
||||
order.IssuedAt = now; order.ExpiresAt = belief.ObservedAt + 120000;
|
||||
if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) continue;
|
||||
// The attempt stays spent after radio interruption, physical refusal or loss.
|
||||
m_MountedAttemptBeliefs.Insert(order.BeliefId); m_MountedAttemptRevisions.Insert(order.BeliefRevision);
|
||||
best.LogisticsReservation = order.Token;
|
||||
if (!m_Convoys.ReserveIntercept(bestTask, order, now))
|
||||
{ if (best.LogisticsReservation == order.Token) best.LogisticsReservation = 0; return; }
|
||||
m_MountedIntercept = order; m_MountedInterceptUnit = best; m_MountedInterceptTask = bestTask;
|
||||
m_MountedInterceptRoute = route; m_MountedInterceptDelivered = false;
|
||||
route.MessageId = m_Journal.Message();
|
||||
route.Payload = string.Format("original escort %1 to road %2; delivered track %3 revision %4 report M%5", order.VehicleIndex, destination, order.BeliefId, order.BeliefRevision, order.ReportId);
|
||||
m_Journal.Write("MOUNTED INTERCEPT QUEUED", route.Payload, route.MessageId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
override void Stop()
|
||||
{
|
||||
float now;
|
||||
if (GetGame() && GetGame().GetWorld()) now = GetGame().GetWorld().GetWorldTime();
|
||||
ReleaseMountedIntercept(now, "mission stopped");
|
||||
super.Stop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
"""Execute actual mounted HQ dispatch methods; radio/physical service are adversarial doubles."""
|
||||
from policy_harness import ROOT, body, run
|
||||
from pathlib import Path
|
||||
import os,re
|
||||
P=Path(os.environ.get('ERR_HQ_MOUNTED_DISPATCH_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c'))
|
||||
names=['override string Status(', 'protected override int Assigned(', 'protected override int HuntingAssigned(', 'protected override void ReserveUnit(', 'protected bool MountedInterceptAttempted(', 'protected bool MountedInterceptStatus(', 'protected void ReleaseMountedIntercept(', 'protected override void ReleaseUnit(', 'protected override void TickMountedIntercept(', 'protected bool MountedInterceptTaskAvailable(', 'protected bool HasMountedInterceptCandidate(', 'protected override void PlanMountedIntercept(', 'override void Stop(']
|
||||
def cpp(s):
|
||||
s=s.replace('protected ','').replace('override ','').replace('ref ','').replace('out int ','int& ').replace('super.','Base::').replace('null','nullptr').replace('foreach (','for (')
|
||||
for typ in ['ERR_ConvoyInterceptOrder','ERR_HQUnit','ERR_ConvoyTask','ERR_HQRoute','ERR_ConvoyVehicle','ERR_HQWatchEntry','ERR_HQBelief']:
|
||||
s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
|
||||
s=s.replace('array<vector> reserved','Array<vector> reserved')
|
||||
for typ in ['Replication','ERR_ForceRules','ERR_HQMemoryRules','ERR_HQPursuitRules','ERR_HQMountedInterceptRules','vector','string']:
|
||||
s=s.replace(typ+'.',typ+'::')
|
||||
for var in ['unit','vehicle','entry','order','task','belief','known','best','bestTask','bestVehicle','route','candidateRoute','own','m_Mode','m_Watch','m_Memory','m_Convoys','m_Journal','m_Network','m_MountedIntercept','m_MountedInterceptTask','m_MountedInterceptRoute']:
|
||||
s=re.sub(r'\b'+var+r'\.',var+'->',s)
|
||||
for name in ['StatusSampler','MountedAmmo','Combat','Receipt']:
|
||||
s=s.replace('->'+name+'.','->'+name+'->')
|
||||
s=s.replace('GetGame().','GetGame()->').replace('GetWorld().','GetWorld()->').replace('Settings().','Settings()->')
|
||||
s=re.sub(r'(ERR_\w+\*) (\w+);',r'\1 \2 = nullptr;',s)
|
||||
s=s.replace('int queries;','int queries=0;').replace('float now;','float now=0;')
|
||||
s=s.replace('Array<vector> reserved,','Array<vector>& reserved,')
|
||||
return s
|
||||
methods='\n'.join(cpp(body(P,n)) for n in names if n not in ('protected bool HasMountedInterceptCandidate(', 'protected bool MountedInterceptTaskAvailable(') or n in P.read_text())
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <sstream>
|
||||
struct string:std::string{using std::string::string;string(std::string s):std::string(s){} template<class...T>static string Format(const char*fmt,T...v){std::ostringstream out;out<<fmt;((out<<"|"<<v),...);return out.str();}};
|
||||
struct vector{float x=0,z=0;static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}bool operator==(vector b)const{return x==b.x&&z==b.z;}};
|
||||
std::ostream&operator<<(std::ostream&o,vector v){return o<<v.x<<","<<v.z;}
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}void Insert(T v){this->push_back(v);}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}};
|
||||
struct Group{};struct Entity{};struct Sampler{bool roster=true,bound=true;bool OwnsRoster(Group*){return roster;}bool ValidateBoundVehicle(Entity*){return bound;}};
|
||||
struct Ammo{bool Complete=true,valid=true;int Weapons=1,Rounds=20,Chambers=0;bool Valid(){return valid;}};
|
||||
struct ERR_HQWatchEntry{bool Closed=false;string SourceId="crew";int MessageId=10,Count=3,Strength=3,Fit=3;float ObservedAt=1000,LastDeliveredAt=2000;vector Position;Ammo*MountedAmmo=nullptr;};
|
||||
struct ERR_HQUnit{struct Group*Group=nullptr;Sampler*StatusSampler=nullptr;string ForceSourceId="crew";int LogisticsReservation=0;bool eligible=true,contact=false,PendingConvoyOrder=false,Downlink=false;Entity*Radio=nullptr;};
|
||||
struct CombatState{bool Held=false;};struct Receipt{string SourceId="convoy";};
|
||||
struct ERR_ConvoyVehicle{struct Group*Group=nullptr;struct Entity*Entity=nullptr;int Index=0,InterceptToken=0,SupplyToken=0;bool Released=false,Arrived=false,Registered=true,SupplyHeld=false;CombatState*Combat=nullptr;};
|
||||
struct ERR_ConvoyTask{bool Stopped=false,Ready=true;struct Receipt*Receipt=nullptr;Array<ERR_ConvoyVehicle*>Vehicles;};
|
||||
struct ERR_ConvoyInterceptOrder{int Token=0,VehicleIndex=0,BeliefId=0,BeliefRevision=0,ReportId=0;struct Group*Group=nullptr;string SourceId;vector Position;float IssuedAt=0,ExpiresAt=0,ObservedAt=0;};
|
||||
struct ERR_HQBelief{int BeliefId=1,BeliefRevision=2,MessageId=20;float ObservedAt=1000;bool eligible=true,select=true;vector destination{100,0};};
|
||||
struct Journal{int rows=0;int Message(){return 200;}void Write(string,string,int=0){rows++;}};
|
||||
struct ERR_HQRoute{int result=0,calls=0,MessageId=0;string Payload;int Advance(float,Journal*){calls++;return result;}};
|
||||
struct Watch{ERR_HQWatchEntry*entry=nullptr,*second=nullptr;int SquadLimit(int n){return n;}ERR_HQWatchEntry*Find(string id){return entry&&entry->SourceId==id?entry:(second&&second->SourceId==id?second:nullptr);}};
|
||||
struct Memory{Array<ERR_HQBelief*>entries;int Count(){return entries.Count();}ERR_HQBelief*At(int n){return entries[n];}ERR_HQBelief*GetById(int n){for(auto b:entries)if(b->BeliefId==n)return b;return nullptr;}};
|
||||
struct Network{ERR_HQRoute*route=nullptr;Entity*denied=nullptr;int calls=0;ERR_HQRoute*Route(Entity*radio,bool){calls++;return radio==denied?nullptr:route;}};
|
||||
struct Settings{int cap=3,radius=1000;int Runtime(string s){return s=="supportSquadLimit"?cap:radius;}};
|
||||
struct Mode{struct Settings settings;struct Settings*Settings(){return &settings;}};
|
||||
struct Service{Array<ERR_ConvoyTask*>tasks;bool current=true,reserve=true,apply=true;int reserves=0,applies=0,releases=0;std::function<void()>onRelease;
|
||||
int Count(){return tasks.Count();}ERR_ConvoyTask*At(int n){return tasks[n];}
|
||||
bool InterceptCurrent(ERR_ConvoyTask*,ERR_ConvoyInterceptOrder*){return current;}
|
||||
bool ReserveIntercept(ERR_ConvoyTask*,ERR_ConvoyInterceptOrder*,float){reserves++;return reserve;}
|
||||
bool ApplyIntercept(ERR_ConvoyTask*,ERR_ConvoyInterceptOrder*,float){applies++;return apply;}
|
||||
void ReleaseIntercept(ERR_ConvoyTask*,ERR_ConvoyInterceptOrder*,float){releases++;if(onRelease)onRelease();}
|
||||
};
|
||||
struct Replication{inline static bool server=true;static bool IsServer(){return server;}};
|
||||
struct ERR_ForceRules{static bool Finite(float n){return std::isfinite(n);}static bool FiniteVector(vector v){return Finite(v.x)&&Finite(v.z);}};
|
||||
struct ERR_HQMemoryRules{static bool DeliveryFresh(float observed,float now){return std::isfinite(observed)&&std::isfinite(now)&&observed>=0&&now>=observed&&now-observed<=120000;}};
|
||||
struct ERR_HQPursuitRules{static int Limit(int n){return std::min(n,3);}};
|
||||
struct ERR_HQMountedInterceptRules{inline static int selects=0;static bool Eligible(ERR_HQBelief*b,float){return b&&b->eligible;}
|
||||
static bool Current(ERR_ConvoyInterceptOrder*o,ERR_HQBelief*b,float now){return Eligible(b,now)&&o->BeliefId==b->BeliefId&&o->BeliefRevision==b->BeliefRevision&&now>=o->IssuedAt&&now<o->ExpiresAt;}
|
||||
static bool Select(ERR_HQBelief*b,float,Array<vector>&reserved,vector&dest){selects++;dest=b->destination;return b->select&&!reserved.Contains(dest);}};
|
||||
struct Clock{Clock*GetWorld(){return this;}float GetWorldTime(){return 3000;}}clock_;Clock*GetGame(){return &clock_;}
|
||||
struct Base{virtual string Status(){return "base HQ status";}int assigned=0,hunting=0,releaseUnits=0,stops=0;Array<vector>destinations;virtual int Assigned(){return assigned;}virtual int HuntingAssigned(){return hunting;}
|
||||
virtual void ReserveUnit(ERR_HQUnit*,Array<vector>&r,int&,int&){for(auto p:destinations)r.Insert(p);}virtual void ReleaseUnit(ERR_HQUnit*,float){releaseUnits++;}virtual void Stop(){stops++;}};
|
||||
struct HQ:Base{
|
||||
ERR_ConvoyInterceptOrder*m_MountedIntercept=nullptr;ERR_HQUnit*m_MountedInterceptUnit=nullptr;ERR_ConvoyTask*m_MountedInterceptTask=nullptr;ERR_HQRoute*m_MountedInterceptRoute=nullptr;bool m_MountedInterceptDelivered=false,m_Stopping=false,active=true;
|
||||
int m_MountedSurveyCursor=0;
|
||||
Array<int>m_MountedAttemptBeliefs,m_MountedAttemptRevisions;Array<ERR_HQUnit*>m_Units;Watch*m_Watch;Mode*m_Mode;Service*m_Convoys;Memory*m_Memory;Journal*m_Journal;Network*m_Network;
|
||||
bool MissionActive(){return active;}bool MountedLogisticsEligible(ERR_HQUnit*u){return u&&u->eligible&&!u->LogisticsReservation;}bool ConvoyContact(ERR_HQUnit*u,float){return u->contact;}
|
||||
'''+methods+r'''
|
||||
};
|
||||
struct Fixture{Group group,otherGroup;Entity entity,radio;Sampler sampler;Ammo ammo;ERR_HQWatchEntry entry;ERR_HQUnit unit;CombatState combat;Receipt receipt;ERR_ConvoyVehicle vehicle,unused;ERR_ConvoyTask task;ERR_HQBelief belief;ERR_HQRoute route;Watch watch;Mode mode;Service service;Memory memory;Journal journal;Network network;HQ h;
|
||||
Fixture(){entry.MountedAmmo=&ammo;unit.Group=&group;unit.Radio=&radio;unit.StatusSampler=&sampler;vehicle.Group=&group;vehicle.Entity=&entity;vehicle.Combat=&combat;unused.Combat=&combat;unused.Released=true;task.Receipt=&receipt;task.Vehicles={&vehicle,&unused,&unused,&unused,&unused,&unused};service.tasks={&task};memory.entries={&belief};watch.entry=&entry;network.route=&route;h.m_Units={&unit};h.m_Watch=&watch;h.m_Mode=&mode;h.m_Convoys=&service;h.m_Memory=&memory;h.m_Journal=&journal;h.m_Network=&network;}
|
||||
void plan(){h.PlanMountedIntercept(3000);}void queued(){plan();assert(h.m_MountedIntercept&&unit.LogisticsReservation==400001);assert(service.reserves==1&&service.applies==0&&route.calls==0);}
|
||||
};
|
||||
int main(){
|
||||
{Fixture f;assert(f.h.Status()=="base HQ status");f.queued();string queued=f.h.Status();assert(queued.find("base HQ status")!=string::npos&&queued.find("|0|queued|20|100,0")!=string::npos);f.route.result=1;f.h.TickMountedIntercept(4000);assert(f.h.Status().find("|0|delivered|20|100,0")!=string::npos);f.h.Stop();assert(f.h.Status()=="base HQ status");}
|
||||
{Fixture f;f.queued();assert(f.h.Assigned()==1&&f.h.HuntingAssigned()==1);Array<vector>r;int a=-1,b=-1;f.h.ReserveUnit(&f.unit,r,a,b);assert(r.Count()==1&&r[0]==f.belief.destination);f.h.PlanMountedIntercept(4000);assert(f.service.reserves==1);f.h.TickMountedIntercept(4000);assert(f.route.calls==1&&f.service.applies==0);f.route.result=1;f.h.TickMountedIntercept(5000);assert(f.service.applies==1&&f.h.m_MountedInterceptDelivered);f.belief.BeliefRevision++;f.h.TickMountedIntercept(6000);assert(f.h.m_MountedInterceptDelivered&&f.service.releases==0);f.h.TickMountedIntercept(121000);assert(!f.h.m_MountedIntercept&&f.unit.LogisticsReservation==0);}
|
||||
// Task-wide logistics and queued sibling downlinks cannot consume a revision or starve a free farther convoy.
|
||||
for(int kind=0;kind<5;kind++){Fixture f,far;far.unit.ForceSourceId=far.entry.SourceId="crew2";far.receipt.SourceId="convoy2";far.entry.Position={-200,0};f.watch.second=&far.entry;f.service.tasks.Insert(&far.task);f.h.m_Units.Insert(&far.unit);ERR_HQUnit sibling; sibling.Group=&f.otherGroup;f.unused.Group=&f.otherGroup;f.h.m_Units.Insert(&sibling);if(kind==0)f.unused.SupplyToken=123;if(kind==1)f.unused.SupplyHeld=true;if(kind==2)sibling.PendingConvoyOrder=true;if(kind==3)sibling.Downlink=true;if(kind==4)sibling.LogisticsReservation=123;f.plan();assert(f.h.m_MountedIntercept&&f.h.m_MountedInterceptUnit==&far.unit&&!f.unit.LogisticsReservation&&far.unit.LogisticsReservation==400001&&f.service.reserves==1);}
|
||||
// A disconnected nearest escort cannot starve the farther reachable original escort.
|
||||
{Fixture f;Group group2;Entity radio2,entity2;ERR_HQUnit u2=f.unit;u2.Group=&group2;u2.Radio=&radio2;u2.ForceSourceId="crew2";ERR_HQWatchEntry entry2=f.entry;entry2.SourceId="crew2";entry2.Position={-200,0};f.watch.second=&entry2;ERR_ConvoyVehicle vehicle2=f.vehicle;vehicle2.Group=&group2;vehicle2.Entity=&entity2;vehicle2.Index=3;f.task.Vehicles[3]=&vehicle2;f.h.m_Units.Insert(&u2);f.network.denied=&f.radio;f.plan();assert(f.h.m_MountedIntercept&&f.h.m_MountedInterceptUnit==&u2&&f.h.m_MountedIntercept->VehicleIndex==3&&f.network.calls==2&&!f.unit.LogisticsReservation&&u2.LogisticsReservation==400001);}
|
||||
// Invalid world clock must preserve the exact claim for the next finite tick.
|
||||
{Fixture f;f.queued();f.h.TickMountedIntercept(std::nanf(""));assert(f.h.m_MountedIntercept&&f.unit.LogisticsReservation==400001&&f.service.releases==0&&f.route.calls==0);}
|
||||
// No own vehicle/fresh crew evidence means no native road surveys.
|
||||
for(int kind=0;kind<6;kind++){Fixture f;if(kind==0)f.service.tasks.clear();if(kind==1)f.task.Stopped=true;if(kind==2)f.unit.LogisticsReservation=1;if(kind==3)f.entry.Closed=true;if(kind==4)f.sampler.bound=false;if(kind==5)f.combat.Held=true;int before=ERR_HQMountedInterceptRules::selects;f.plan();assert(ERR_HQMountedInterceptRules::selects==before&&f.service.reserves==0);}
|
||||
// Interrupted pending claims never apply physical orders, and the same delivered revision cannot retry.
|
||||
{Fixture f;f.queued();f.route.result=-1;f.h.TickMountedIntercept(4000);assert(!f.h.m_MountedIntercept&&!f.unit.LogisticsReservation&&f.service.applies==0);f.plan();assert(f.service.reserves==1);f.belief.BeliefRevision++;f.plan();assert(f.service.reserves==2&&f.unit.LogisticsReservation==400002);}
|
||||
{Fixture f;f.queued();f.belief.BeliefRevision++;f.route.result=1;f.h.TickMountedIntercept(4000);assert(f.route.calls==0&&f.service.applies==0&&!f.h.m_MountedIntercept);}
|
||||
// Physical validation changes after radio delivery cannot trigger a stale order.
|
||||
for(int kind=0;kind<7;kind++){Fixture f;f.queued();f.route.result=1;if(kind==0)f.ammo.Complete=false;if(kind==1)f.ammo.valid=false;if(kind==2)f.sampler.bound=false;if(kind==3)f.entry.Fit--;if(kind==4)f.entry.LastDeliveredAt=9000;if(kind==5)f.unit.contact=true;if(kind==6)f.service.apply=false;f.h.TickMountedIntercept(4000);assert(!f.h.m_MountedIntercept&&!f.unit.LogisticsReservation);assert(f.service.applies==(kind==6?1:0));}
|
||||
// Conditional release and recursive unregister preserve unrelated reservations.
|
||||
{Fixture f;f.queued();f.unit.LogisticsReservation=123;f.service.onRelease=[&]{assert(!f.h.m_MountedIntercept);f.h.ReleaseUnit(&f.unit,4000);};f.h.TickMountedIntercept(4000);assert(f.unit.LogisticsReservation==123&&f.service.releases==1&&f.h.releaseUnits==1);}
|
||||
{Fixture f;f.queued();f.h.Stop();assert(!f.h.m_MountedIntercept&&!f.unit.LogisticsReservation&&f.service.releases==1&&f.h.stops==1&&f.service.applies==0);}
|
||||
// Ordinary assigned and pursuit capacities reserve space before any road query or radio action.
|
||||
for(int kind=0;kind<3;kind++){Fixture f;if(kind==0)f.mode.settings.cap=0;if(kind==1)f.h.assigned=3;if(kind==2)f.h.hunting=3;int before=ERR_HQMountedInterceptRules::selects;f.plan();assert(!f.h.m_MountedIntercept&&f.service.reserves==0&&ERR_HQMountedInterceptRules::selects==before);}
|
||||
{Fixture f;f.queued();f.h.assigned=3;f.h.TickMountedIntercept(4000);assert(!f.h.m_MountedIntercept&&f.service.applies==0);}
|
||||
// Admission is based on detached delivered position, not physical entity telemetry; stock/crew/roles guard it.
|
||||
for(int kind=0;kind<14;kind++){Fixture f;if(kind==0)f.vehicle.Index=1;if(kind==1)f.vehicle.Index=2;if(kind==2)f.vehicle.Arrived=true;if(kind==3)f.vehicle.SupplyToken=1;if(kind==4)f.combat.Held=true;if(kind==5)f.unit.LogisticsReservation=300001;if(kind==6)f.sampler.roster=false;if(kind==7)f.entry.Position={2000,0};if(kind==8)f.entry.ObservedAt=4000;if(kind==9)f.entry.LastDeliveredAt=0;if(kind==10)f.ammo.Weapons=0;if(kind==11)f.ammo.Rounds=0;if(kind==12)f.entry.Count=2;if(kind==13)f.entry.Closed=true;f.plan();assert(!f.h.m_MountedIntercept&&f.service.reserves==0);}
|
||||
{Fixture f;f.service.reserve=false;f.plan();assert(!f.unit.LogisticsReservation&&!f.h.m_MountedIntercept);f.service.reserve=true;f.plan();assert(f.service.reserves==1);}
|
||||
{Fixture f;for(int i=0;i<256;i++){f.h.m_MountedAttemptBeliefs.Insert(i+20);f.h.m_MountedAttemptRevisions.Insert(1);}f.plan();assert(!f.h.m_MountedIntercept&&f.service.reserves==0);}
|
||||
{Fixture f;ERR_HQBelief b2,b3;f.belief.select=b2.select=false;b2.BeliefId=2;b3.BeliefId=3;f.memory.entries={&f.belief,&b2,&b3};int before=ERR_HQMountedInterceptRules::selects;f.plan();assert(ERR_HQMountedInterceptRules::selects-before==2&&f.service.reserves==0);assert(f.h.m_MountedAttemptBeliefs.empty());int after=ERR_HQMountedInterceptRules::selects;f.h.PlanMountedIntercept(8000);assert(ERR_HQMountedInterceptRules::selects-after==1);assert(f.h.m_MountedIntercept&&f.h.m_MountedIntercept->BeliefId==3&&f.service.reserves==1);}
|
||||
// Cursor normalization handles track expiry/removal without out-of-range access.
|
||||
{Fixture f;ERR_HQBelief second;second.BeliefId=2;f.belief.select=second.select=false;f.memory.entries={&f.belief,&second};f.plan();assert(!f.h.m_MountedIntercept);f.h.m_MountedSurveyCursor=31;f.memory.entries={&f.belief};f.belief.select=true;f.h.PlanMountedIntercept(8000);assert(f.h.m_MountedIntercept&&f.h.m_MountedIntercept->BeliefId==1);}
|
||||
// Skipped ineligible/already-attempted tracks do not spend the two-road-query budget.
|
||||
{Fixture f;ERR_HQBelief second,third;second.BeliefId=2;third.BeliefId=3;f.belief.eligible=false;f.h.m_MountedAttemptBeliefs.Insert(2);f.h.m_MountedAttemptRevisions.Insert(second.BeliefRevision);f.memory.entries={&f.belief,&second,&third};int before=ERR_HQMountedInterceptRules::selects;f.plan();assert(ERR_HQMountedInterceptRules::selects-before==1&&f.h.m_MountedIntercept&&f.h.m_MountedIntercept->BeliefId==3);}
|
||||
// No eligible reports and no reports both produce zero native road queries.
|
||||
{Fixture f;f.belief.eligible=false;int before=ERR_HQMountedInterceptRules::selects;f.plan();f.memory.entries.clear();f.plan();assert(ERR_HQMountedInterceptRules::selects==before&&!f.h.m_MountedIntercept&&f.service.reserves==0);}
|
||||
// Full bounded memory: every pass has at most two surveys, no failed revision is consumed.
|
||||
{Fixture f;ERR_HQBelief beliefs[64];f.memory.entries.clear();for(int i=0;i<64;i++){beliefs[i].BeliefId=i+1;beliefs[i].select=false;f.memory.entries.Insert(&beliefs[i]);}for(int pass=0;pass<32;pass++){int before=ERR_HQMountedInterceptRules::selects;float now=3000+pass*5000;f.entry.ObservedAt=f.entry.LastDeliveredAt=now;f.h.PlanMountedIntercept(now);assert(ERR_HQMountedInterceptRules::selects-before==2&&f.h.m_MountedSurveyCursor==((pass+1)*2)%64&&f.h.m_MountedAttemptBeliefs.empty());}assert(!f.h.m_MountedIntercept&&f.service.reserves==0);}
|
||||
|
||||
}
|
||||
''')
|
||||
print('actual mounted HQ dispatch planning, radio, ownership and lifecycle checks passed')
|
||||
@@ -0,0 +1,266 @@
|
||||
from pathlib import Path
|
||||
p=Path('/tmp/erri-pair-hq-0054/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c')
|
||||
s=p.read_text()
|
||||
s=s.replace('// One physical escort may intercept a road ahead of delivered travel history.','// One or two original escorts may intercept a road ahead of delivered travel history.')
|
||||
s='''// Detached planning choice; the route remains strongly owned before admission.
|
||||
class ERR_HQMountedChoice
|
||||
{
|
||||
ERR_HQUnit Unit;
|
||||
ERR_ConvoyTask Task;
|
||||
ERR_ConvoyVehicle Vehicle;
|
||||
ref ERR_HQRoute Route;
|
||||
vector Destination;
|
||||
float Distance;
|
||||
}
|
||||
|
||||
'''+s
|
||||
s=s.replace(' protected int m_MountedSurveyCursor;', ''' protected ref ERR_ConvoyInterceptOrder m_MountedInterceptPartner;
|
||||
protected ERR_HQUnit m_MountedInterceptPartnerUnit;
|
||||
protected ref ERR_HQRoute m_MountedInterceptPartnerRoute;
|
||||
protected bool m_MountedInterceptReceipt;
|
||||
protected bool m_MountedInterceptPartnerReceipt;
|
||||
protected int m_MountedTokenCount;
|
||||
protected int m_MountedSurveyCursor;''')
|
||||
s=s.replace(' return result + "\\n" + string.Format(', ' if (m_MountedInterceptPartner) result += "\\nPaired mounted interception (two original escorts)";\n return result + "\\n" + string.Format(',1)
|
||||
s=s.replace(' if (m_MountedIntercept) count++;',' if (m_MountedIntercept) count++;\n if (m_MountedInterceptPartner) count++;')
|
||||
s=s.replace(' if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);',' if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);\n if (m_MountedInterceptPartner && unit == m_MountedInterceptPartnerUnit) reserved.Insert(m_MountedInterceptPartner.Position);')
|
||||
def method(sig,new):
|
||||
global s
|
||||
a=s.index(sig);b=s.index('{',a);depth=1;i=b+1
|
||||
while depth:
|
||||
depth+=(s[i]=='{')-(s[i]=='}');i+=1
|
||||
s=s[:a]+new+s[i:]
|
||||
method(' protected void ReleaseMountedIntercept(',''' protected void ReleaseMountedIntercept(float now, string reason)
|
||||
{
|
||||
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
|
||||
ref ERR_ConvoyInterceptOrder partner = m_MountedInterceptPartner;
|
||||
ERR_HQUnit unit = m_MountedInterceptUnit;
|
||||
ERR_HQUnit partnerUnit = m_MountedInterceptPartnerUnit;
|
||||
ERR_ConvoyTask task = m_MountedInterceptTask;
|
||||
// Detach BOTH first: physical release may synchronously unregister a group.
|
||||
m_MountedIntercept = null; m_MountedInterceptUnit = null; m_MountedInterceptTask = null;
|
||||
m_MountedInterceptPartner = null; m_MountedInterceptPartnerUnit = null;
|
||||
m_MountedInterceptRoute = null; m_MountedInterceptPartnerRoute = null;
|
||||
m_MountedInterceptDelivered = false; m_MountedInterceptReceipt = false; m_MountedInterceptPartnerReceipt = false;
|
||||
if (!order) return;
|
||||
if (m_Convoys) m_Convoys.ReleaseIntercept(task, order, now);
|
||||
if (partner && m_Convoys) m_Convoys.ReleaseIntercept(task, partner, now);
|
||||
if (unit && unit.LogisticsReservation == order.Token) unit.LogisticsReservation = 0;
|
||||
if (partner && partnerUnit && partnerUnit.LogisticsReservation == partner.Token) partnerUnit.LogisticsReservation = 0;
|
||||
m_Journal.Write("MOUNTED INTERCEPT END", reason, order.ReportId);
|
||||
}''')
|
||||
s=s.replace('if (m_MountedIntercept && unit == m_MountedInterceptUnit) ReleaseMountedIntercept', 'if (m_MountedIntercept && (unit == m_MountedInterceptUnit || unit == m_MountedInterceptPartnerUnit)) ReleaseMountedIntercept')
|
||||
method(' protected override void TickMountedIntercept(',''' protected bool MountedMemberCurrent(ERR_HQUnit unit, ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
if (!order || !unit || !m_Units.Contains(unit) || unit.Group != order.Group) return false;
|
||||
if (unit.LogisticsReservation != order.Token || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)) return false;
|
||||
return m_Convoys && m_Convoys.InterceptCurrent(m_MountedInterceptTask, order);
|
||||
}
|
||||
protected bool MountedMemberReady(ERR_HQUnit unit, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!MountedMemberCurrent(unit, order)) return false;
|
||||
ERR_ConvoyVehicle vehicle = m_MountedInterceptTask.Vehicles[order.VehicleIndex];
|
||||
return MountedInterceptStatus(unit, vehicle, now) && !ConvoyContact(unit, now);
|
||||
}
|
||||
protected override void TickMountedIntercept(float now)
|
||||
{
|
||||
if (!m_MountedIntercept || !ERR_ForceRules.Finite(now)) return;
|
||||
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
|
||||
ref ERR_ConvoyInterceptOrder partner = m_MountedInterceptPartner;
|
||||
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
|
||||
if (now < order.IssuedAt || now >= order.ExpiresAt || !MissionActive() || m_Stopping
|
||||
|| Assigned() > globalLimit || HuntingAssigned() > ERR_HQPursuitRules.Limit(globalLimit)
|
||||
|| !MountedMemberCurrent(m_MountedInterceptUnit, order))
|
||||
{ ReleaseMountedIntercept(now, "expiry, capacity or original ownership changed"); return; }
|
||||
if (partner && !MountedMemberCurrent(m_MountedInterceptPartnerUnit, partner))
|
||||
{ ReleaseMountedIntercept(now, "partner ownership changed"); return; }
|
||||
if (m_MountedInterceptDelivered) return;
|
||||
ERR_HQBelief belief = m_Memory.GetById(order.BeliefId);
|
||||
if (!ERR_HQMountedInterceptRules.Current(order, belief, now))
|
||||
{ ReleaseMountedIntercept(now, "delivered history changed before command arrival"); return; }
|
||||
if (partner && !ERR_HQMountedInterceptRules.Current(partner, belief, now))
|
||||
{ ReleaseMountedIntercept(now, "partner command expired"); return; }
|
||||
if (!m_MountedInterceptReceipt)
|
||||
{
|
||||
if (!m_MountedInterceptRoute) { ReleaseMountedIntercept(now, "missing radio route"); return; }
|
||||
int delivered = m_MountedInterceptRoute.Advance(now, m_Journal);
|
||||
if (m_MountedIntercept != order) return;
|
||||
if (delivered < 0) { ReleaseMountedIntercept(now, "radio interrupted; authored route retained"); return; }
|
||||
if (delivered > 0) { m_MountedInterceptReceipt = true; m_MountedInterceptRoute = null; }
|
||||
}
|
||||
if (partner && !m_MountedInterceptPartnerReceipt)
|
||||
{
|
||||
if (!m_MountedInterceptPartnerRoute) { ReleaseMountedIntercept(now, "missing partner route"); return; }
|
||||
int partnerDelivered = m_MountedInterceptPartnerRoute.Advance(now, m_Journal);
|
||||
if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
if (partnerDelivered < 0) { ReleaseMountedIntercept(now, "partner radio interrupted"); return; }
|
||||
if (partnerDelivered > 0) { m_MountedInterceptPartnerReceipt = true; m_MountedInterceptPartnerRoute = null; }
|
||||
}
|
||||
if (!m_MountedInterceptReceipt || (partner && !m_MountedInterceptPartnerReceipt)) return;
|
||||
if (!MountedMemberReady(m_MountedInterceptUnit, order, now))
|
||||
{ ReleaseMountedIntercept(now, "command refused by original crew"); return; }
|
||||
if (partner && !MountedMemberReady(m_MountedInterceptPartnerUnit, partner, now))
|
||||
{ ReleaseMountedIntercept(now, "command refused by original partner"); return; }
|
||||
bool applied;
|
||||
if (partner) applied = m_Convoys.ApplyInterceptPair(m_MountedInterceptTask, order, partner, now);
|
||||
else applied = m_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now);
|
||||
if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
if (!applied) { ReleaseMountedIntercept(now, "physical command refused"); return; }
|
||||
m_MountedInterceptDelivered = true;
|
||||
m_Journal.Write("MOUNTED INTERCEPT DELIVERED", "original armed escorts ordered to remembered road positions", order.ReportId);
|
||||
}''')
|
||||
# extract reusable per-task candidate selection and standalone queue.
|
||||
a=s.index(' protected override void PlanMountedIntercept(')
|
||||
b=s.index(' override void Stop()',a)
|
||||
s=s[:a]+''' protected ERR_HQMountedChoice MountedChoice(ERR_ConvoyTask task, vector destination, ERR_HQUnit excluded, float now)
|
||||
{
|
||||
if (!MountedInterceptTaskAvailable(task)) return null;
|
||||
ref ERR_HQMountedChoice best;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (unit == excluded || unit.Group != vehicle.Group || !MountedLogisticsEligible(unit)
|
||||
|| ConvoyContact(unit, now) || !MountedInterceptStatus(unit, vehicle, now)) continue;
|
||||
ERR_HQWatchEntry own = m_Watch.Find(unit.ForceSourceId);
|
||||
float distance = vector.DistanceXZ(own.Position, destination);
|
||||
if (distance > m_Mode.Settings().Runtime("responseRadiusMetres") || (best && distance >= best.Distance)) continue;
|
||||
ref ERR_HQRoute route = m_Network.Route(unit.Radio, true);
|
||||
if (!route) continue;
|
||||
best = new ERR_HQMountedChoice();
|
||||
best.Unit = unit; best.Task = task; best.Vehicle = vehicle; best.Route = route;
|
||||
best.Destination = destination; best.Distance = distance;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
protected bool HasMountedPairCandidate(float now)
|
||||
{
|
||||
// Cheap original/status gate; no geometry or claims before both roles exist.
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
ERR_HQUnit firstUnit;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now)
|
||||
|| !MountedInterceptStatus(unit, vehicle, now)) continue;
|
||||
if (firstUnit && firstUnit != unit && firstUnit.Group != unit.Group) return true;
|
||||
firstUnit = unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected ERR_ConvoyInterceptOrder MountedOrder(ERR_HQMountedChoice choice, ERR_HQBelief belief, float now, int token)
|
||||
{
|
||||
ERR_ConvoyInterceptOrder order = new ERR_ConvoyInterceptOrder();
|
||||
order.Token = token; order.Group = choice.Unit.Group; order.SourceId = choice.Task.Receipt.SourceId;
|
||||
order.VehicleIndex = choice.Vehicle.Index; order.Position = choice.Destination;
|
||||
order.BeliefId = belief.BeliefId; order.BeliefRevision = belief.BeliefRevision;
|
||||
order.ReportId = belief.MessageId; order.ObservedAt = belief.ObservedAt;
|
||||
order.IssuedAt = now; order.ExpiresAt = belief.ObservedAt + 120000;
|
||||
return order;
|
||||
}
|
||||
protected void QueueMountedIntercept(ERR_HQMountedChoice choice, ERR_HQMountedChoice partnerChoice, ERR_HQBelief belief, float now)
|
||||
{
|
||||
int members = 1;
|
||||
if (partnerChoice) members = 2;
|
||||
if (m_MountedTokenCount + members > 256) return;
|
||||
ref ERR_ConvoyInterceptOrder order = MountedOrder(choice, belief, now, 400001 + m_MountedTokenCount);
|
||||
ref ERR_ConvoyInterceptOrder partner;
|
||||
if (partnerChoice) partner = MountedOrder(partnerChoice, belief, now, 400002 + m_MountedTokenCount);
|
||||
if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) return;
|
||||
if (partner && !ERR_HQMountedInterceptRules.Current(partner, belief, now)) return;
|
||||
// IDs and this revision remain spent after refusal; no post-claim fallback.
|
||||
m_MountedTokenCount += members;
|
||||
m_MountedAttemptBeliefs.Insert(order.BeliefId); m_MountedAttemptRevisions.Insert(order.BeliefRevision);
|
||||
choice.Unit.LogisticsReservation = order.Token;
|
||||
if (partnerChoice) partnerChoice.Unit.LogisticsReservation = partner.Token;
|
||||
bool admitted;
|
||||
if (partner) admitted = m_Convoys.ReserveInterceptPair(choice.Task, order, partner, now);
|
||||
else admitted = m_Convoys.ReserveIntercept(choice.Task, order, now);
|
||||
if (!admitted)
|
||||
{
|
||||
m_Convoys.ReleaseIntercept(choice.Task, order, now);
|
||||
if (partner) m_Convoys.ReleaseIntercept(choice.Task, partner, now);
|
||||
if (choice.Unit.LogisticsReservation == order.Token) choice.Unit.LogisticsReservation = 0;
|
||||
if (partnerChoice && partnerChoice.Unit.LogisticsReservation == partner.Token) partnerChoice.Unit.LogisticsReservation = 0;
|
||||
return;
|
||||
}
|
||||
m_MountedIntercept = order; m_MountedInterceptUnit = choice.Unit; m_MountedInterceptTask = choice.Task;
|
||||
m_MountedInterceptRoute = choice.Route; m_MountedInterceptDelivered = false; m_MountedInterceptReceipt = false;
|
||||
m_MountedInterceptPartner = partner; m_MountedInterceptPartnerReceipt = false;
|
||||
if (partnerChoice) { m_MountedInterceptPartnerUnit = partnerChoice.Unit; m_MountedInterceptPartnerRoute = partnerChoice.Route; }
|
||||
choice.Route.MessageId = m_Journal.Message();
|
||||
choice.Route.Payload = string.Format("original escort %1 to road %2; delivered track %3 revision %4 report M%5", order.VehicleIndex, order.Position, order.BeliefId, order.BeliefRevision, order.ReportId);
|
||||
m_Journal.Write("MOUNTED INTERCEPT QUEUED", choice.Route.Payload, choice.Route.MessageId);
|
||||
if (partnerChoice)
|
||||
{
|
||||
partnerChoice.Route.MessageId = m_Journal.Message();
|
||||
partnerChoice.Route.Payload = string.Format("original partner %1 to road %2; report M%3", partner.VehicleIndex, partner.Position, partner.ReportId);
|
||||
m_Journal.Write("MOUNTED INTERCEPT QUEUED", partnerChoice.Route.Payload, partnerChoice.Route.MessageId);
|
||||
}
|
||||
}
|
||||
protected override void PlanMountedIntercept(float now)
|
||||
{
|
||||
if (!Replication.IsServer() || m_Stopping || !MissionActive() || m_MountedIntercept
|
||||
|| m_MountedAttemptBeliefs.Count() >= 256 || m_MountedTokenCount >= 256) return;
|
||||
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
|
||||
int freeGlobal = globalLimit - Assigned();
|
||||
int freeHunt = ERR_HQPursuitRules.Limit(globalLimit) - HuntingAssigned();
|
||||
if (freeGlobal < 1 || freeHunt < 1 || !HasMountedInterceptCandidate(now)) return;
|
||||
bool pairPossible = freeGlobal >= 2 && freeHunt >= 2 && m_MountedTokenCount <= 254 && HasMountedPairCandidate(now);
|
||||
array<vector> reserved = {}; int first = -1; int second = -1;
|
||||
foreach (ERR_HQUnit known : m_Units) ReserveUnit(known, reserved, first, second);
|
||||
int count = m_Memory.Count();
|
||||
if (count == 0) return;
|
||||
int start = m_MountedSurveyCursor % count;
|
||||
int queries;
|
||||
for (int scanned = 0; scanned < count && queries < 2; scanned++)
|
||||
{
|
||||
int b = (start + scanned) % count;
|
||||
m_MountedSurveyCursor = (b + 1) % count;
|
||||
ERR_HQBelief belief = m_Memory.At(b);
|
||||
if (!ERR_HQMountedInterceptRules.Eligible(belief, now) || MountedInterceptAttempted(belief.BeliefId, belief.BeliefRevision)) continue;
|
||||
if (pairPossible)
|
||||
{
|
||||
vector positionA; vector positionB; queries++;
|
||||
if (ERR_HQMountedInterceptRules.SelectPair(belief, now, reserved, positionA, positionB))
|
||||
{
|
||||
ref ERR_HQMountedChoice bestA; ref ERR_HQMountedChoice bestB;
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
ref ERR_HQMountedChoice candidateA = MountedChoice(task, positionA, null, now);
|
||||
if (!candidateA) continue;
|
||||
ref ERR_HQMountedChoice candidateB = MountedChoice(task, positionB, candidateA.Unit, now);
|
||||
if (!candidateB || candidateA.Unit.Group == candidateB.Unit.Group || candidateA.Route == candidateB.Route) continue;
|
||||
if (bestA && candidateA.Distance + candidateB.Distance >= bestA.Distance + bestB.Distance) continue;
|
||||
bestA = candidateA; bestB = candidateB;
|
||||
}
|
||||
if (bestA) { QueueMountedIntercept(bestA, bestB, belief, now); return; }
|
||||
}
|
||||
}
|
||||
// Only pre-admission fallback, sharing the SAME two-survey pass budget.
|
||||
if (queries >= 2) continue;
|
||||
vector destination; queries++;
|
||||
if (!ERR_HQMountedInterceptRules.Select(belief, now, reserved, destination)) continue;
|
||||
ref ERR_HQMountedChoice best;
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ref ERR_HQMountedChoice candidate = MountedChoice(m_Convoys.At(t), destination, null, now);
|
||||
if (candidate && (!best || candidate.Distance < best.Distance)) best = candidate;
|
||||
}
|
||||
if (best) { QueueMountedIntercept(best, null, belief, now); return; }
|
||||
}
|
||||
}
|
||||
'''+s[b:]
|
||||
p.write_text(s)
|
||||
@@ -0,0 +1,502 @@
|
||||
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c
|
||||
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c
|
||||
@@ -1,4 +1,15 @@
|
||||
-// One physical escort may intercept a road ahead of delivered travel history.
|
||||
+// Detached planning choice; the route remains strongly owned before admission.
|
||||
+class ERR_HQMountedChoice
|
||||
+{
|
||||
+ ERR_HQUnit Unit;
|
||||
+ ERR_ConvoyTask Task;
|
||||
+ ERR_ConvoyVehicle Vehicle;
|
||||
+ ref ERR_HQRoute Route;
|
||||
+ vector Destination;
|
||||
+ float Distance;
|
||||
+}
|
||||
+
|
||||
+// One or two original escorts may intercept a road ahead of delivered travel history.
|
||||
// This HQ-owned transport uses the ordinary radio graph and never reads players.
|
||||
modded class ERR_HQ
|
||||
{
|
||||
@@ -7,6 +18,12 @@
|
||||
protected ERR_ConvoyTask m_MountedInterceptTask;
|
||||
protected ref ERR_HQRoute m_MountedInterceptRoute;
|
||||
protected bool m_MountedInterceptDelivered;
|
||||
+ protected ref ERR_ConvoyInterceptOrder m_MountedInterceptPartner;
|
||||
+ protected ERR_HQUnit m_MountedInterceptPartnerUnit;
|
||||
+ protected ref ERR_HQRoute m_MountedInterceptPartnerRoute;
|
||||
+ protected bool m_MountedInterceptReceipt;
|
||||
+ protected bool m_MountedInterceptPartnerReceipt;
|
||||
+ protected int m_MountedTokenCount;
|
||||
protected int m_MountedSurveyCursor;
|
||||
protected ref array<int> m_MountedAttemptBeliefs = {};
|
||||
protected ref array<int> m_MountedAttemptRevisions = {};
|
||||
@@ -17,6 +34,7 @@
|
||||
if (!m_MountedIntercept) return result;
|
||||
string phase = "queued";
|
||||
if (m_MountedInterceptDelivered) phase = "delivered";
|
||||
+ if (m_MountedInterceptPartner) result += "\nPaired mounted interception (two original escorts)";
|
||||
return result + "\n" + string.Format("Mounted interception: escort %1; %2; report M%3; road %4", m_MountedIntercept.VehicleIndex, phase, m_MountedIntercept.ReportId, m_MountedIntercept.Position);
|
||||
}
|
||||
|
||||
@@ -24,18 +42,21 @@
|
||||
{
|
||||
int count = super.Assigned();
|
||||
if (m_MountedIntercept) count++;
|
||||
+ if (m_MountedInterceptPartner) count++;
|
||||
return count;
|
||||
}
|
||||
protected override int HuntingAssigned()
|
||||
{
|
||||
int count = super.HuntingAssigned();
|
||||
if (m_MountedIntercept) count++;
|
||||
+ if (m_MountedInterceptPartner) count++;
|
||||
return count;
|
||||
}
|
||||
protected override void ReserveUnit(ERR_HQUnit unit, array<vector> reserved, out int first, out int second)
|
||||
{
|
||||
super.ReserveUnit(unit, reserved, first, second);
|
||||
if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);
|
||||
+ if (m_MountedInterceptPartner && unit == m_MountedInterceptPartnerUnit) reserved.Insert(m_MountedInterceptPartner.Position);
|
||||
}
|
||||
protected bool MountedInterceptAttempted(int beliefId, int revision)
|
||||
{
|
||||
@@ -58,47 +79,85 @@
|
||||
protected void ReleaseMountedIntercept(float now, string reason)
|
||||
{
|
||||
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
|
||||
+ ref ERR_ConvoyInterceptOrder partner = m_MountedInterceptPartner;
|
||||
ERR_HQUnit unit = m_MountedInterceptUnit;
|
||||
+ ERR_HQUnit partnerUnit = m_MountedInterceptPartnerUnit;
|
||||
ERR_ConvoyTask task = m_MountedInterceptTask;
|
||||
- // Detach first: physical release may synchronously unregister the group.
|
||||
+ // Detach BOTH first: physical release may synchronously unregister a group.
|
||||
m_MountedIntercept = null; m_MountedInterceptUnit = null; m_MountedInterceptTask = null;
|
||||
- m_MountedInterceptRoute = null; m_MountedInterceptDelivered = false;
|
||||
+ m_MountedInterceptPartner = null; m_MountedInterceptPartnerUnit = null;
|
||||
+ m_MountedInterceptRoute = null; m_MountedInterceptPartnerRoute = null;
|
||||
+ m_MountedInterceptDelivered = false; m_MountedInterceptReceipt = false; m_MountedInterceptPartnerReceipt = false;
|
||||
if (!order) return;
|
||||
if (m_Convoys) m_Convoys.ReleaseIntercept(task, order, now);
|
||||
+ if (partner && m_Convoys) m_Convoys.ReleaseIntercept(task, partner, now);
|
||||
if (unit && unit.LogisticsReservation == order.Token) unit.LogisticsReservation = 0;
|
||||
+ if (partner && partnerUnit && partnerUnit.LogisticsReservation == partner.Token) partnerUnit.LogisticsReservation = 0;
|
||||
m_Journal.Write("MOUNTED INTERCEPT END", reason, order.ReportId);
|
||||
}
|
||||
protected override void ReleaseUnit(ERR_HQUnit unit, float now)
|
||||
{
|
||||
- if (m_MountedIntercept && unit == m_MountedInterceptUnit) ReleaseMountedIntercept(now, "original formation released");
|
||||
+ if (m_MountedIntercept && (unit == m_MountedInterceptUnit || unit == m_MountedInterceptPartnerUnit)) ReleaseMountedIntercept(now, "original formation released");
|
||||
super.ReleaseUnit(unit, now);
|
||||
}
|
||||
+ protected bool MountedMemberCurrent(ERR_HQUnit unit, ERR_ConvoyInterceptOrder order)
|
||||
+ {
|
||||
+ if (!order || !unit || !m_Units.Contains(unit) || unit.Group != order.Group) return false;
|
||||
+ if (unit.LogisticsReservation != order.Token || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)) return false;
|
||||
+ return m_Convoys && m_Convoys.InterceptCurrent(m_MountedInterceptTask, order);
|
||||
+ }
|
||||
+ protected bool MountedMemberReady(ERR_HQUnit unit, ERR_ConvoyInterceptOrder order, float now)
|
||||
+ {
|
||||
+ if (!MountedMemberCurrent(unit, order)) return false;
|
||||
+ ERR_ConvoyVehicle vehicle = m_MountedInterceptTask.Vehicles[order.VehicleIndex];
|
||||
+ return MountedInterceptStatus(unit, vehicle, now) && !ConvoyContact(unit, now);
|
||||
+ }
|
||||
protected override void TickMountedIntercept(float now)
|
||||
{
|
||||
if (!m_MountedIntercept || !ERR_ForceRules.Finite(now)) return;
|
||||
- ERR_HQUnit unit = m_MountedInterceptUnit;
|
||||
- ERR_ConvoyInterceptOrder order = m_MountedIntercept;
|
||||
+ ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
|
||||
+ ref ERR_ConvoyInterceptOrder partner = m_MountedInterceptPartner;
|
||||
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
|
||||
- if (now < order.IssuedAt || now >= order.ExpiresAt
|
||||
- || !MissionActive() || m_Stopping || !unit || !m_Units.Contains(unit) || unit.Group != order.Group
|
||||
- || unit.LogisticsReservation != order.Token || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)
|
||||
+ if (now < order.IssuedAt || now >= order.ExpiresAt || !MissionActive() || m_Stopping
|
||||
|| Assigned() > globalLimit || HuntingAssigned() > ERR_HQPursuitRules.Limit(globalLimit)
|
||||
- || !m_Convoys.InterceptCurrent(m_MountedInterceptTask, order))
|
||||
+ || !MountedMemberCurrent(m_MountedInterceptUnit, order))
|
||||
{ ReleaseMountedIntercept(now, "expiry, capacity or original ownership changed"); return; }
|
||||
+ if (partner && !MountedMemberCurrent(m_MountedInterceptPartnerUnit, partner))
|
||||
+ { ReleaseMountedIntercept(now, "partner ownership changed"); return; }
|
||||
if (m_MountedInterceptDelivered) return;
|
||||
ERR_HQBelief belief = m_Memory.GetById(order.BeliefId);
|
||||
- if (!ERR_HQMountedInterceptRules.Current(order, belief, now) || !m_MountedInterceptRoute)
|
||||
+ if (!ERR_HQMountedInterceptRules.Current(order, belief, now))
|
||||
{ ReleaseMountedIntercept(now, "delivered history changed before command arrival"); return; }
|
||||
- int delivered = m_MountedInterceptRoute.Advance(now, m_Journal);
|
||||
- if (delivered == 0) return;
|
||||
- if (delivered < 0)
|
||||
- { ReleaseMountedIntercept(now, "radio interrupted; authored route retained"); return; }
|
||||
- ERR_ConvoyVehicle vehicle = m_MountedInterceptTask.Vehicles[order.VehicleIndex];
|
||||
- if (!MountedInterceptStatus(unit, vehicle, now) || ConvoyContact(unit, now)
|
||||
- || !m_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now))
|
||||
+ if (partner && !ERR_HQMountedInterceptRules.Current(partner, belief, now))
|
||||
+ { ReleaseMountedIntercept(now, "partner command expired"); return; }
|
||||
+ if (!m_MountedInterceptReceipt)
|
||||
+ {
|
||||
+ if (!m_MountedInterceptRoute) { ReleaseMountedIntercept(now, "missing radio route"); return; }
|
||||
+ int delivered = m_MountedInterceptRoute.Advance(now, m_Journal);
|
||||
+ if (m_MountedIntercept != order) return;
|
||||
+ if (delivered < 0) { ReleaseMountedIntercept(now, "radio interrupted; authored route retained"); return; }
|
||||
+ if (delivered > 0) { m_MountedInterceptReceipt = true; }
|
||||
+ }
|
||||
+ if (partner && !m_MountedInterceptPartnerReceipt)
|
||||
+ {
|
||||
+ if (!m_MountedInterceptPartnerRoute) { ReleaseMountedIntercept(now, "missing partner route"); return; }
|
||||
+ int partnerDelivered = m_MountedInterceptPartnerRoute.Advance(now, m_Journal);
|
||||
+ if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
+ if (partnerDelivered < 0) { ReleaseMountedIntercept(now, "partner radio interrupted"); return; }
|
||||
+ if (partnerDelivered > 0) { m_MountedInterceptPartnerReceipt = true; }
|
||||
+ }
|
||||
+ if (!m_MountedInterceptReceipt || (partner && !m_MountedInterceptPartnerReceipt)) return;
|
||||
+ if (!MountedMemberReady(m_MountedInterceptUnit, order, now))
|
||||
{ ReleaseMountedIntercept(now, "command refused by original crew"); return; }
|
||||
- m_MountedInterceptRoute = null; m_MountedInterceptDelivered = true;
|
||||
- m_Journal.Write("MOUNTED INTERCEPT DELIVERED", "original armed escort ordered to the remembered road ahead", order.ReportId);
|
||||
+ if (partner && !MountedMemberReady(m_MountedInterceptPartnerUnit, partner, now))
|
||||
+ { ReleaseMountedIntercept(now, "command refused by original partner"); return; }
|
||||
+ bool applied;
|
||||
+ if (partner) applied = m_Convoys.ApplyInterceptPair(m_MountedInterceptTask, order, partner, now);
|
||||
+ else applied = m_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now);
|
||||
+ if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
+ if (!applied) { ReleaseMountedIntercept(now, "physical command refused"); return; }
|
||||
+ m_MountedInterceptDelivered = true;
|
||||
+ m_Journal.Write("MOUNTED INTERCEPT DELIVERED", "original armed escorts ordered to remembered road positions", order.ReportId);
|
||||
}
|
||||
protected bool MountedInterceptTaskAvailable(ERR_ConvoyTask task)
|
||||
{
|
||||
@@ -132,17 +191,109 @@
|
||||
}
|
||||
return false;
|
||||
}
|
||||
+ protected ERR_HQMountedChoice MountedChoice(ERR_ConvoyTask task, vector destination, ERR_HQUnit excluded, float now, ERR_ConvoyVehicle required = null)
|
||||
+ {
|
||||
+ if (!MountedInterceptTaskAvailable(task)) return null;
|
||||
+ ref ERR_HQMountedChoice best;
|
||||
+ foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
+ {
|
||||
+ if (required && vehicle != required) continue;
|
||||
+ if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
+ || vehicle.Released || vehicle.Arrived || !vehicle.Registered) continue;
|
||||
+ foreach (ERR_HQUnit unit : m_Units)
|
||||
+ {
|
||||
+ if (unit == excluded || unit.Group != vehicle.Group || !MountedLogisticsEligible(unit)
|
||||
+ || ConvoyContact(unit, now) || !MountedInterceptStatus(unit, vehicle, now)) continue;
|
||||
+ ERR_HQWatchEntry own = m_Watch.Find(unit.ForceSourceId);
|
||||
+ float distance = vector.DistanceXZ(own.Position, destination);
|
||||
+ if (distance > m_Mode.Settings().Runtime("responseRadiusMetres") || (best && distance >= best.Distance)) continue;
|
||||
+ ref ERR_HQRoute route = m_Network.Route(unit.Radio, true);
|
||||
+ if (!route) continue;
|
||||
+ best = new ERR_HQMountedChoice();
|
||||
+ best.Unit = unit; best.Task = task; best.Vehicle = vehicle; best.Route = route;
|
||||
+ best.Destination = destination; best.Distance = distance;
|
||||
+ }
|
||||
+ }
|
||||
+ return best;
|
||||
+ }
|
||||
+ protected bool HasMountedPairCandidate(float now)
|
||||
+ {
|
||||
+ // Cheap original/status gate; no geometry or claims before both roles exist.
|
||||
+ for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
+ {
|
||||
+ ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
+ if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
+ ERR_HQUnit firstUnit;
|
||||
+ foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
+ {
|
||||
+ if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
+ || vehicle.Released || vehicle.Arrived || !vehicle.Registered) continue;
|
||||
+ foreach (ERR_HQUnit unit : m_Units)
|
||||
+ {
|
||||
+ if (unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now)
|
||||
+ || !MountedInterceptStatus(unit, vehicle, now)) continue;
|
||||
+ if (firstUnit && firstUnit != unit && firstUnit.Group != unit.Group) return true;
|
||||
+ firstUnit = unit;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ return false;
|
||||
+ }
|
||||
+ protected ERR_ConvoyInterceptOrder MountedOrder(ERR_HQMountedChoice choice, ERR_HQBelief belief, float now, int token)
|
||||
+ {
|
||||
+ ERR_ConvoyInterceptOrder order = new ERR_ConvoyInterceptOrder();
|
||||
+ order.Token = token; order.Group = choice.Unit.Group; order.SourceId = choice.Task.Receipt.SourceId;
|
||||
+ order.VehicleIndex = choice.Vehicle.Index; order.Position = choice.Destination;
|
||||
+ order.BeliefId = belief.BeliefId; order.BeliefRevision = belief.BeliefRevision;
|
||||
+ order.ReportId = belief.MessageId; order.ObservedAt = belief.ObservedAt;
|
||||
+ order.IssuedAt = now; order.ExpiresAt = belief.ObservedAt + 120000;
|
||||
+ return order;
|
||||
+ }
|
||||
+ protected void QueueMountedIntercept(ERR_HQMountedChoice choice, ERR_HQMountedChoice partnerChoice, ERR_HQBelief belief, float now)
|
||||
+ {
|
||||
+ int members = 1;
|
||||
+ if (partnerChoice) members = 2;
|
||||
+ if (m_MountedTokenCount + members > 256) return;
|
||||
+ ref ERR_ConvoyInterceptOrder order = MountedOrder(choice, belief, now, 400001 + m_MountedTokenCount);
|
||||
+ ref ERR_ConvoyInterceptOrder partner;
|
||||
+ if (partnerChoice) partner = MountedOrder(partnerChoice, belief, now, 400002 + m_MountedTokenCount);
|
||||
+ if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) return;
|
||||
+ if (partner && !ERR_HQMountedInterceptRules.Current(partner, belief, now)) return;
|
||||
+ // IDs and this revision remain spent after refusal; no post-claim fallback.
|
||||
+ m_MountedTokenCount += members;
|
||||
+ m_MountedAttemptBeliefs.Insert(order.BeliefId); m_MountedAttemptRevisions.Insert(order.BeliefRevision);
|
||||
+ choice.Unit.LogisticsReservation = order.Token;
|
||||
+ if (partnerChoice) partnerChoice.Unit.LogisticsReservation = partner.Token;
|
||||
+ m_MountedIntercept = order; m_MountedInterceptUnit = choice.Unit; m_MountedInterceptTask = choice.Task;
|
||||
+ m_MountedInterceptRoute = choice.Route; m_MountedInterceptDelivered = false; m_MountedInterceptReceipt = false;
|
||||
+ m_MountedInterceptPartner = partner; m_MountedInterceptPartnerReceipt = false;
|
||||
+ if (partnerChoice) { m_MountedInterceptPartnerUnit = partnerChoice.Unit; m_MountedInterceptPartnerRoute = partnerChoice.Route; }
|
||||
+ bool admitted;
|
||||
+ if (partner) admitted = m_Convoys.ReserveInterceptPair(choice.Task, order, partner, now);
|
||||
+ else admitted = m_Convoys.ReserveIntercept(choice.Task, order, now);
|
||||
+ if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
+ if (!admitted) { ReleaseMountedIntercept(now, "reservation refused"); return; }
|
||||
+ choice.Route.MessageId = m_Journal.Message();
|
||||
+ choice.Route.Payload = string.Format("original escort %1 to road %2; delivered track %3 revision %4 report M%5", order.VehicleIndex, order.Position, order.BeliefId, order.BeliefRevision, order.ReportId);
|
||||
+ m_Journal.Write("MOUNTED INTERCEPT QUEUED", choice.Route.Payload, choice.Route.MessageId);
|
||||
+ if (partnerChoice)
|
||||
+ {
|
||||
+ partnerChoice.Route.MessageId = m_Journal.Message();
|
||||
+ partnerChoice.Route.Payload = string.Format("original partner %1 to road %2; report M%3", partner.VehicleIndex, partner.Position, partner.ReportId);
|
||||
+ m_Journal.Write("MOUNTED INTERCEPT QUEUED", partnerChoice.Route.Payload, partnerChoice.Route.MessageId);
|
||||
+ }
|
||||
+ }
|
||||
protected override void PlanMountedIntercept(float now)
|
||||
{
|
||||
- if (!Replication.IsServer() || m_Stopping || !MissionActive() || m_MountedIntercept || m_MountedAttemptBeliefs.Count() >= 256) return;
|
||||
+ if (!Replication.IsServer() || m_Stopping || !MissionActive() || m_MountedIntercept
|
||||
+ || m_MountedAttemptBeliefs.Count() >= 256 || m_MountedTokenCount >= 256) return;
|
||||
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
|
||||
- if (Assigned() >= globalLimit || HuntingAssigned() >= ERR_HQPursuitRules.Limit(globalLimit)) return;
|
||||
- if (!HasMountedInterceptCandidate(now)) return;
|
||||
+ int freeGlobal = globalLimit - Assigned();
|
||||
+ int freeHunt = ERR_HQPursuitRules.Limit(globalLimit) - HuntingAssigned();
|
||||
+ if (freeGlobal < 1 || freeHunt < 1 || !HasMountedInterceptCandidate(now)) return;
|
||||
+ bool pairPossible = freeGlobal >= 2 && freeHunt >= 2 && m_MountedTokenCount <= 254 && HasMountedPairCandidate(now);
|
||||
array<vector> reserved = {}; int first = -1; int second = -1;
|
||||
foreach (ERR_HQUnit known : m_Units) ReserveUnit(known, reserved, first, second);
|
||||
- // At most two bounded native road queries per normal five-second HQ pass.
|
||||
- // Continue the next pass after the last surveyed track, so unsuitable roads
|
||||
- // for older reports cannot indefinitely hide later delivered tracks.
|
||||
int count = m_Memory.Count();
|
||||
if (count == 0) return;
|
||||
int start = m_MountedSurveyCursor % count;
|
||||
@@ -153,56 +304,43 @@
|
||||
m_MountedSurveyCursor = (b + 1) % count;
|
||||
ERR_HQBelief belief = m_Memory.At(b);
|
||||
if (!ERR_HQMountedInterceptRules.Eligible(belief, now) || MountedInterceptAttempted(belief.BeliefId, belief.BeliefRevision)) continue;
|
||||
+ if (pairPossible)
|
||||
+ {
|
||||
+ vector positionA; vector positionB; queries++;
|
||||
+ if (ERR_HQMountedInterceptRules.SelectPair(belief, now, reserved, positionA, positionB))
|
||||
+ {
|
||||
+ ref ERR_HQMountedChoice bestA; ref ERR_HQMountedChoice bestB;
|
||||
+ for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
+ {
|
||||
+ ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
+ if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
+ // Enumerate the bounded original escorts: a greedy nearest first crew
|
||||
+ // can be the only crew within range of the other road position.
|
||||
+ foreach (ERR_ConvoyVehicle firstVehicle : task.Vehicles)
|
||||
+ {
|
||||
+ if (!firstVehicle || (firstVehicle.Index != 0 && firstVehicle.Index != 3 && firstVehicle.Index != 5)) continue;
|
||||
+ ref ERR_HQMountedChoice candidateA = MountedChoice(task, positionA, null, now, firstVehicle);
|
||||
+ if (!candidateA) continue;
|
||||
+ ref ERR_HQMountedChoice candidateB = MountedChoice(task, positionB, candidateA.Unit, now);
|
||||
+ if (!candidateB || candidateA.Unit.Group == candidateB.Unit.Group || candidateA.Route == candidateB.Route) continue;
|
||||
+ if (bestA && candidateA.Distance + candidateB.Distance >= bestA.Distance + bestB.Distance) continue;
|
||||
+ bestA = candidateA; bestB = candidateB;
|
||||
+ }
|
||||
+ }
|
||||
+ if (bestA) { QueueMountedIntercept(bestA, bestB, belief, now); return; }
|
||||
+ }
|
||||
+ }
|
||||
+ // Only pre-admission fallback, sharing the SAME two-survey pass budget.
|
||||
+ if (queries >= 2) continue;
|
||||
vector destination; queries++;
|
||||
if (!ERR_HQMountedInterceptRules.Select(belief, now, reserved, destination)) continue;
|
||||
- ERR_HQUnit best;
|
||||
- ERR_ConvoyTask bestTask;
|
||||
- ERR_ConvoyVehicle bestVehicle;
|
||||
- ERR_HQRoute bestRoute;
|
||||
- float bestDistance = 1e20;
|
||||
+ ref ERR_HQMountedChoice best;
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
- ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
- if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
- foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
- {
|
||||
- if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
- || vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|
||||
- || vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
|
||||
- foreach (ERR_HQUnit unit : m_Units)
|
||||
- {
|
||||
- if (unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now)
|
||||
- || !MountedInterceptStatus(unit, vehicle, now)) continue;
|
||||
- ERR_HQWatchEntry own = m_Watch.Find(unit.ForceSourceId);
|
||||
- float distance = vector.DistanceXZ(own.Position, destination);
|
||||
- if (distance > m_Mode.Settings().Runtime("responseRadiusMetres") || distance >= bestDistance) continue;
|
||||
- ERR_HQRoute candidateRoute = m_Network.Route(unit.Radio, true);
|
||||
- if (!candidateRoute) continue;
|
||||
- best = unit; bestTask = task; bestVehicle = vehicle; bestRoute = candidateRoute; bestDistance = distance;
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
- if (!best) continue;
|
||||
- ERR_HQRoute route = bestRoute;
|
||||
- if (!route) continue;
|
||||
- ERR_ConvoyInterceptOrder order = new ERR_ConvoyInterceptOrder();
|
||||
- order.Token = 400001 + m_MountedAttemptBeliefs.Count(); order.Group = best.Group;
|
||||
- order.SourceId = bestTask.Receipt.SourceId; order.VehicleIndex = bestVehicle.Index;
|
||||
- order.Position = destination; order.BeliefId = belief.BeliefId; order.BeliefRevision = belief.BeliefRevision;
|
||||
- order.ReportId = belief.MessageId; order.ObservedAt = belief.ObservedAt;
|
||||
- order.IssuedAt = now; order.ExpiresAt = belief.ObservedAt + 120000;
|
||||
- if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) continue;
|
||||
- // The attempt stays spent after radio interruption, physical refusal or loss.
|
||||
- m_MountedAttemptBeliefs.Insert(order.BeliefId); m_MountedAttemptRevisions.Insert(order.BeliefRevision);
|
||||
- best.LogisticsReservation = order.Token;
|
||||
- if (!m_Convoys.ReserveIntercept(bestTask, order, now))
|
||||
- { if (best.LogisticsReservation == order.Token) best.LogisticsReservation = 0; return; }
|
||||
- m_MountedIntercept = order; m_MountedInterceptUnit = best; m_MountedInterceptTask = bestTask;
|
||||
- m_MountedInterceptRoute = route; m_MountedInterceptDelivered = false;
|
||||
- route.MessageId = m_Journal.Message();
|
||||
- route.Payload = string.Format("original escort %1 to road %2; delivered track %3 revision %4 report M%5", order.VehicleIndex, destination, order.BeliefId, order.BeliefRevision, order.ReportId);
|
||||
- m_Journal.Write("MOUNTED INTERCEPT QUEUED", route.Payload, route.MessageId);
|
||||
- return;
|
||||
+ ref ERR_HQMountedChoice candidate = MountedChoice(m_Convoys.At(t), destination, null, now);
|
||||
+ if (candidate && (!best || candidate.Distance < best.Distance)) best = candidate;
|
||||
+ }
|
||||
+ if (best) { QueueMountedIntercept(best, null, belief, now); return; }
|
||||
}
|
||||
}
|
||||
override void Stop()
|
||||
--- a/tests/test_hq_mounted_dispatch.py
|
||||
+++ b/tests/test_hq_mounted_dispatch.py
|
||||
@@ -3,24 +3,25 @@
|
||||
from pathlib import Path
|
||||
import os,re
|
||||
P=Path(os.environ.get('ERR_HQ_MOUNTED_DISPATCH_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c'))
|
||||
-names=['override string Status(', 'protected override int Assigned(', 'protected override int HuntingAssigned(', 'protected override void ReserveUnit(', 'protected bool MountedInterceptAttempted(', 'protected bool MountedInterceptStatus(', 'protected void ReleaseMountedIntercept(', 'protected override void ReleaseUnit(', 'protected override void TickMountedIntercept(', 'protected bool MountedInterceptTaskAvailable(', 'protected bool HasMountedInterceptCandidate(', 'protected override void PlanMountedIntercept(', 'override void Stop(']
|
||||
+names=['protected bool MountedMemberCurrent(', 'protected bool MountedMemberReady(', 'protected ERR_HQMountedChoice MountedChoice(', 'protected bool HasMountedPairCandidate(', 'protected ERR_ConvoyInterceptOrder MountedOrder(', 'protected void QueueMountedIntercept(', 'override string Status(', 'protected override int Assigned(', 'protected override int HuntingAssigned(', 'protected override void ReserveUnit(', 'protected bool MountedInterceptAttempted(', 'protected bool MountedInterceptStatus(', 'protected void ReleaseMountedIntercept(', 'protected override void ReleaseUnit(', 'protected override void TickMountedIntercept(', 'protected bool MountedInterceptTaskAvailable(', 'protected bool HasMountedInterceptCandidate(', 'protected override void PlanMountedIntercept(', 'override void Stop(']
|
||||
def cpp(s):
|
||||
s=s.replace('protected ','').replace('override ','').replace('ref ','').replace('out int ','int& ').replace('super.','Base::').replace('null','nullptr').replace('foreach (','for (')
|
||||
- for typ in ['ERR_ConvoyInterceptOrder','ERR_HQUnit','ERR_ConvoyTask','ERR_HQRoute','ERR_ConvoyVehicle','ERR_HQWatchEntry','ERR_HQBelief']:
|
||||
+ for typ in ['ERR_ConvoyInterceptOrder','ERR_HQUnit','ERR_ConvoyTask','ERR_HQRoute','ERR_ConvoyVehicle','ERR_HQWatchEntry','ERR_HQBelief','ERR_HQMountedChoice']:
|
||||
s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
|
||||
s=s.replace('array<vector> reserved','Array<vector> reserved')
|
||||
for typ in ['Replication','ERR_ForceRules','ERR_HQMemoryRules','ERR_HQPursuitRules','ERR_HQMountedInterceptRules','vector','string']:
|
||||
s=s.replace(typ+'.',typ+'::')
|
||||
- for var in ['unit','vehicle','entry','order','task','belief','known','best','bestTask','bestVehicle','route','candidateRoute','own','m_Mode','m_Watch','m_Memory','m_Convoys','m_Journal','m_Network','m_MountedIntercept','m_MountedInterceptTask','m_MountedInterceptRoute']:
|
||||
+ for var in ['unit','vehicle','entry','order','task','belief','known','best','bestTask','bestVehicle','route','candidateRoute','own','m_Mode','m_Watch','m_Memory','m_Convoys','m_Journal','m_Network','m_MountedIntercept','m_MountedInterceptTask','m_MountedInterceptRoute','required','firstVehicle','partner','partnerUnit','firstUnit','excluded','choice','partnerChoice','bestA','bestB','candidateA','candidateB','candidate','m_MountedInterceptPartner','m_MountedInterceptPartnerUnit','m_MountedInterceptPartnerRoute']:
|
||||
s=re.sub(r'\b'+var+r'\.',var+'->',s)
|
||||
- for name in ['StatusSampler','MountedAmmo','Combat','Receipt']:
|
||||
+ for name in ['Unit','Task','Vehicle','Route','StatusSampler','MountedAmmo','Combat','Receipt']:
|
||||
s=s.replace('->'+name+'.','->'+name+'->')
|
||||
s=s.replace('GetGame().','GetGame()->').replace('GetWorld().','GetWorld()->').replace('Settings().','Settings()->')
|
||||
s=re.sub(r'(ERR_\w+\*) (\w+);',r'\1 \2 = nullptr;',s)
|
||||
+ s=s.replace('bool applied;', 'bool applied=false;').replace('bool admitted;', 'bool admitted=false;')
|
||||
s=s.replace('int queries;','int queries=0;').replace('float now;','float now=0;')
|
||||
s=s.replace('Array<vector> reserved,','Array<vector>& reserved,')
|
||||
return s
|
||||
-methods='\n'.join(cpp(body(P,n)) for n in names if n not in ('protected bool HasMountedInterceptCandidate(', 'protected bool MountedInterceptTaskAvailable(') or n in P.read_text())
|
||||
+methods='\n'.join(cpp(body(P,n)) for n in names if n in P.read_text())
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
@@ -47,10 +48,12 @@
|
||||
struct ERR_HQRoute{int result=0,calls=0,MessageId=0;string Payload;int Advance(float,Journal*){calls++;return result;}};
|
||||
struct Watch{ERR_HQWatchEntry*entry=nullptr,*second=nullptr;int SquadLimit(int n){return n;}ERR_HQWatchEntry*Find(string id){return entry&&entry->SourceId==id?entry:(second&&second->SourceId==id?second:nullptr);}};
|
||||
struct Memory{Array<ERR_HQBelief*>entries;int Count(){return entries.Count();}ERR_HQBelief*At(int n){return entries[n];}ERR_HQBelief*GetById(int n){for(auto b:entries)if(b->BeliefId==n)return b;return nullptr;}};
|
||||
-struct Network{ERR_HQRoute*route=nullptr;Entity*denied=nullptr;int calls=0;ERR_HQRoute*Route(Entity*radio,bool){calls++;return radio==denied?nullptr:route;}};
|
||||
+struct Network{std::map<Entity*,ERR_HQRoute*>routes;ERR_HQRoute*route=nullptr;Entity*denied=nullptr;int calls=0;ERR_HQRoute*Route(Entity*radio,bool){calls++;return radio==denied?nullptr:(routes.count(radio)?routes[radio]:route);}};
|
||||
struct Settings{int cap=3,radius=1000;int Runtime(string s){return s=="supportSquadLimit"?cap:radius;}};
|
||||
struct Mode{struct Settings settings;struct Settings*Settings(){return &settings;}};
|
||||
-struct Service{Array<ERR_ConvoyTask*>tasks;bool current=true,reserve=true,apply=true;int reserves=0,applies=0,releases=0;std::function<void()>onRelease;
|
||||
+struct Service{Array<ERR_ConvoyTask*>tasks;bool current=true,reserve=true,apply=true;int pairReserves=0,pairApplies=0;std::function<void()>onReserve,onApply;int reserves=0,applies=0,releases=0;std::function<void()>onRelease;
|
||||
+ bool ReserveInterceptPair(ERR_ConvoyTask*,ERR_ConvoyInterceptOrder*a,ERR_ConvoyInterceptOrder*b,float){pairReserves++;assert(a->Token!=b->Token&&a->Group!=b->Group);if(onReserve)onReserve();return reserve;}
|
||||
+ bool ApplyInterceptPair(ERR_ConvoyTask*,ERR_ConvoyInterceptOrder*,ERR_ConvoyInterceptOrder*,float){pairApplies++;if(onApply)onApply();return apply;}
|
||||
int Count(){return tasks.Count();}ERR_ConvoyTask*At(int n){return tasks[n];}
|
||||
bool InterceptCurrent(ERR_ConvoyTask*,ERR_ConvoyInterceptOrder*){return current;}
|
||||
bool ReserveIntercept(ERR_ConvoyTask*,ERR_ConvoyInterceptOrder*,float){reserves++;return reserve;}
|
||||
@@ -61,14 +64,18 @@
|
||||
struct ERR_ForceRules{static bool Finite(float n){return std::isfinite(n);}static bool FiniteVector(vector v){return Finite(v.x)&&Finite(v.z);}};
|
||||
struct ERR_HQMemoryRules{static bool DeliveryFresh(float observed,float now){return std::isfinite(observed)&&std::isfinite(now)&&observed>=0&&now>=observed&&now-observed<=120000;}};
|
||||
struct ERR_HQPursuitRules{static int Limit(int n){return std::min(n,3);}};
|
||||
-struct ERR_HQMountedInterceptRules{inline static int selects=0;static bool Eligible(ERR_HQBelief*b,float){return b&&b->eligible;}
|
||||
- static bool Current(ERR_ConvoyInterceptOrder*o,ERR_HQBelief*b,float now){return Eligible(b,now)&&o->BeliefId==b->BeliefId&&o->BeliefRevision==b->BeliefRevision&&now>=o->IssuedAt&&now<o->ExpiresAt;}
|
||||
+struct ERR_HQMountedInterceptRules{inline static int selects=0,pairSelects=0;inline static bool pairGeometry=true;static bool Eligible(ERR_HQBelief*b,float){return b&&b->eligible;}
|
||||
+ static bool Current(ERR_ConvoyInterceptOrder*o,ERR_HQBelief*b,float now){return Eligible(b,now)&&o->BeliefId==b->BeliefId&&o->BeliefRevision==b->BeliefRevision&&now>=o->IssuedAt&&now-o->IssuedAt<=30000&&now<o->ExpiresAt;}
|
||||
+ static bool SelectPair(ERR_HQBelief*b,float,Array<vector>&,vector&a,vector&c){pairSelects++;a=b->destination;c={a.x+60,a.z};return b->select&&pairGeometry;}
|
||||
static bool Select(ERR_HQBelief*b,float,Array<vector>&reserved,vector&dest){selects++;dest=b->destination;return b->select&&!reserved.Contains(dest);}};
|
||||
struct Clock{Clock*GetWorld(){return this;}float GetWorldTime(){return 3000;}}clock_;Clock*GetGame(){return &clock_;}
|
||||
struct Base{virtual string Status(){return "base HQ status";}int assigned=0,hunting=0,releaseUnits=0,stops=0;Array<vector>destinations;virtual int Assigned(){return assigned;}virtual int HuntingAssigned(){return hunting;}
|
||||
virtual void ReserveUnit(ERR_HQUnit*,Array<vector>&r,int&,int&){for(auto p:destinations)r.Insert(p);}virtual void ReleaseUnit(ERR_HQUnit*,float){releaseUnits++;}virtual void Stop(){stops++;}};
|
||||
+struct ERR_HQMountedChoice{ERR_HQUnit*Unit=nullptr;ERR_ConvoyTask*Task=nullptr;ERR_ConvoyVehicle*Vehicle=nullptr;ERR_HQRoute*Route=nullptr;vector Destination;float Distance=0;};
|
||||
struct HQ:Base{
|
||||
ERR_ConvoyInterceptOrder*m_MountedIntercept=nullptr;ERR_HQUnit*m_MountedInterceptUnit=nullptr;ERR_ConvoyTask*m_MountedInterceptTask=nullptr;ERR_HQRoute*m_MountedInterceptRoute=nullptr;bool m_MountedInterceptDelivered=false,m_Stopping=false,active=true;
|
||||
+ ERR_ConvoyInterceptOrder*m_MountedInterceptPartner=nullptr;ERR_HQUnit*m_MountedInterceptPartnerUnit=nullptr;ERR_HQRoute*m_MountedInterceptPartnerRoute=nullptr;
|
||||
+ bool m_MountedInterceptReceipt=false,m_MountedInterceptPartnerReceipt=false;int m_MountedTokenCount=0;
|
||||
int m_MountedSurveyCursor=0;
|
||||
Array<int>m_MountedAttemptBeliefs,m_MountedAttemptRevisions;Array<ERR_HQUnit*>m_Units;Watch*m_Watch;Mode*m_Mode;Service*m_Convoys;Memory*m_Memory;Journal*m_Journal;Network*m_Network;
|
||||
bool MissionActive(){return active;}bool MountedLogisticsEligible(ERR_HQUnit*u){return u&&u->eligible&&!u->LogisticsReservation;}bool ConvoyContact(ERR_HQUnit*u,float){return u->contact;}
|
||||
@@ -78,13 +85,17 @@
|
||||
Fixture(){entry.MountedAmmo=&ammo;unit.Group=&group;unit.Radio=&radio;unit.StatusSampler=&sampler;vehicle.Group=&group;vehicle.Entity=&entity;vehicle.Combat=&combat;unused.Combat=&combat;unused.Released=true;task.Receipt=&receipt;task.Vehicles={&vehicle,&unused,&unused,&unused,&unused,&unused};service.tasks={&task};memory.entries={&belief};watch.entry=&entry;network.route=&route;h.m_Units={&unit};h.m_Watch=&watch;h.m_Mode=&mode;h.m_Convoys=&service;h.m_Memory=&memory;h.m_Journal=&journal;h.m_Network=&network;}
|
||||
void plan(){h.PlanMountedIntercept(3000);}void queued(){plan();assert(h.m_MountedIntercept&&unit.LogisticsReservation==400001);assert(service.reserves==1&&service.applies==0&&route.calls==0);}
|
||||
};
|
||||
+struct PairFixture:Fixture{Group group2;Entity entity2,radio2;Sampler sampler2;ERR_HQUnit unit2;ERR_ConvoyVehicle vehicle2;ERR_HQWatchEntry entry2;ERR_HQRoute route2;
|
||||
+ PairFixture(){unit2=unit;unit2.Group=&group2;unit2.Radio=&radio2;unit2.StatusSampler=&sampler2;unit2.ForceSourceId="partner";entry2=entry;entry2.SourceId="partner";entry2.Position={200,0};watch.second=&entry2;vehicle2=vehicle;vehicle2.Index=3;vehicle2.Group=&group2;vehicle2.Entity=&entity2;task.Vehicles[3]=&vehicle2;h.m_Units.Insert(&unit2);network.routes[&radio2]=&route2;ERR_HQMountedInterceptRules::pairGeometry=true;}
|
||||
+ void pair(){plan();assert(h.m_MountedIntercept&&h.m_MountedInterceptPartner);assert(h.m_MountedInterceptTask==&task);assert(unit.LogisticsReservation==400001&&unit2.LogisticsReservation==400002);assert(service.pairReserves==1&&service.reserves==0&&service.pairApplies==0);}
|
||||
+};
|
||||
int main(){
|
||||
{Fixture f;assert(f.h.Status()=="base HQ status");f.queued();string queued=f.h.Status();assert(queued.find("base HQ status")!=string::npos&&queued.find("|0|queued|20|100,0")!=string::npos);f.route.result=1;f.h.TickMountedIntercept(4000);assert(f.h.Status().find("|0|delivered|20|100,0")!=string::npos);f.h.Stop();assert(f.h.Status()=="base HQ status");}
|
||||
{Fixture f;f.queued();assert(f.h.Assigned()==1&&f.h.HuntingAssigned()==1);Array<vector>r;int a=-1,b=-1;f.h.ReserveUnit(&f.unit,r,a,b);assert(r.Count()==1&&r[0]==f.belief.destination);f.h.PlanMountedIntercept(4000);assert(f.service.reserves==1);f.h.TickMountedIntercept(4000);assert(f.route.calls==1&&f.service.applies==0);f.route.result=1;f.h.TickMountedIntercept(5000);assert(f.service.applies==1&&f.h.m_MountedInterceptDelivered);f.belief.BeliefRevision++;f.h.TickMountedIntercept(6000);assert(f.h.m_MountedInterceptDelivered&&f.service.releases==0);f.h.TickMountedIntercept(121000);assert(!f.h.m_MountedIntercept&&f.unit.LogisticsReservation==0);}
|
||||
// Task-wide logistics and queued sibling downlinks cannot consume a revision or starve a free farther convoy.
|
||||
for(int kind=0;kind<5;kind++){Fixture f,far;far.unit.ForceSourceId=far.entry.SourceId="crew2";far.receipt.SourceId="convoy2";far.entry.Position={-200,0};f.watch.second=&far.entry;f.service.tasks.Insert(&far.task);f.h.m_Units.Insert(&far.unit);ERR_HQUnit sibling; sibling.Group=&f.otherGroup;f.unused.Group=&f.otherGroup;f.h.m_Units.Insert(&sibling);if(kind==0)f.unused.SupplyToken=123;if(kind==1)f.unused.SupplyHeld=true;if(kind==2)sibling.PendingConvoyOrder=true;if(kind==3)sibling.Downlink=true;if(kind==4)sibling.LogisticsReservation=123;f.plan();assert(f.h.m_MountedIntercept&&f.h.m_MountedInterceptUnit==&far.unit&&!f.unit.LogisticsReservation&&far.unit.LogisticsReservation==400001&&f.service.reserves==1);}
|
||||
// A disconnected nearest escort cannot starve the farther reachable original escort.
|
||||
- {Fixture f;Group group2;Entity radio2,entity2;ERR_HQUnit u2=f.unit;u2.Group=&group2;u2.Radio=&radio2;u2.ForceSourceId="crew2";ERR_HQWatchEntry entry2=f.entry;entry2.SourceId="crew2";entry2.Position={-200,0};f.watch.second=&entry2;ERR_ConvoyVehicle vehicle2=f.vehicle;vehicle2.Group=&group2;vehicle2.Entity=&entity2;vehicle2.Index=3;f.task.Vehicles[3]=&vehicle2;f.h.m_Units.Insert(&u2);f.network.denied=&f.radio;f.plan();assert(f.h.m_MountedIntercept&&f.h.m_MountedInterceptUnit==&u2&&f.h.m_MountedIntercept->VehicleIndex==3&&f.network.calls==2&&!f.unit.LogisticsReservation&&u2.LogisticsReservation==400001);}
|
||||
+ {Fixture f;Group group2;Entity radio2,entity2;ERR_HQUnit u2=f.unit;u2.Group=&group2;u2.Radio=&radio2;u2.ForceSourceId="crew2";ERR_HQWatchEntry entry2=f.entry;entry2.SourceId="crew2";entry2.Position={-200,0};f.watch.second=&entry2;ERR_ConvoyVehicle vehicle2=f.vehicle;vehicle2.Group=&group2;vehicle2.Entity=&entity2;vehicle2.Index=3;f.task.Vehicles[3]=&vehicle2;f.h.m_Units.Insert(&u2);f.network.denied=&f.radio;f.plan();assert(f.h.m_MountedIntercept&&f.h.m_MountedInterceptUnit==&u2&&f.h.m_MountedIntercept->VehicleIndex==3&&f.network.calls>=2&&!f.unit.LogisticsReservation&&u2.LogisticsReservation==400001);}
|
||||
// Invalid world clock must preserve the exact claim for the next finite tick.
|
||||
{Fixture f;f.queued();f.h.TickMountedIntercept(std::nanf(""));assert(f.h.m_MountedIntercept&&f.unit.LogisticsReservation==400001&&f.service.releases==0&&f.route.calls==0);}
|
||||
// No own vehicle/fresh crew evidence means no native road surveys.
|
||||
@@ -114,6 +125,40 @@
|
||||
// Full bounded memory: every pass has at most two surveys, no failed revision is consumed.
|
||||
{Fixture f;ERR_HQBelief beliefs[64];f.memory.entries.clear();for(int i=0;i<64;i++){beliefs[i].BeliefId=i+1;beliefs[i].select=false;f.memory.entries.Insert(&beliefs[i]);}for(int pass=0;pass<32;pass++){int before=ERR_HQMountedInterceptRules::selects;float now=3000+pass*5000;f.entry.ObservedAt=f.entry.LastDeliveredAt=now;f.h.PlanMountedIntercept(now);assert(ERR_HQMountedInterceptRules::selects-before==2&&f.h.m_MountedSurveyCursor==((pass+1)*2)%64&&f.h.m_MountedAttemptBeliefs.empty());}assert(!f.h.m_MountedIntercept&&f.service.reserves==0);}
|
||||
|
||||
+ // Paired admission counts and reserves both original slots; one receipt never detours either.
|
||||
+ {PairFixture f;f.pair();assert(f.h.Assigned()==2&&f.h.HuntingAssigned()==2);Array<vector>r;int a=-1,b=-1;f.h.ReserveUnit(&f.unit,r,a,b);f.h.ReserveUnit(&f.unit2,r,a,b);assert(r.Count()==2&&vector::DistanceXZ(r[0],r[1])>=40);assert(f.h.Status().find("Paired")!=string::npos);
|
||||
+ f.route.result=1;f.h.TickMountedIntercept(4000);assert(f.h.m_MountedInterceptReceipt&&!f.h.m_MountedInterceptPartnerReceipt&&!f.h.m_MountedInterceptDelivered&&f.service.pairApplies==0&&f.service.applies==0);f.h.TickMountedIntercept(4500);assert(f.route.calls==1&&f.route2.calls==2&&f.h.m_MountedInterceptRoute==&f.route&&f.h.m_MountedInterceptPartnerRoute==&f.route2);
|
||||
+ f.route2.result=1;f.h.TickMountedIntercept(5000);assert(f.service.pairApplies==1&&f.h.m_MountedInterceptDelivered&&f.h.m_MountedIntercept->ExpiresAt==121000&&f.h.m_MountedInterceptPartner->ExpiresAt==121000);f.h.TickMountedIntercept(6000);assert(f.service.pairApplies==1);f.h.TickMountedIntercept(121000);assert(!f.h.m_MountedIntercept&&!f.h.m_MountedInterceptPartner&&!f.unit.LogisticsReservation&&!f.unit2.LogisticsReservation&&f.service.releases==2);}
|
||||
+ // Either receipt may arrive first; interrupted/expired/revised/changed ownership never creates half a pair.
|
||||
+ for(int kind=0;kind<9;kind++){PairFixture f;f.pair();f.route2.result=1;f.h.TickMountedIntercept(4000);assert(f.h.m_MountedInterceptPartnerReceipt&&f.service.pairApplies==0);float now=5000;
|
||||
+ if(kind==0)f.route.result=-1;
|
||||
+ if(kind==1)now=121000;
|
||||
+ if(kind==2)f.belief.BeliefRevision++;
|
||||
+ if(kind==3)f.unit2.LogisticsReservation=999;
|
||||
+ if(kind==4)f.unit2.contact=true;
|
||||
+ if(kind==5)f.entry2.Fit--;
|
||||
+ if(kind==6)f.sampler2.bound=false;
|
||||
+ if(kind==7)f.h.assigned=2;
|
||||
+ if(kind==8)now=34000;
|
||||
+ if(kind!=0)f.route.result=1;
|
||||
+ f.h.TickMountedIntercept(now);assert(!f.h.m_MountedIntercept&&!f.h.m_MountedInterceptPartner&&f.service.pairApplies==0&&f.service.applies==0&&!f.unit.LogisticsReservation);assert(f.unit2.LogisticsReservation==(kind==3?999:0));}
|
||||
+ // Native preparation failure settles BOTH claims. No retry or singleton replacement after commitment.
|
||||
+ {PairFixture f;f.pair();f.route.result=f.route2.result=1;f.service.apply=false;f.h.TickMountedIntercept(5000);assert(f.service.pairApplies==1&&!f.h.m_MountedIntercept&&!f.unit.LogisticsReservation&&!f.unit2.LogisticsReservation);f.plan();assert(f.service.pairReserves==1&&f.service.reserves==0);}
|
||||
+ {PairFixture f;f.service.reserve=false;f.plan();assert(f.service.pairReserves==1&&f.service.reserves==0&&!f.unit.LogisticsReservation&&!f.unit2.LogisticsReservation&&f.h.m_MountedTokenCount==2);f.service.reserve=true;f.plan();assert(f.service.pairReserves==1);f.belief.BeliefRevision++;f.plan();assert(f.h.m_MountedIntercept->Token==400003&&f.h.m_MountedInterceptPartner->Token==400004);}
|
||||
+ // Stop/reentrant unregister detach both owned handles before external release; unrelated claims survive.
|
||||
+ {PairFixture f;f.pair();f.unit2.LogisticsReservation=999;f.service.onRelease=[&]{assert(!f.h.m_MountedIntercept&&!f.h.m_MountedInterceptPartner);f.h.ReleaseUnit(&f.unit2,5000);};f.h.Stop();f.h.Stop();assert(f.service.releases==2&&!f.unit.LogisticsReservation&&f.unit2.LogisticsReservation==999);}
|
||||
+ {PairFixture f;f.pair();f.route.result=f.route2.result=1;f.service.onApply=[&]{f.h.Stop();};f.h.TickMountedIntercept(5000);assert(!f.h.m_MountedIntercept&&!f.h.m_MountedInterceptDelivered&&!f.unit.LogisticsReservation&&!f.unit2.LogisticsReservation);}
|
||||
+ // Two free slots and two IDs are required for pair. Singleton fallback occurs only before claims.
|
||||
+ for(int kind=0;kind<5;kind++){PairFixture f;if(kind==0)f.h.assigned=2;if(kind==1)f.h.hunting=2;if(kind==2)f.h.m_MountedTokenCount=255;if(kind==3)f.network.denied=&f.radio2;if(kind==4)ERR_HQMountedInterceptRules::pairGeometry=false;int before=ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects;f.plan();assert(f.h.m_MountedIntercept&&!f.h.m_MountedInterceptPartner&&f.service.reserves==1&&f.service.pairReserves==0);assert(ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects-before<=2);}
|
||||
+ {PairFixture f;f.h.m_MountedTokenCount=256;int before=ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects;f.plan();assert(!f.h.m_MountedIntercept&&ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects==before);}
|
||||
+ // A valid reversed assignment must win over greedy singleton fallback (same bounded road survey).
|
||||
+ {PairFixture f;f.mode.settings.radius=800;f.belief.destination={0,0};f.entry.Position={0,0};f.entry2.Position={-800,0};int before=ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects;f.plan();assert(f.h.m_MountedInterceptPartner&&f.h.m_MountedInterceptUnit==&f.unit2&&f.h.m_MountedInterceptPartnerUnit==&f.unit);assert(ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects-before==1);}
|
||||
+ // Stop during reservation cannot resurrect a job after external callback returns.
|
||||
+ {PairFixture f;f.service.onReserve=[&]{f.h.Stop();};f.plan();assert(!f.h.m_MountedIntercept&&!f.h.m_MountedInterceptPartner&&!f.unit.LogisticsReservation&&!f.unit2.LogisticsReservation&&f.service.releases==2);}
|
||||
+ // No mixed-convoy pair; both geometric failures still cost at most two queries, no attempts/tokens.
|
||||
+ {PairFixture f;ERR_ConvoyTask other=f.task;other.Vehicles[0]=&f.unused;f.task.Vehicles[3]=&f.unused;f.service.tasks.Insert(&other);f.plan();assert(f.h.m_MountedIntercept&&!f.h.m_MountedInterceptPartner&&f.service.pairReserves==0);}
|
||||
+ {PairFixture f;ERR_HQBelief b2;b2.BeliefId=2;f.belief.select=b2.select=false;f.memory.entries.Insert(&b2);int before=ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects;f.plan();assert(ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects-before==2&&!f.h.m_MountedIntercept&&f.h.m_MountedAttemptBeliefs.empty()&&f.h.m_MountedTokenCount==0);}
|
||||
+
|
||||
}
|
||||
''')
|
||||
print('actual mounted HQ dispatch planning, radio, ownership and lifecycle checks passed')
|
||||
@@ -0,0 +1 @@
|
||||
actual mounted HQ dispatch planning, radio, ownership and lifecycle checks passed
|
||||
+353
@@ -0,0 +1,353 @@
|
||||
// Detached planning choice; the route remains strongly owned before admission.
|
||||
class ERR_HQMountedChoice
|
||||
{
|
||||
ERR_HQUnit Unit;
|
||||
ERR_ConvoyTask Task;
|
||||
ERR_ConvoyVehicle Vehicle;
|
||||
ref ERR_HQRoute Route;
|
||||
vector Destination;
|
||||
float Distance;
|
||||
}
|
||||
|
||||
// One or two original escorts may intercept a road ahead of delivered travel history.
|
||||
// This HQ-owned transport uses the ordinary radio graph and never reads players.
|
||||
modded class ERR_HQ
|
||||
{
|
||||
protected ref ERR_ConvoyInterceptOrder m_MountedIntercept;
|
||||
protected ERR_HQUnit m_MountedInterceptUnit;
|
||||
protected ERR_ConvoyTask m_MountedInterceptTask;
|
||||
protected ref ERR_HQRoute m_MountedInterceptRoute;
|
||||
protected bool m_MountedInterceptDelivered;
|
||||
protected ref ERR_ConvoyInterceptOrder m_MountedInterceptPartner;
|
||||
protected ERR_HQUnit m_MountedInterceptPartnerUnit;
|
||||
protected ref ERR_HQRoute m_MountedInterceptPartnerRoute;
|
||||
protected bool m_MountedInterceptReceipt;
|
||||
protected bool m_MountedInterceptPartnerReceipt;
|
||||
protected int m_MountedTokenCount;
|
||||
protected int m_MountedSurveyCursor;
|
||||
protected ref array<int> m_MountedAttemptBeliefs = {};
|
||||
protected ref array<int> m_MountedAttemptRevisions = {};
|
||||
|
||||
override string Status()
|
||||
{
|
||||
string result = super.Status();
|
||||
if (!m_MountedIntercept) return result;
|
||||
string phase = "queued";
|
||||
if (m_MountedInterceptDelivered) phase = "delivered";
|
||||
if (m_MountedInterceptPartner) result += "\nPaired mounted interception (two original escorts)";
|
||||
return result + "\n" + string.Format("Mounted interception: escort %1; %2; report M%3; road %4", m_MountedIntercept.VehicleIndex, phase, m_MountedIntercept.ReportId, m_MountedIntercept.Position);
|
||||
}
|
||||
|
||||
protected override int Assigned()
|
||||
{
|
||||
int count = super.Assigned();
|
||||
if (m_MountedIntercept) count++;
|
||||
if (m_MountedInterceptPartner) count++;
|
||||
return count;
|
||||
}
|
||||
protected override int HuntingAssigned()
|
||||
{
|
||||
int count = super.HuntingAssigned();
|
||||
if (m_MountedIntercept) count++;
|
||||
if (m_MountedInterceptPartner) count++;
|
||||
return count;
|
||||
}
|
||||
protected override void ReserveUnit(ERR_HQUnit unit, array<vector> reserved, out int first, out int second)
|
||||
{
|
||||
super.ReserveUnit(unit, reserved, first, second);
|
||||
if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);
|
||||
if (m_MountedInterceptPartner && unit == m_MountedInterceptPartnerUnit) reserved.Insert(m_MountedInterceptPartner.Position);
|
||||
}
|
||||
protected bool MountedInterceptAttempted(int beliefId, int revision)
|
||||
{
|
||||
for (int i = 0; i < m_MountedAttemptBeliefs.Count(); i++)
|
||||
if (m_MountedAttemptBeliefs[i] == beliefId && m_MountedAttemptRevisions[i] == revision) return true;
|
||||
return false;
|
||||
}
|
||||
protected bool MountedInterceptStatus(ERR_HQUnit unit, ERR_ConvoyVehicle vehicle, float now)
|
||||
{
|
||||
if (!unit || !vehicle || !unit.StatusSampler || vehicle.Group != unit.Group || !vehicle.Entity
|
||||
|| !unit.StatusSampler.OwnsRoster(unit.Group) || !unit.StatusSampler.ValidateBoundVehicle(vehicle.Entity)) return false;
|
||||
ERR_HQWatchEntry entry = m_Watch.Find(unit.ForceSourceId);
|
||||
if (!entry || entry.Closed || entry.SourceId != unit.ForceSourceId || entry.MessageId <= 0
|
||||
|| !ERR_HQMemoryRules.DeliveryFresh(entry.ObservedAt, now) || !ERR_ForceRules.Finite(entry.LastDeliveredAt)
|
||||
|| entry.LastDeliveredAt < entry.ObservedAt || entry.LastDeliveredAt > now
|
||||
|| !ERR_ForceRules.FiniteVector(entry.Position) || entry.Count < 1 || entry.Count != entry.Strength
|
||||
|| entry.Fit != entry.Count || !entry.MountedAmmo || !entry.MountedAmmo.Complete || !entry.MountedAmmo.Valid()) return false;
|
||||
return entry.MountedAmmo.Weapons > 0 && entry.MountedAmmo.Rounds + entry.MountedAmmo.Chambers > 0;
|
||||
}
|
||||
protected void ReleaseMountedIntercept(float now, string reason)
|
||||
{
|
||||
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
|
||||
ref ERR_ConvoyInterceptOrder partner = m_MountedInterceptPartner;
|
||||
ERR_HQUnit unit = m_MountedInterceptUnit;
|
||||
ERR_HQUnit partnerUnit = m_MountedInterceptPartnerUnit;
|
||||
ERR_ConvoyTask task = m_MountedInterceptTask;
|
||||
// Detach BOTH first: physical release may synchronously unregister a group.
|
||||
m_MountedIntercept = null; m_MountedInterceptUnit = null; m_MountedInterceptTask = null;
|
||||
m_MountedInterceptPartner = null; m_MountedInterceptPartnerUnit = null;
|
||||
m_MountedInterceptRoute = null; m_MountedInterceptPartnerRoute = null;
|
||||
m_MountedInterceptDelivered = false; m_MountedInterceptReceipt = false; m_MountedInterceptPartnerReceipt = false;
|
||||
if (!order) return;
|
||||
if (m_Convoys) m_Convoys.ReleaseIntercept(task, order, now);
|
||||
if (partner && m_Convoys) m_Convoys.ReleaseIntercept(task, partner, now);
|
||||
if (unit && unit.LogisticsReservation == order.Token) unit.LogisticsReservation = 0;
|
||||
if (partner && partnerUnit && partnerUnit.LogisticsReservation == partner.Token) partnerUnit.LogisticsReservation = partner.Token;
|
||||
m_Journal.Write("MOUNTED INTERCEPT END", reason, order.ReportId);
|
||||
}
|
||||
protected override void ReleaseUnit(ERR_HQUnit unit, float now)
|
||||
{
|
||||
if (m_MountedIntercept && (unit == m_MountedInterceptUnit || unit == m_MountedInterceptPartnerUnit)) ReleaseMountedIntercept(now, "original formation released");
|
||||
super.ReleaseUnit(unit, now);
|
||||
}
|
||||
protected bool MountedMemberCurrent(ERR_HQUnit unit, ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
if (!order || !unit || !m_Units.Contains(unit) || unit.Group != order.Group) return false;
|
||||
if (unit.LogisticsReservation != order.Token || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)) return false;
|
||||
return m_Convoys && m_Convoys.InterceptCurrent(m_MountedInterceptTask, order);
|
||||
}
|
||||
protected bool MountedMemberReady(ERR_HQUnit unit, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!MountedMemberCurrent(unit, order)) return false;
|
||||
ERR_ConvoyVehicle vehicle = m_MountedInterceptTask.Vehicles[order.VehicleIndex];
|
||||
return MountedInterceptStatus(unit, vehicle, now) && !ConvoyContact(unit, now);
|
||||
}
|
||||
protected override void TickMountedIntercept(float now)
|
||||
{
|
||||
if (!m_MountedIntercept || !ERR_ForceRules.Finite(now)) return;
|
||||
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
|
||||
ref ERR_ConvoyInterceptOrder partner = m_MountedInterceptPartner;
|
||||
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
|
||||
if (now < order.IssuedAt || now >= order.ExpiresAt || !MissionActive() || m_Stopping
|
||||
|| Assigned() > globalLimit || HuntingAssigned() > ERR_HQPursuitRules.Limit(globalLimit)
|
||||
|| !MountedMemberCurrent(m_MountedInterceptUnit, order))
|
||||
{ ReleaseMountedIntercept(now, "expiry, capacity or original ownership changed"); return; }
|
||||
if (partner && !MountedMemberCurrent(m_MountedInterceptPartnerUnit, partner))
|
||||
{ ReleaseMountedIntercept(now, "partner ownership changed"); return; }
|
||||
if (m_MountedInterceptDelivered) return;
|
||||
ERR_HQBelief belief = m_Memory.GetById(order.BeliefId);
|
||||
if (!ERR_HQMountedInterceptRules.Current(order, belief, now))
|
||||
{ ReleaseMountedIntercept(now, "delivered history changed before command arrival"); return; }
|
||||
if (partner && !ERR_HQMountedInterceptRules.Current(partner, belief, now))
|
||||
{ ReleaseMountedIntercept(now, "partner command expired"); return; }
|
||||
if (!m_MountedInterceptReceipt)
|
||||
{
|
||||
if (!m_MountedInterceptRoute) { ReleaseMountedIntercept(now, "missing radio route"); return; }
|
||||
int delivered = m_MountedInterceptRoute.Advance(now, m_Journal);
|
||||
if (m_MountedIntercept != order) return;
|
||||
if (delivered < 0) { ReleaseMountedIntercept(now, "radio interrupted; authored route retained"); return; }
|
||||
if (delivered > 0) { m_MountedInterceptReceipt = true; }
|
||||
}
|
||||
if (partner && !m_MountedInterceptPartnerReceipt)
|
||||
{
|
||||
if (!m_MountedInterceptPartnerRoute) { ReleaseMountedIntercept(now, "missing partner route"); return; }
|
||||
int partnerDelivered = m_MountedInterceptPartnerRoute.Advance(now, m_Journal);
|
||||
if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
if (partnerDelivered < 0) { ReleaseMountedIntercept(now, "partner radio interrupted"); return; }
|
||||
if (partnerDelivered > 0) { m_MountedInterceptPartnerReceipt = true; }
|
||||
}
|
||||
if (!m_MountedInterceptReceipt || (partner && !m_MountedInterceptPartnerReceipt)) return;
|
||||
if (!MountedMemberReady(m_MountedInterceptUnit, order, now))
|
||||
{ ReleaseMountedIntercept(now, "command refused by original crew"); return; }
|
||||
if (partner && !MountedMemberReady(m_MountedInterceptPartnerUnit, partner, now))
|
||||
{ ReleaseMountedIntercept(now, "command refused by original partner"); return; }
|
||||
bool applied;
|
||||
if (partner) applied = m_Convoys.ApplyInterceptPair(m_MountedInterceptTask, order, partner, now);
|
||||
else applied = m_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now);
|
||||
if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
if (!applied) { ReleaseMountedIntercept(now, "physical command refused"); return; }
|
||||
m_MountedInterceptDelivered = true;
|
||||
m_Journal.Write("MOUNTED INTERCEPT DELIVERED", "original armed escorts ordered to remembered road positions", order.ReportId);
|
||||
}
|
||||
protected bool MountedInterceptTaskAvailable(ERR_ConvoyTask task)
|
||||
{
|
||||
if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) return false;
|
||||
// Service admission excludes the whole original convoy, including sibling
|
||||
// holds still in transit. Do not spend a delivered revision on a busy task.
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || vehicle.InterceptToken > 0 || vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) return false;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
if (unit.Group == vehicle.Group && (unit.PendingConvoyOrder || unit.Downlink || unit.LogisticsReservation > 0)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected bool HasMountedInterceptCandidate(float now)
|
||||
{
|
||||
// Avoid native road surveys until one original escort has usable delivered status.
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|
||||
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
if (unit.Group == vehicle.Group && MountedLogisticsEligible(unit) && !ConvoyContact(unit, now)
|
||||
&& MountedInterceptStatus(unit, vehicle, now)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected ERR_HQMountedChoice MountedChoice(ERR_ConvoyTask task, vector destination, ERR_HQUnit excluded, float now, ERR_ConvoyVehicle required = null)
|
||||
{
|
||||
if (!MountedInterceptTaskAvailable(task)) return null;
|
||||
ref ERR_HQMountedChoice best;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (required && vehicle != required) continue;
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (unit == excluded || unit.Group != vehicle.Group || !MountedLogisticsEligible(unit)
|
||||
|| ConvoyContact(unit, now) || !MountedInterceptStatus(unit, vehicle, now)) continue;
|
||||
ERR_HQWatchEntry own = m_Watch.Find(unit.ForceSourceId);
|
||||
float distance = vector.DistanceXZ(own.Position, destination);
|
||||
if (distance > m_Mode.Settings().Runtime("responseRadiusMetres") || (best && distance >= best.Distance)) continue;
|
||||
ref ERR_HQRoute route = m_Network.Route(unit.Radio, true);
|
||||
if (!route) continue;
|
||||
best = new ERR_HQMountedChoice();
|
||||
best.Unit = unit; best.Task = task; best.Vehicle = vehicle; best.Route = route;
|
||||
best.Destination = destination; best.Distance = distance;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
protected bool HasMountedPairCandidate(float now)
|
||||
{
|
||||
// Cheap original/status gate; no geometry or claims before both roles exist.
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
ERR_HQUnit firstUnit;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now)
|
||||
|| !MountedInterceptStatus(unit, vehicle, now)) continue;
|
||||
if (firstUnit && firstUnit != unit && firstUnit.Group != unit.Group) return true;
|
||||
firstUnit = unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected ERR_ConvoyInterceptOrder MountedOrder(ERR_HQMountedChoice choice, ERR_HQBelief belief, float now, int token)
|
||||
{
|
||||
ERR_ConvoyInterceptOrder order = new ERR_ConvoyInterceptOrder();
|
||||
order.Token = token; order.Group = choice.Unit.Group; order.SourceId = choice.Task.Receipt.SourceId;
|
||||
order.VehicleIndex = choice.Vehicle.Index; order.Position = choice.Destination;
|
||||
order.BeliefId = belief.BeliefId; order.BeliefRevision = belief.BeliefRevision;
|
||||
order.ReportId = belief.MessageId; order.ObservedAt = belief.ObservedAt;
|
||||
order.IssuedAt = now; order.ExpiresAt = belief.ObservedAt + 120000;
|
||||
return order;
|
||||
}
|
||||
protected void QueueMountedIntercept(ERR_HQMountedChoice choice, ERR_HQMountedChoice partnerChoice, ERR_HQBelief belief, float now)
|
||||
{
|
||||
int members = 1;
|
||||
if (partnerChoice) members = 2;
|
||||
if (m_MountedTokenCount + members > 256) return;
|
||||
ref ERR_ConvoyInterceptOrder order = MountedOrder(choice, belief, now, 400001 + m_MountedTokenCount);
|
||||
ref ERR_ConvoyInterceptOrder partner;
|
||||
if (partnerChoice) partner = MountedOrder(partnerChoice, belief, now, 400002 + m_MountedTokenCount);
|
||||
if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) return;
|
||||
if (partner && !ERR_HQMountedInterceptRules.Current(partner, belief, now)) return;
|
||||
// IDs and this revision remain spent after refusal; no post-claim fallback.
|
||||
m_MountedTokenCount += members;
|
||||
m_MountedAttemptBeliefs.Insert(order.BeliefId); m_MountedAttemptRevisions.Insert(order.BeliefRevision);
|
||||
choice.Unit.LogisticsReservation = order.Token;
|
||||
if (partnerChoice) partnerChoice.Unit.LogisticsReservation = partner.Token;
|
||||
m_MountedIntercept = order; m_MountedInterceptUnit = choice.Unit; m_MountedInterceptTask = choice.Task;
|
||||
m_MountedInterceptRoute = choice.Route; m_MountedInterceptDelivered = false; m_MountedInterceptReceipt = false;
|
||||
m_MountedInterceptPartner = partner; m_MountedInterceptPartnerReceipt = false;
|
||||
if (partnerChoice) { m_MountedInterceptPartnerUnit = partnerChoice.Unit; m_MountedInterceptPartnerRoute = partnerChoice.Route; }
|
||||
bool admitted;
|
||||
if (partner) admitted = m_Convoys.ReserveInterceptPair(choice.Task, order, partner, now);
|
||||
else admitted = m_Convoys.ReserveIntercept(choice.Task, order, now);
|
||||
if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
if (!admitted) { ReleaseMountedIntercept(now, "reservation refused"); return; }
|
||||
choice.Route.MessageId = m_Journal.Message();
|
||||
choice.Route.Payload = string.Format("original escort %1 to road %2; delivered track %3 revision %4 report M%5", order.VehicleIndex, order.Position, order.BeliefId, order.BeliefRevision, order.ReportId);
|
||||
m_Journal.Write("MOUNTED INTERCEPT QUEUED", choice.Route.Payload, choice.Route.MessageId);
|
||||
if (partnerChoice)
|
||||
{
|
||||
partnerChoice.Route.MessageId = m_Journal.Message();
|
||||
partnerChoice.Route.Payload = string.Format("original partner %1 to road %2; report M%3", partner.VehicleIndex, partner.Position, partner.ReportId);
|
||||
m_Journal.Write("MOUNTED INTERCEPT QUEUED", partnerChoice.Route.Payload, partnerChoice.Route.MessageId);
|
||||
}
|
||||
}
|
||||
protected override void PlanMountedIntercept(float now)
|
||||
{
|
||||
if (!Replication.IsServer() || m_Stopping || !MissionActive() || m_MountedIntercept
|
||||
|| m_MountedAttemptBeliefs.Count() >= 256 || m_MountedTokenCount >= 256) return;
|
||||
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
|
||||
int freeGlobal = globalLimit - Assigned();
|
||||
int freeHunt = ERR_HQPursuitRules.Limit(globalLimit) - HuntingAssigned();
|
||||
if (freeGlobal < 1 || freeHunt < 1 || !HasMountedInterceptCandidate(now)) return;
|
||||
bool pairPossible = freeGlobal >= 2 && freeHunt >= 2 && m_MountedTokenCount <= 254 && HasMountedPairCandidate(now);
|
||||
array<vector> reserved = {}; int first = -1; int second = -1;
|
||||
foreach (ERR_HQUnit known : m_Units) ReserveUnit(known, reserved, first, second);
|
||||
int count = m_Memory.Count();
|
||||
if (count == 0) return;
|
||||
int start = m_MountedSurveyCursor % count;
|
||||
int queries;
|
||||
for (int scanned = 0; scanned < count && queries < 2; scanned++)
|
||||
{
|
||||
int b = (start + scanned) % count;
|
||||
m_MountedSurveyCursor = (b + 1) % count;
|
||||
ERR_HQBelief belief = m_Memory.At(b);
|
||||
if (!ERR_HQMountedInterceptRules.Eligible(belief, now) || MountedInterceptAttempted(belief.BeliefId, belief.BeliefRevision)) continue;
|
||||
if (pairPossible)
|
||||
{
|
||||
vector positionA; vector positionB; queries++;
|
||||
if (ERR_HQMountedInterceptRules.SelectPair(belief, now, reserved, positionA, positionB))
|
||||
{
|
||||
ref ERR_HQMountedChoice bestA; ref ERR_HQMountedChoice bestB;
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
// Enumerate the bounded original escorts: a greedy nearest first crew
|
||||
// can be the only crew within range of the other road position.
|
||||
foreach (ERR_ConvoyVehicle firstVehicle : task.Vehicles)
|
||||
{
|
||||
if (!firstVehicle || (firstVehicle.Index != 0 && firstVehicle.Index != 3 && firstVehicle.Index != 5)) continue;
|
||||
ref ERR_HQMountedChoice candidateA = MountedChoice(task, positionA, null, now, firstVehicle);
|
||||
if (!candidateA) continue;
|
||||
ref ERR_HQMountedChoice candidateB = MountedChoice(task, positionB, candidateA.Unit, now);
|
||||
if (!candidateB || candidateA.Unit.Group == candidateB.Unit.Group || candidateA.Route == candidateB.Route) continue;
|
||||
if (bestA && candidateA.Distance + candidateB.Distance >= bestA.Distance + bestB.Distance) continue;
|
||||
bestA = candidateA; bestB = candidateB;
|
||||
}
|
||||
}
|
||||
if (bestA) { QueueMountedIntercept(bestA, bestB, belief, now); return; }
|
||||
}
|
||||
}
|
||||
// Only pre-admission fallback, sharing the SAME two-survey pass budget.
|
||||
if (queries >= 2) continue;
|
||||
vector destination; queries++;
|
||||
if (!ERR_HQMountedInterceptRules.Select(belief, now, reserved, destination)) continue;
|
||||
ref ERR_HQMountedChoice best;
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ref ERR_HQMountedChoice candidate = MountedChoice(m_Convoys.At(t), destination, null, now);
|
||||
if (candidate && (!best || candidate.Distance < best.Distance)) best = candidate;
|
||||
}
|
||||
if (best) { QueueMountedIntercept(best, null, belief, now); return; }
|
||||
}
|
||||
}
|
||||
override void Stop()
|
||||
{
|
||||
float now;
|
||||
if (GetGame() && GetGame().GetWorld()) now = GetGame().GetWorld().GetWorldTime();
|
||||
ReleaseMountedIntercept(now, "mission stopped");
|
||||
super.Stop();
|
||||
}
|
||||
}
|
||||
+353
@@ -0,0 +1,353 @@
|
||||
// Detached planning choice; the route remains strongly owned before admission.
|
||||
class ERR_HQMountedChoice
|
||||
{
|
||||
ERR_HQUnit Unit;
|
||||
ERR_ConvoyTask Task;
|
||||
ERR_ConvoyVehicle Vehicle;
|
||||
ref ERR_HQRoute Route;
|
||||
vector Destination;
|
||||
float Distance;
|
||||
}
|
||||
|
||||
// One or two original escorts may intercept a road ahead of delivered travel history.
|
||||
// This HQ-owned transport uses the ordinary radio graph and never reads players.
|
||||
modded class ERR_HQ
|
||||
{
|
||||
protected ref ERR_ConvoyInterceptOrder m_MountedIntercept;
|
||||
protected ERR_HQUnit m_MountedInterceptUnit;
|
||||
protected ERR_ConvoyTask m_MountedInterceptTask;
|
||||
protected ref ERR_HQRoute m_MountedInterceptRoute;
|
||||
protected bool m_MountedInterceptDelivered;
|
||||
protected ref ERR_ConvoyInterceptOrder m_MountedInterceptPartner;
|
||||
protected ERR_HQUnit m_MountedInterceptPartnerUnit;
|
||||
protected ref ERR_HQRoute m_MountedInterceptPartnerRoute;
|
||||
protected bool m_MountedInterceptReceipt;
|
||||
protected bool m_MountedInterceptPartnerReceipt;
|
||||
protected int m_MountedTokenCount;
|
||||
protected int m_MountedSurveyCursor;
|
||||
protected ref array<int> m_MountedAttemptBeliefs = {};
|
||||
protected ref array<int> m_MountedAttemptRevisions = {};
|
||||
|
||||
override string Status()
|
||||
{
|
||||
string result = super.Status();
|
||||
if (!m_MountedIntercept) return result;
|
||||
string phase = "queued";
|
||||
if (m_MountedInterceptDelivered) phase = "delivered";
|
||||
if (m_MountedInterceptPartner) result += "\nPaired mounted interception (two original escorts)";
|
||||
return result + "\n" + string.Format("Mounted interception: escort %1; %2; report M%3; road %4", m_MountedIntercept.VehicleIndex, phase, m_MountedIntercept.ReportId, m_MountedIntercept.Position);
|
||||
}
|
||||
|
||||
protected override int Assigned()
|
||||
{
|
||||
int count = super.Assigned();
|
||||
if (m_MountedIntercept) count++;
|
||||
// mutation: partner not counted
|
||||
return count;
|
||||
}
|
||||
protected override int HuntingAssigned()
|
||||
{
|
||||
int count = super.HuntingAssigned();
|
||||
if (m_MountedIntercept) count++;
|
||||
// mutation: partner not counted
|
||||
return count;
|
||||
}
|
||||
protected override void ReserveUnit(ERR_HQUnit unit, array<vector> reserved, out int first, out int second)
|
||||
{
|
||||
super.ReserveUnit(unit, reserved, first, second);
|
||||
if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);
|
||||
if (m_MountedInterceptPartner && unit == m_MountedInterceptPartnerUnit) reserved.Insert(m_MountedInterceptPartner.Position);
|
||||
}
|
||||
protected bool MountedInterceptAttempted(int beliefId, int revision)
|
||||
{
|
||||
for (int i = 0; i < m_MountedAttemptBeliefs.Count(); i++)
|
||||
if (m_MountedAttemptBeliefs[i] == beliefId && m_MountedAttemptRevisions[i] == revision) return true;
|
||||
return false;
|
||||
}
|
||||
protected bool MountedInterceptStatus(ERR_HQUnit unit, ERR_ConvoyVehicle vehicle, float now)
|
||||
{
|
||||
if (!unit || !vehicle || !unit.StatusSampler || vehicle.Group != unit.Group || !vehicle.Entity
|
||||
|| !unit.StatusSampler.OwnsRoster(unit.Group) || !unit.StatusSampler.ValidateBoundVehicle(vehicle.Entity)) return false;
|
||||
ERR_HQWatchEntry entry = m_Watch.Find(unit.ForceSourceId);
|
||||
if (!entry || entry.Closed || entry.SourceId != unit.ForceSourceId || entry.MessageId <= 0
|
||||
|| !ERR_HQMemoryRules.DeliveryFresh(entry.ObservedAt, now) || !ERR_ForceRules.Finite(entry.LastDeliveredAt)
|
||||
|| entry.LastDeliveredAt < entry.ObservedAt || entry.LastDeliveredAt > now
|
||||
|| !ERR_ForceRules.FiniteVector(entry.Position) || entry.Count < 1 || entry.Count != entry.Strength
|
||||
|| entry.Fit != entry.Count || !entry.MountedAmmo || !entry.MountedAmmo.Complete || !entry.MountedAmmo.Valid()) return false;
|
||||
return entry.MountedAmmo.Weapons > 0 && entry.MountedAmmo.Rounds + entry.MountedAmmo.Chambers > 0;
|
||||
}
|
||||
protected void ReleaseMountedIntercept(float now, string reason)
|
||||
{
|
||||
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
|
||||
ref ERR_ConvoyInterceptOrder partner = m_MountedInterceptPartner;
|
||||
ERR_HQUnit unit = m_MountedInterceptUnit;
|
||||
ERR_HQUnit partnerUnit = m_MountedInterceptPartnerUnit;
|
||||
ERR_ConvoyTask task = m_MountedInterceptTask;
|
||||
// Detach BOTH first: physical release may synchronously unregister a group.
|
||||
m_MountedIntercept = null; m_MountedInterceptUnit = null; m_MountedInterceptTask = null;
|
||||
m_MountedInterceptPartner = null; m_MountedInterceptPartnerUnit = null;
|
||||
m_MountedInterceptRoute = null; m_MountedInterceptPartnerRoute = null;
|
||||
m_MountedInterceptDelivered = false; m_MountedInterceptReceipt = false; m_MountedInterceptPartnerReceipt = false;
|
||||
if (!order) return;
|
||||
if (m_Convoys) m_Convoys.ReleaseIntercept(task, order, now);
|
||||
if (partner && m_Convoys) m_Convoys.ReleaseIntercept(task, partner, now);
|
||||
if (unit && unit.LogisticsReservation == order.Token) unit.LogisticsReservation = 0;
|
||||
if (partner && partnerUnit && partnerUnit.LogisticsReservation == partner.Token) partnerUnit.LogisticsReservation = 0;
|
||||
m_Journal.Write("MOUNTED INTERCEPT END", reason, order.ReportId);
|
||||
}
|
||||
protected override void ReleaseUnit(ERR_HQUnit unit, float now)
|
||||
{
|
||||
if (m_MountedIntercept && (unit == m_MountedInterceptUnit || unit == m_MountedInterceptPartnerUnit)) ReleaseMountedIntercept(now, "original formation released");
|
||||
super.ReleaseUnit(unit, now);
|
||||
}
|
||||
protected bool MountedMemberCurrent(ERR_HQUnit unit, ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
if (!order || !unit || !m_Units.Contains(unit) || unit.Group != order.Group) return false;
|
||||
if (unit.LogisticsReservation != order.Token || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)) return false;
|
||||
return m_Convoys && m_Convoys.InterceptCurrent(m_MountedInterceptTask, order);
|
||||
}
|
||||
protected bool MountedMemberReady(ERR_HQUnit unit, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!MountedMemberCurrent(unit, order)) return false;
|
||||
ERR_ConvoyVehicle vehicle = m_MountedInterceptTask.Vehicles[order.VehicleIndex];
|
||||
return MountedInterceptStatus(unit, vehicle, now) && !ConvoyContact(unit, now);
|
||||
}
|
||||
protected override void TickMountedIntercept(float now)
|
||||
{
|
||||
if (!m_MountedIntercept || !ERR_ForceRules.Finite(now)) return;
|
||||
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
|
||||
ref ERR_ConvoyInterceptOrder partner = m_MountedInterceptPartner;
|
||||
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
|
||||
if (now < order.IssuedAt || now >= order.ExpiresAt || !MissionActive() || m_Stopping
|
||||
|| Assigned() > globalLimit || HuntingAssigned() > ERR_HQPursuitRules.Limit(globalLimit)
|
||||
|| !MountedMemberCurrent(m_MountedInterceptUnit, order))
|
||||
{ ReleaseMountedIntercept(now, "expiry, capacity or original ownership changed"); return; }
|
||||
if (partner && !MountedMemberCurrent(m_MountedInterceptPartnerUnit, partner))
|
||||
{ ReleaseMountedIntercept(now, "partner ownership changed"); return; }
|
||||
if (m_MountedInterceptDelivered) return;
|
||||
ERR_HQBelief belief = m_Memory.GetById(order.BeliefId);
|
||||
if (!ERR_HQMountedInterceptRules.Current(order, belief, now))
|
||||
{ ReleaseMountedIntercept(now, "delivered history changed before command arrival"); return; }
|
||||
if (partner && !ERR_HQMountedInterceptRules.Current(partner, belief, now))
|
||||
{ ReleaseMountedIntercept(now, "partner command expired"); return; }
|
||||
if (!m_MountedInterceptReceipt)
|
||||
{
|
||||
if (!m_MountedInterceptRoute) { ReleaseMountedIntercept(now, "missing radio route"); return; }
|
||||
int delivered = m_MountedInterceptRoute.Advance(now, m_Journal);
|
||||
if (m_MountedIntercept != order) return;
|
||||
if (delivered < 0) { ReleaseMountedIntercept(now, "radio interrupted; authored route retained"); return; }
|
||||
if (delivered > 0) { m_MountedInterceptReceipt = true; }
|
||||
}
|
||||
if (partner && !m_MountedInterceptPartnerReceipt)
|
||||
{
|
||||
if (!m_MountedInterceptPartnerRoute) { ReleaseMountedIntercept(now, "missing partner route"); return; }
|
||||
int partnerDelivered = m_MountedInterceptPartnerRoute.Advance(now, m_Journal);
|
||||
if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
if (partnerDelivered < 0) { ReleaseMountedIntercept(now, "partner radio interrupted"); return; }
|
||||
if (partnerDelivered > 0) { m_MountedInterceptPartnerReceipt = true; }
|
||||
}
|
||||
if (!m_MountedInterceptReceipt || (partner && !m_MountedInterceptPartnerReceipt)) return;
|
||||
if (!MountedMemberReady(m_MountedInterceptUnit, order, now))
|
||||
{ ReleaseMountedIntercept(now, "command refused by original crew"); return; }
|
||||
if (partner && !MountedMemberReady(m_MountedInterceptPartnerUnit, partner, now))
|
||||
{ ReleaseMountedIntercept(now, "command refused by original partner"); return; }
|
||||
bool applied;
|
||||
if (partner) applied = m_Convoys.ApplyInterceptPair(m_MountedInterceptTask, order, partner, now);
|
||||
else applied = m_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now);
|
||||
if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
if (!applied) { ReleaseMountedIntercept(now, "physical command refused"); return; }
|
||||
m_MountedInterceptDelivered = true;
|
||||
m_Journal.Write("MOUNTED INTERCEPT DELIVERED", "original armed escorts ordered to remembered road positions", order.ReportId);
|
||||
}
|
||||
protected bool MountedInterceptTaskAvailable(ERR_ConvoyTask task)
|
||||
{
|
||||
if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) return false;
|
||||
// Service admission excludes the whole original convoy, including sibling
|
||||
// holds still in transit. Do not spend a delivered revision on a busy task.
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || vehicle.InterceptToken > 0 || vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) return false;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
if (unit.Group == vehicle.Group && (unit.PendingConvoyOrder || unit.Downlink || unit.LogisticsReservation > 0)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected bool HasMountedInterceptCandidate(float now)
|
||||
{
|
||||
// Avoid native road surveys until one original escort has usable delivered status.
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|
||||
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
if (unit.Group == vehicle.Group && MountedLogisticsEligible(unit) && !ConvoyContact(unit, now)
|
||||
&& MountedInterceptStatus(unit, vehicle, now)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected ERR_HQMountedChoice MountedChoice(ERR_ConvoyTask task, vector destination, ERR_HQUnit excluded, float now, ERR_ConvoyVehicle required = null)
|
||||
{
|
||||
if (!MountedInterceptTaskAvailable(task)) return null;
|
||||
ref ERR_HQMountedChoice best;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (required && vehicle != required) continue;
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (unit == excluded || unit.Group != vehicle.Group || !MountedLogisticsEligible(unit)
|
||||
|| ConvoyContact(unit, now) || !MountedInterceptStatus(unit, vehicle, now)) continue;
|
||||
ERR_HQWatchEntry own = m_Watch.Find(unit.ForceSourceId);
|
||||
float distance = vector.DistanceXZ(own.Position, destination);
|
||||
if (distance > m_Mode.Settings().Runtime("responseRadiusMetres") || (best && distance >= best.Distance)) continue;
|
||||
ref ERR_HQRoute route = m_Network.Route(unit.Radio, true);
|
||||
if (!route) continue;
|
||||
best = new ERR_HQMountedChoice();
|
||||
best.Unit = unit; best.Task = task; best.Vehicle = vehicle; best.Route = route;
|
||||
best.Destination = destination; best.Distance = distance;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
protected bool HasMountedPairCandidate(float now)
|
||||
{
|
||||
// Cheap original/status gate; no geometry or claims before both roles exist.
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
ERR_HQUnit firstUnit;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now)
|
||||
|| !MountedInterceptStatus(unit, vehicle, now)) continue;
|
||||
if (firstUnit && firstUnit != unit && firstUnit.Group != unit.Group) return true;
|
||||
firstUnit = unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected ERR_ConvoyInterceptOrder MountedOrder(ERR_HQMountedChoice choice, ERR_HQBelief belief, float now, int token)
|
||||
{
|
||||
ERR_ConvoyInterceptOrder order = new ERR_ConvoyInterceptOrder();
|
||||
order.Token = token; order.Group = choice.Unit.Group; order.SourceId = choice.Task.Receipt.SourceId;
|
||||
order.VehicleIndex = choice.Vehicle.Index; order.Position = choice.Destination;
|
||||
order.BeliefId = belief.BeliefId; order.BeliefRevision = belief.BeliefRevision;
|
||||
order.ReportId = belief.MessageId; order.ObservedAt = belief.ObservedAt;
|
||||
order.IssuedAt = now; order.ExpiresAt = belief.ObservedAt + 120000;
|
||||
return order;
|
||||
}
|
||||
protected void QueueMountedIntercept(ERR_HQMountedChoice choice, ERR_HQMountedChoice partnerChoice, ERR_HQBelief belief, float now)
|
||||
{
|
||||
int members = 1;
|
||||
if (partnerChoice) members = 2;
|
||||
if (m_MountedTokenCount + members > 256) return;
|
||||
ref ERR_ConvoyInterceptOrder order = MountedOrder(choice, belief, now, 400001 + m_MountedTokenCount);
|
||||
ref ERR_ConvoyInterceptOrder partner;
|
||||
if (partnerChoice) partner = MountedOrder(partnerChoice, belief, now, 400002 + m_MountedTokenCount);
|
||||
if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) return;
|
||||
if (partner && !ERR_HQMountedInterceptRules.Current(partner, belief, now)) return;
|
||||
// IDs and this revision remain spent after refusal; no post-claim fallback.
|
||||
m_MountedTokenCount += members;
|
||||
m_MountedAttemptBeliefs.Insert(order.BeliefId); m_MountedAttemptRevisions.Insert(order.BeliefRevision);
|
||||
choice.Unit.LogisticsReservation = order.Token;
|
||||
if (partnerChoice) partnerChoice.Unit.LogisticsReservation = partner.Token;
|
||||
m_MountedIntercept = order; m_MountedInterceptUnit = choice.Unit; m_MountedInterceptTask = choice.Task;
|
||||
m_MountedInterceptRoute = choice.Route; m_MountedInterceptDelivered = false; m_MountedInterceptReceipt = false;
|
||||
m_MountedInterceptPartner = partner; m_MountedInterceptPartnerReceipt = false;
|
||||
if (partnerChoice) { m_MountedInterceptPartnerUnit = partnerChoice.Unit; m_MountedInterceptPartnerRoute = partnerChoice.Route; }
|
||||
bool admitted;
|
||||
if (partner) admitted = m_Convoys.ReserveInterceptPair(choice.Task, order, partner, now);
|
||||
else admitted = m_Convoys.ReserveIntercept(choice.Task, order, now);
|
||||
if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
if (!admitted) { ReleaseMountedIntercept(now, "reservation refused"); return; }
|
||||
choice.Route.MessageId = m_Journal.Message();
|
||||
choice.Route.Payload = string.Format("original escort %1 to road %2; delivered track %3 revision %4 report M%5", order.VehicleIndex, order.Position, order.BeliefId, order.BeliefRevision, order.ReportId);
|
||||
m_Journal.Write("MOUNTED INTERCEPT QUEUED", choice.Route.Payload, choice.Route.MessageId);
|
||||
if (partnerChoice)
|
||||
{
|
||||
partnerChoice.Route.MessageId = m_Journal.Message();
|
||||
partnerChoice.Route.Payload = string.Format("original partner %1 to road %2; report M%3", partner.VehicleIndex, partner.Position, partner.ReportId);
|
||||
m_Journal.Write("MOUNTED INTERCEPT QUEUED", partnerChoice.Route.Payload, partnerChoice.Route.MessageId);
|
||||
}
|
||||
}
|
||||
protected override void PlanMountedIntercept(float now)
|
||||
{
|
||||
if (!Replication.IsServer() || m_Stopping || !MissionActive() || m_MountedIntercept
|
||||
|| m_MountedAttemptBeliefs.Count() >= 256 || m_MountedTokenCount >= 256) return;
|
||||
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
|
||||
int freeGlobal = globalLimit - Assigned();
|
||||
int freeHunt = ERR_HQPursuitRules.Limit(globalLimit) - HuntingAssigned();
|
||||
if (freeGlobal < 1 || freeHunt < 1 || !HasMountedInterceptCandidate(now)) return;
|
||||
bool pairPossible = freeGlobal >= 2 && freeHunt >= 2 && m_MountedTokenCount <= 254 && HasMountedPairCandidate(now);
|
||||
array<vector> reserved = {}; int first = -1; int second = -1;
|
||||
foreach (ERR_HQUnit known : m_Units) ReserveUnit(known, reserved, first, second);
|
||||
int count = m_Memory.Count();
|
||||
if (count == 0) return;
|
||||
int start = m_MountedSurveyCursor % count;
|
||||
int queries;
|
||||
for (int scanned = 0; scanned < count && queries < 2; scanned++)
|
||||
{
|
||||
int b = (start + scanned) % count;
|
||||
m_MountedSurveyCursor = (b + 1) % count;
|
||||
ERR_HQBelief belief = m_Memory.At(b);
|
||||
if (!ERR_HQMountedInterceptRules.Eligible(belief, now) || MountedInterceptAttempted(belief.BeliefId, belief.BeliefRevision)) continue;
|
||||
if (pairPossible)
|
||||
{
|
||||
vector positionA; vector positionB; queries++;
|
||||
if (ERR_HQMountedInterceptRules.SelectPair(belief, now, reserved, positionA, positionB))
|
||||
{
|
||||
ref ERR_HQMountedChoice bestA; ref ERR_HQMountedChoice bestB;
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
// Enumerate the bounded original escorts: a greedy nearest first crew
|
||||
// can be the only crew within range of the other road position.
|
||||
foreach (ERR_ConvoyVehicle firstVehicle : task.Vehicles)
|
||||
{
|
||||
if (!firstVehicle || (firstVehicle.Index != 0 && firstVehicle.Index != 3 && firstVehicle.Index != 5)) continue;
|
||||
ref ERR_HQMountedChoice candidateA = MountedChoice(task, positionA, null, now, firstVehicle);
|
||||
if (!candidateA) continue;
|
||||
ref ERR_HQMountedChoice candidateB = MountedChoice(task, positionB, candidateA.Unit, now);
|
||||
if (!candidateB || candidateA.Unit.Group == candidateB.Unit.Group || candidateA.Route == candidateB.Route) continue;
|
||||
if (bestA && candidateA.Distance + candidateB.Distance >= bestA.Distance + bestB.Distance) continue;
|
||||
bestA = candidateA; bestB = candidateB;
|
||||
}
|
||||
}
|
||||
if (bestA) { QueueMountedIntercept(bestA, bestB, belief, now); return; }
|
||||
}
|
||||
}
|
||||
// Only pre-admission fallback, sharing the SAME two-survey pass budget.
|
||||
if (queries >= 2) continue;
|
||||
vector destination; queries++;
|
||||
if (!ERR_HQMountedInterceptRules.Select(belief, now, reserved, destination)) continue;
|
||||
ref ERR_HQMountedChoice best;
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ref ERR_HQMountedChoice candidate = MountedChoice(m_Convoys.At(t), destination, null, now);
|
||||
if (candidate && (!best || candidate.Distance < best.Distance)) best = candidate;
|
||||
}
|
||||
if (best) { QueueMountedIntercept(best, null, belief, now); return; }
|
||||
}
|
||||
}
|
||||
override void Stop()
|
||||
{
|
||||
float now;
|
||||
if (GetGame() && GetGame().GetWorld()) now = GetGame().GetWorld().GetWorldTime();
|
||||
ReleaseMountedIntercept(now, "mission stopped");
|
||||
super.Stop();
|
||||
}
|
||||
}
|
||||
+353
@@ -0,0 +1,353 @@
|
||||
// Detached planning choice; the route remains strongly owned before admission.
|
||||
class ERR_HQMountedChoice
|
||||
{
|
||||
ERR_HQUnit Unit;
|
||||
ERR_ConvoyTask Task;
|
||||
ERR_ConvoyVehicle Vehicle;
|
||||
ref ERR_HQRoute Route;
|
||||
vector Destination;
|
||||
float Distance;
|
||||
}
|
||||
|
||||
// One or two original escorts may intercept a road ahead of delivered travel history.
|
||||
// This HQ-owned transport uses the ordinary radio graph and never reads players.
|
||||
modded class ERR_HQ
|
||||
{
|
||||
protected ref ERR_ConvoyInterceptOrder m_MountedIntercept;
|
||||
protected ERR_HQUnit m_MountedInterceptUnit;
|
||||
protected ERR_ConvoyTask m_MountedInterceptTask;
|
||||
protected ref ERR_HQRoute m_MountedInterceptRoute;
|
||||
protected bool m_MountedInterceptDelivered;
|
||||
protected ref ERR_ConvoyInterceptOrder m_MountedInterceptPartner;
|
||||
protected ERR_HQUnit m_MountedInterceptPartnerUnit;
|
||||
protected ref ERR_HQRoute m_MountedInterceptPartnerRoute;
|
||||
protected bool m_MountedInterceptReceipt;
|
||||
protected bool m_MountedInterceptPartnerReceipt;
|
||||
protected int m_MountedTokenCount;
|
||||
protected int m_MountedSurveyCursor;
|
||||
protected ref array<int> m_MountedAttemptBeliefs = {};
|
||||
protected ref array<int> m_MountedAttemptRevisions = {};
|
||||
|
||||
override string Status()
|
||||
{
|
||||
string result = super.Status();
|
||||
if (!m_MountedIntercept) return result;
|
||||
string phase = "queued";
|
||||
if (m_MountedInterceptDelivered) phase = "delivered";
|
||||
if (m_MountedInterceptPartner) result += "\nPaired mounted interception (two original escorts)";
|
||||
return result + "\n" + string.Format("Mounted interception: escort %1; %2; report M%3; road %4", m_MountedIntercept.VehicleIndex, phase, m_MountedIntercept.ReportId, m_MountedIntercept.Position);
|
||||
}
|
||||
|
||||
protected override int Assigned()
|
||||
{
|
||||
int count = super.Assigned();
|
||||
if (m_MountedIntercept) count++;
|
||||
if (m_MountedInterceptPartner) count++;
|
||||
return count;
|
||||
}
|
||||
protected override int HuntingAssigned()
|
||||
{
|
||||
int count = super.HuntingAssigned();
|
||||
if (m_MountedIntercept) count++;
|
||||
if (m_MountedInterceptPartner) count++;
|
||||
return count;
|
||||
}
|
||||
protected override void ReserveUnit(ERR_HQUnit unit, array<vector> reserved, out int first, out int second)
|
||||
{
|
||||
super.ReserveUnit(unit, reserved, first, second);
|
||||
if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);
|
||||
if (m_MountedInterceptPartner && unit == m_MountedInterceptPartnerUnit) reserved.Insert(m_MountedInterceptPartner.Position);
|
||||
}
|
||||
protected bool MountedInterceptAttempted(int beliefId, int revision)
|
||||
{
|
||||
for (int i = 0; i < m_MountedAttemptBeliefs.Count(); i++)
|
||||
if (m_MountedAttemptBeliefs[i] == beliefId && m_MountedAttemptRevisions[i] == revision) return true;
|
||||
return false;
|
||||
}
|
||||
protected bool MountedInterceptStatus(ERR_HQUnit unit, ERR_ConvoyVehicle vehicle, float now)
|
||||
{
|
||||
if (!unit || !vehicle || !unit.StatusSampler || vehicle.Group != unit.Group || !vehicle.Entity
|
||||
|| !unit.StatusSampler.OwnsRoster(unit.Group) || !unit.StatusSampler.ValidateBoundVehicle(vehicle.Entity)) return false;
|
||||
ERR_HQWatchEntry entry = m_Watch.Find(unit.ForceSourceId);
|
||||
if (!entry || entry.Closed || entry.SourceId != unit.ForceSourceId || entry.MessageId <= 0
|
||||
|| !ERR_HQMemoryRules.DeliveryFresh(entry.ObservedAt, now) || !ERR_ForceRules.Finite(entry.LastDeliveredAt)
|
||||
|| entry.LastDeliveredAt < entry.ObservedAt || entry.LastDeliveredAt > now
|
||||
|| !ERR_ForceRules.FiniteVector(entry.Position) || entry.Count < 1 || entry.Count != entry.Strength
|
||||
|| entry.Fit != entry.Count || !entry.MountedAmmo || !entry.MountedAmmo.Complete || !entry.MountedAmmo.Valid()) return false;
|
||||
return entry.MountedAmmo.Weapons > 0 && entry.MountedAmmo.Rounds + entry.MountedAmmo.Chambers > 0;
|
||||
}
|
||||
protected void ReleaseMountedIntercept(float now, string reason)
|
||||
{
|
||||
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
|
||||
ref ERR_ConvoyInterceptOrder partner = m_MountedInterceptPartner;
|
||||
ERR_HQUnit unit = m_MountedInterceptUnit;
|
||||
ERR_HQUnit partnerUnit = m_MountedInterceptPartnerUnit;
|
||||
ERR_ConvoyTask task = m_MountedInterceptTask;
|
||||
// Detach BOTH first: physical release may synchronously unregister a group.
|
||||
m_MountedIntercept = null; m_MountedInterceptUnit = null; m_MountedInterceptTask = null;
|
||||
m_MountedInterceptPartner = null; m_MountedInterceptPartnerUnit = null;
|
||||
m_MountedInterceptRoute = null; m_MountedInterceptPartnerRoute = null;
|
||||
m_MountedInterceptDelivered = false; m_MountedInterceptReceipt = false; m_MountedInterceptPartnerReceipt = false;
|
||||
if (!order) return;
|
||||
if (m_Convoys) m_Convoys.ReleaseIntercept(task, order, now);
|
||||
if (partner && m_Convoys) m_Convoys.ReleaseIntercept(task, partner, now);
|
||||
if (unit && unit.LogisticsReservation == order.Token) unit.LogisticsReservation = 0;
|
||||
if (partner && partnerUnit && partnerUnit.LogisticsReservation == partner.Token) partnerUnit.LogisticsReservation = 0;
|
||||
m_Journal.Write("MOUNTED INTERCEPT END", reason, order.ReportId);
|
||||
}
|
||||
protected override void ReleaseUnit(ERR_HQUnit unit, float now)
|
||||
{
|
||||
if (m_MountedIntercept && (unit == m_MountedInterceptUnit || unit == m_MountedInterceptPartnerUnit)) ReleaseMountedIntercept(now, "original formation released");
|
||||
super.ReleaseUnit(unit, now);
|
||||
}
|
||||
protected bool MountedMemberCurrent(ERR_HQUnit unit, ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
if (!order || !unit || !m_Units.Contains(unit) || unit.Group != order.Group) return false;
|
||||
if (unit.LogisticsReservation != order.Token || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)) return false;
|
||||
return m_Convoys && m_Convoys.InterceptCurrent(m_MountedInterceptTask, order);
|
||||
}
|
||||
protected bool MountedMemberReady(ERR_HQUnit unit, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!MountedMemberCurrent(unit, order)) return false;
|
||||
ERR_ConvoyVehicle vehicle = m_MountedInterceptTask.Vehicles[order.VehicleIndex];
|
||||
return MountedInterceptStatus(unit, vehicle, now) && !ConvoyContact(unit, now);
|
||||
}
|
||||
protected override void TickMountedIntercept(float now)
|
||||
{
|
||||
if (!m_MountedIntercept || !ERR_ForceRules.Finite(now)) return;
|
||||
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
|
||||
ref ERR_ConvoyInterceptOrder partner = m_MountedInterceptPartner;
|
||||
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
|
||||
if (now < order.IssuedAt || now >= order.ExpiresAt || !MissionActive() || m_Stopping
|
||||
|| Assigned() > globalLimit || HuntingAssigned() > ERR_HQPursuitRules.Limit(globalLimit)
|
||||
|| !MountedMemberCurrent(m_MountedInterceptUnit, order))
|
||||
{ ReleaseMountedIntercept(now, "expiry, capacity or original ownership changed"); return; }
|
||||
if (partner && !MountedMemberCurrent(m_MountedInterceptPartnerUnit, partner))
|
||||
{ ReleaseMountedIntercept(now, "partner ownership changed"); return; }
|
||||
if (m_MountedInterceptDelivered) return;
|
||||
ERR_HQBelief belief = m_Memory.GetById(order.BeliefId);
|
||||
if (!ERR_HQMountedInterceptRules.Current(order, belief, now))
|
||||
{ ReleaseMountedIntercept(now, "delivered history changed before command arrival"); return; }
|
||||
if (partner && !ERR_HQMountedInterceptRules.Current(partner, belief, now))
|
||||
{ ReleaseMountedIntercept(now, "partner command expired"); return; }
|
||||
if (!m_MountedInterceptReceipt)
|
||||
{
|
||||
if (!m_MountedInterceptRoute) { ReleaseMountedIntercept(now, "missing radio route"); return; }
|
||||
int delivered = m_MountedInterceptRoute.Advance(now, m_Journal);
|
||||
if (m_MountedIntercept != order) return;
|
||||
if (delivered < 0) { ReleaseMountedIntercept(now, "radio interrupted; authored route retained"); return; }
|
||||
if (delivered > 0) { m_MountedInterceptReceipt = true; }
|
||||
}
|
||||
if (partner && !m_MountedInterceptPartnerReceipt)
|
||||
{
|
||||
if (!m_MountedInterceptPartnerRoute) { ReleaseMountedIntercept(now, "missing partner route"); return; }
|
||||
int partnerDelivered = m_MountedInterceptPartnerRoute.Advance(now, m_Journal);
|
||||
if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
if (partnerDelivered < 0) { ReleaseMountedIntercept(now, "partner radio interrupted"); return; }
|
||||
if (partnerDelivered > 0) { m_MountedInterceptPartnerReceipt = true; }
|
||||
}
|
||||
if (!m_MountedInterceptReceipt) return;
|
||||
if (!MountedMemberReady(m_MountedInterceptUnit, order, now))
|
||||
{ ReleaseMountedIntercept(now, "command refused by original crew"); return; }
|
||||
if (partner && !MountedMemberReady(m_MountedInterceptPartnerUnit, partner, now))
|
||||
{ ReleaseMountedIntercept(now, "command refused by original partner"); return; }
|
||||
bool applied;
|
||||
if (partner) applied = m_Convoys.ApplyInterceptPair(m_MountedInterceptTask, order, partner, now);
|
||||
else applied = m_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now);
|
||||
if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
if (!applied) { ReleaseMountedIntercept(now, "physical command refused"); return; }
|
||||
m_MountedInterceptDelivered = true;
|
||||
m_Journal.Write("MOUNTED INTERCEPT DELIVERED", "original armed escorts ordered to remembered road positions", order.ReportId);
|
||||
}
|
||||
protected bool MountedInterceptTaskAvailable(ERR_ConvoyTask task)
|
||||
{
|
||||
if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) return false;
|
||||
// Service admission excludes the whole original convoy, including sibling
|
||||
// holds still in transit. Do not spend a delivered revision on a busy task.
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || vehicle.InterceptToken > 0 || vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) return false;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
if (unit.Group == vehicle.Group && (unit.PendingConvoyOrder || unit.Downlink || unit.LogisticsReservation > 0)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected bool HasMountedInterceptCandidate(float now)
|
||||
{
|
||||
// Avoid native road surveys until one original escort has usable delivered status.
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|
||||
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
if (unit.Group == vehicle.Group && MountedLogisticsEligible(unit) && !ConvoyContact(unit, now)
|
||||
&& MountedInterceptStatus(unit, vehicle, now)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected ERR_HQMountedChoice MountedChoice(ERR_ConvoyTask task, vector destination, ERR_HQUnit excluded, float now, ERR_ConvoyVehicle required = null)
|
||||
{
|
||||
if (!MountedInterceptTaskAvailable(task)) return null;
|
||||
ref ERR_HQMountedChoice best;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (required && vehicle != required) continue;
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (unit == excluded || unit.Group != vehicle.Group || !MountedLogisticsEligible(unit)
|
||||
|| ConvoyContact(unit, now) || !MountedInterceptStatus(unit, vehicle, now)) continue;
|
||||
ERR_HQWatchEntry own = m_Watch.Find(unit.ForceSourceId);
|
||||
float distance = vector.DistanceXZ(own.Position, destination);
|
||||
if (distance > m_Mode.Settings().Runtime("responseRadiusMetres") || (best && distance >= best.Distance)) continue;
|
||||
ref ERR_HQRoute route = m_Network.Route(unit.Radio, true);
|
||||
if (!route) continue;
|
||||
best = new ERR_HQMountedChoice();
|
||||
best.Unit = unit; best.Task = task; best.Vehicle = vehicle; best.Route = route;
|
||||
best.Destination = destination; best.Distance = distance;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
protected bool HasMountedPairCandidate(float now)
|
||||
{
|
||||
// Cheap original/status gate; no geometry or claims before both roles exist.
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
ERR_HQUnit firstUnit;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now)
|
||||
|| !MountedInterceptStatus(unit, vehicle, now)) continue;
|
||||
if (firstUnit && firstUnit != unit && firstUnit.Group != unit.Group) return true;
|
||||
firstUnit = unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected ERR_ConvoyInterceptOrder MountedOrder(ERR_HQMountedChoice choice, ERR_HQBelief belief, float now, int token)
|
||||
{
|
||||
ERR_ConvoyInterceptOrder order = new ERR_ConvoyInterceptOrder();
|
||||
order.Token = token; order.Group = choice.Unit.Group; order.SourceId = choice.Task.Receipt.SourceId;
|
||||
order.VehicleIndex = choice.Vehicle.Index; order.Position = choice.Destination;
|
||||
order.BeliefId = belief.BeliefId; order.BeliefRevision = belief.BeliefRevision;
|
||||
order.ReportId = belief.MessageId; order.ObservedAt = belief.ObservedAt;
|
||||
order.IssuedAt = now; order.ExpiresAt = belief.ObservedAt + 120000;
|
||||
return order;
|
||||
}
|
||||
protected void QueueMountedIntercept(ERR_HQMountedChoice choice, ERR_HQMountedChoice partnerChoice, ERR_HQBelief belief, float now)
|
||||
{
|
||||
int members = 1;
|
||||
if (partnerChoice) members = 2;
|
||||
if (m_MountedTokenCount + members > 256) return;
|
||||
ref ERR_ConvoyInterceptOrder order = MountedOrder(choice, belief, now, 400001 + m_MountedTokenCount);
|
||||
ref ERR_ConvoyInterceptOrder partner;
|
||||
if (partnerChoice) partner = MountedOrder(partnerChoice, belief, now, 400002 + m_MountedTokenCount);
|
||||
if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) return;
|
||||
if (partner && !ERR_HQMountedInterceptRules.Current(partner, belief, now)) return;
|
||||
// IDs and this revision remain spent after refusal; no post-claim fallback.
|
||||
m_MountedTokenCount += members;
|
||||
m_MountedAttemptBeliefs.Insert(order.BeliefId); m_MountedAttemptRevisions.Insert(order.BeliefRevision);
|
||||
choice.Unit.LogisticsReservation = order.Token;
|
||||
if (partnerChoice) partnerChoice.Unit.LogisticsReservation = partner.Token;
|
||||
m_MountedIntercept = order; m_MountedInterceptUnit = choice.Unit; m_MountedInterceptTask = choice.Task;
|
||||
m_MountedInterceptRoute = choice.Route; m_MountedInterceptDelivered = false; m_MountedInterceptReceipt = false;
|
||||
m_MountedInterceptPartner = partner; m_MountedInterceptPartnerReceipt = false;
|
||||
if (partnerChoice) { m_MountedInterceptPartnerUnit = partnerChoice.Unit; m_MountedInterceptPartnerRoute = partnerChoice.Route; }
|
||||
bool admitted;
|
||||
if (partner) admitted = m_Convoys.ReserveInterceptPair(choice.Task, order, partner, now);
|
||||
else admitted = m_Convoys.ReserveIntercept(choice.Task, order, now);
|
||||
if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
if (!admitted) { ReleaseMountedIntercept(now, "reservation refused"); return; }
|
||||
choice.Route.MessageId = m_Journal.Message();
|
||||
choice.Route.Payload = string.Format("original escort %1 to road %2; delivered track %3 revision %4 report M%5", order.VehicleIndex, order.Position, order.BeliefId, order.BeliefRevision, order.ReportId);
|
||||
m_Journal.Write("MOUNTED INTERCEPT QUEUED", choice.Route.Payload, choice.Route.MessageId);
|
||||
if (partnerChoice)
|
||||
{
|
||||
partnerChoice.Route.MessageId = m_Journal.Message();
|
||||
partnerChoice.Route.Payload = string.Format("original partner %1 to road %2; report M%3", partner.VehicleIndex, partner.Position, partner.ReportId);
|
||||
m_Journal.Write("MOUNTED INTERCEPT QUEUED", partnerChoice.Route.Payload, partnerChoice.Route.MessageId);
|
||||
}
|
||||
}
|
||||
protected override void PlanMountedIntercept(float now)
|
||||
{
|
||||
if (!Replication.IsServer() || m_Stopping || !MissionActive() || m_MountedIntercept
|
||||
|| m_MountedAttemptBeliefs.Count() >= 256 || m_MountedTokenCount >= 256) return;
|
||||
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
|
||||
int freeGlobal = globalLimit - Assigned();
|
||||
int freeHunt = ERR_HQPursuitRules.Limit(globalLimit) - HuntingAssigned();
|
||||
if (freeGlobal < 1 || freeHunt < 1 || !HasMountedInterceptCandidate(now)) return;
|
||||
bool pairPossible = freeGlobal >= 2 && freeHunt >= 2 && m_MountedTokenCount <= 254 && HasMountedPairCandidate(now);
|
||||
array<vector> reserved = {}; int first = -1; int second = -1;
|
||||
foreach (ERR_HQUnit known : m_Units) ReserveUnit(known, reserved, first, second);
|
||||
int count = m_Memory.Count();
|
||||
if (count == 0) return;
|
||||
int start = m_MountedSurveyCursor % count;
|
||||
int queries;
|
||||
for (int scanned = 0; scanned < count && queries < 2; scanned++)
|
||||
{
|
||||
int b = (start + scanned) % count;
|
||||
m_MountedSurveyCursor = (b + 1) % count;
|
||||
ERR_HQBelief belief = m_Memory.At(b);
|
||||
if (!ERR_HQMountedInterceptRules.Eligible(belief, now) || MountedInterceptAttempted(belief.BeliefId, belief.BeliefRevision)) continue;
|
||||
if (pairPossible)
|
||||
{
|
||||
vector positionA; vector positionB; queries++;
|
||||
if (ERR_HQMountedInterceptRules.SelectPair(belief, now, reserved, positionA, positionB))
|
||||
{
|
||||
ref ERR_HQMountedChoice bestA; ref ERR_HQMountedChoice bestB;
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
// Enumerate the bounded original escorts: a greedy nearest first crew
|
||||
// can be the only crew within range of the other road position.
|
||||
foreach (ERR_ConvoyVehicle firstVehicle : task.Vehicles)
|
||||
{
|
||||
if (!firstVehicle || (firstVehicle.Index != 0 && firstVehicle.Index != 3 && firstVehicle.Index != 5)) continue;
|
||||
ref ERR_HQMountedChoice candidateA = MountedChoice(task, positionA, null, now, firstVehicle);
|
||||
if (!candidateA) continue;
|
||||
ref ERR_HQMountedChoice candidateB = MountedChoice(task, positionB, candidateA.Unit, now);
|
||||
if (!candidateB || candidateA.Unit.Group == candidateB.Unit.Group || candidateA.Route == candidateB.Route) continue;
|
||||
if (bestA && candidateA.Distance + candidateB.Distance >= bestA.Distance + bestB.Distance) continue;
|
||||
bestA = candidateA; bestB = candidateB;
|
||||
}
|
||||
}
|
||||
if (bestA) { QueueMountedIntercept(bestA, bestB, belief, now); return; }
|
||||
}
|
||||
}
|
||||
// Only pre-admission fallback, sharing the SAME two-survey pass budget.
|
||||
if (queries >= 2) continue;
|
||||
vector destination; queries++;
|
||||
if (!ERR_HQMountedInterceptRules.Select(belief, now, reserved, destination)) continue;
|
||||
ref ERR_HQMountedChoice best;
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ref ERR_HQMountedChoice candidate = MountedChoice(m_Convoys.At(t), destination, null, now);
|
||||
if (candidate && (!best || candidate.Distance < best.Distance)) best = candidate;
|
||||
}
|
||||
if (best) { QueueMountedIntercept(best, null, belief, now); return; }
|
||||
}
|
||||
}
|
||||
override void Stop()
|
||||
{
|
||||
float now;
|
||||
if (GetGame() && GetGame().GetWorld()) now = GetGame().GetWorld().GetWorldTime();
|
||||
ReleaseMountedIntercept(now, "mission stopped");
|
||||
super.Stop();
|
||||
}
|
||||
}
|
||||
+353
@@ -0,0 +1,353 @@
|
||||
// Detached planning choice; the route remains strongly owned before admission.
|
||||
class ERR_HQMountedChoice
|
||||
{
|
||||
ERR_HQUnit Unit;
|
||||
ERR_ConvoyTask Task;
|
||||
ERR_ConvoyVehicle Vehicle;
|
||||
ref ERR_HQRoute Route;
|
||||
vector Destination;
|
||||
float Distance;
|
||||
}
|
||||
|
||||
// One or two original escorts may intercept a road ahead of delivered travel history.
|
||||
// This HQ-owned transport uses the ordinary radio graph and never reads players.
|
||||
modded class ERR_HQ
|
||||
{
|
||||
protected ref ERR_ConvoyInterceptOrder m_MountedIntercept;
|
||||
protected ERR_HQUnit m_MountedInterceptUnit;
|
||||
protected ERR_ConvoyTask m_MountedInterceptTask;
|
||||
protected ref ERR_HQRoute m_MountedInterceptRoute;
|
||||
protected bool m_MountedInterceptDelivered;
|
||||
protected ref ERR_ConvoyInterceptOrder m_MountedInterceptPartner;
|
||||
protected ERR_HQUnit m_MountedInterceptPartnerUnit;
|
||||
protected ref ERR_HQRoute m_MountedInterceptPartnerRoute;
|
||||
protected bool m_MountedInterceptReceipt;
|
||||
protected bool m_MountedInterceptPartnerReceipt;
|
||||
protected int m_MountedTokenCount;
|
||||
protected int m_MountedSurveyCursor;
|
||||
protected ref array<int> m_MountedAttemptBeliefs = {};
|
||||
protected ref array<int> m_MountedAttemptRevisions = {};
|
||||
|
||||
override string Status()
|
||||
{
|
||||
string result = super.Status();
|
||||
if (!m_MountedIntercept) return result;
|
||||
string phase = "queued";
|
||||
if (m_MountedInterceptDelivered) phase = "delivered";
|
||||
if (m_MountedInterceptPartner) result += "\nPaired mounted interception (two original escorts)";
|
||||
return result + "\n" + string.Format("Mounted interception: escort %1; %2; report M%3; road %4", m_MountedIntercept.VehicleIndex, phase, m_MountedIntercept.ReportId, m_MountedIntercept.Position);
|
||||
}
|
||||
|
||||
protected override int Assigned()
|
||||
{
|
||||
int count = super.Assigned();
|
||||
if (m_MountedIntercept) count++;
|
||||
if (m_MountedInterceptPartner) count++;
|
||||
return count;
|
||||
}
|
||||
protected override int HuntingAssigned()
|
||||
{
|
||||
int count = super.HuntingAssigned();
|
||||
if (m_MountedIntercept) count++;
|
||||
if (m_MountedInterceptPartner) count++;
|
||||
return count;
|
||||
}
|
||||
protected override void ReserveUnit(ERR_HQUnit unit, array<vector> reserved, out int first, out int second)
|
||||
{
|
||||
super.ReserveUnit(unit, reserved, first, second);
|
||||
if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);
|
||||
if (m_MountedInterceptPartner && unit == m_MountedInterceptPartnerUnit) reserved.Insert(m_MountedInterceptPartner.Position);
|
||||
}
|
||||
protected bool MountedInterceptAttempted(int beliefId, int revision)
|
||||
{
|
||||
for (int i = 0; i < m_MountedAttemptBeliefs.Count(); i++)
|
||||
if (m_MountedAttemptBeliefs[i] == beliefId && m_MountedAttemptRevisions[i] == revision) return true;
|
||||
return false;
|
||||
}
|
||||
protected bool MountedInterceptStatus(ERR_HQUnit unit, ERR_ConvoyVehicle vehicle, float now)
|
||||
{
|
||||
if (!unit || !vehicle || !unit.StatusSampler || vehicle.Group != unit.Group || !vehicle.Entity
|
||||
|| !unit.StatusSampler.OwnsRoster(unit.Group) || !unit.StatusSampler.ValidateBoundVehicle(vehicle.Entity)) return false;
|
||||
ERR_HQWatchEntry entry = m_Watch.Find(unit.ForceSourceId);
|
||||
if (!entry || entry.Closed || entry.SourceId != unit.ForceSourceId || entry.MessageId <= 0
|
||||
|| !ERR_HQMemoryRules.DeliveryFresh(entry.ObservedAt, now) || !ERR_ForceRules.Finite(entry.LastDeliveredAt)
|
||||
|| entry.LastDeliveredAt < entry.ObservedAt || entry.LastDeliveredAt > now
|
||||
|| !ERR_ForceRules.FiniteVector(entry.Position) || entry.Count < 1 || entry.Count != entry.Strength
|
||||
|| entry.Fit != entry.Count || !entry.MountedAmmo || !entry.MountedAmmo.Complete || !entry.MountedAmmo.Valid()) return false;
|
||||
return entry.MountedAmmo.Weapons > 0 && entry.MountedAmmo.Rounds + entry.MountedAmmo.Chambers > 0;
|
||||
}
|
||||
protected void ReleaseMountedIntercept(float now, string reason)
|
||||
{
|
||||
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
|
||||
ref ERR_ConvoyInterceptOrder partner = m_MountedInterceptPartner;
|
||||
ERR_HQUnit unit = m_MountedInterceptUnit;
|
||||
ERR_HQUnit partnerUnit = m_MountedInterceptPartnerUnit;
|
||||
ERR_ConvoyTask task = m_MountedInterceptTask;
|
||||
// Detach BOTH first: physical release may synchronously unregister a group.
|
||||
m_MountedIntercept = null; m_MountedInterceptUnit = null; m_MountedInterceptTask = null;
|
||||
m_MountedInterceptPartner = null; m_MountedInterceptPartnerUnit = null;
|
||||
m_MountedInterceptRoute = null; m_MountedInterceptPartnerRoute = null;
|
||||
m_MountedInterceptDelivered = false; m_MountedInterceptReceipt = false; m_MountedInterceptPartnerReceipt = false;
|
||||
if (!order) return;
|
||||
if (m_Convoys) m_Convoys.ReleaseIntercept(task, order, now);
|
||||
if (partner && m_Convoys) m_Convoys.ReleaseIntercept(task, partner, now);
|
||||
if (unit && unit.LogisticsReservation == order.Token) unit.LogisticsReservation = 0;
|
||||
if (partner && partnerUnit && partnerUnit.LogisticsReservation == partner.Token) partnerUnit.LogisticsReservation = 0;
|
||||
m_Journal.Write("MOUNTED INTERCEPT END", reason, order.ReportId);
|
||||
}
|
||||
protected override void ReleaseUnit(ERR_HQUnit unit, float now)
|
||||
{
|
||||
if (m_MountedIntercept && (unit == m_MountedInterceptUnit || unit == m_MountedInterceptPartnerUnit)) ReleaseMountedIntercept(now, "original formation released");
|
||||
super.ReleaseUnit(unit, now);
|
||||
}
|
||||
protected bool MountedMemberCurrent(ERR_HQUnit unit, ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
if (!order || !unit || !m_Units.Contains(unit) || unit.Group != order.Group) return false;
|
||||
if (unit.LogisticsReservation != order.Token || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)) return false;
|
||||
return m_Convoys && m_Convoys.InterceptCurrent(m_MountedInterceptTask, order);
|
||||
}
|
||||
protected bool MountedMemberReady(ERR_HQUnit unit, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!MountedMemberCurrent(unit, order)) return false;
|
||||
ERR_ConvoyVehicle vehicle = m_MountedInterceptTask.Vehicles[order.VehicleIndex];
|
||||
return MountedInterceptStatus(unit, vehicle, now) && !ConvoyContact(unit, now);
|
||||
}
|
||||
protected override void TickMountedIntercept(float now)
|
||||
{
|
||||
if (!m_MountedIntercept || !ERR_ForceRules.Finite(now)) return;
|
||||
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
|
||||
ref ERR_ConvoyInterceptOrder partner = m_MountedInterceptPartner;
|
||||
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
|
||||
if (now < order.IssuedAt || now >= order.ExpiresAt || !MissionActive() || m_Stopping
|
||||
|| Assigned() > globalLimit || HuntingAssigned() > ERR_HQPursuitRules.Limit(globalLimit)
|
||||
|| !MountedMemberCurrent(m_MountedInterceptUnit, order))
|
||||
{ ReleaseMountedIntercept(now, "expiry, capacity or original ownership changed"); return; }
|
||||
if (partner && !MountedMemberCurrent(m_MountedInterceptPartnerUnit, partner))
|
||||
{ ReleaseMountedIntercept(now, "partner ownership changed"); return; }
|
||||
if (m_MountedInterceptDelivered) return;
|
||||
ERR_HQBelief belief = m_Memory.GetById(order.BeliefId);
|
||||
if (!ERR_HQMountedInterceptRules.Current(order, belief, now))
|
||||
{ ReleaseMountedIntercept(now, "delivered history changed before command arrival"); return; }
|
||||
if (partner && !ERR_HQMountedInterceptRules.Current(partner, belief, now))
|
||||
{ ReleaseMountedIntercept(now, "partner command expired"); return; }
|
||||
if (!m_MountedInterceptReceipt)
|
||||
{
|
||||
if (!m_MountedInterceptRoute) { ReleaseMountedIntercept(now, "missing radio route"); return; }
|
||||
int delivered = m_MountedInterceptRoute.Advance(now, m_Journal);
|
||||
if (m_MountedIntercept != order) return;
|
||||
if (delivered < 0) { ReleaseMountedIntercept(now, "radio interrupted; authored route retained"); return; }
|
||||
if (delivered > 0) { m_MountedInterceptReceipt = true; }
|
||||
}
|
||||
if (partner && !m_MountedInterceptPartnerReceipt)
|
||||
{
|
||||
if (!m_MountedInterceptPartnerRoute) { ReleaseMountedIntercept(now, "missing partner route"); return; }
|
||||
int partnerDelivered = m_MountedInterceptPartnerRoute.Advance(now, m_Journal);
|
||||
if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
if (partnerDelivered < 0) { ReleaseMountedIntercept(now, "partner radio interrupted"); return; }
|
||||
if (partnerDelivered > 0) { m_MountedInterceptPartnerReceipt = true; }
|
||||
}
|
||||
if (!m_MountedInterceptReceipt || (partner && !m_MountedInterceptPartnerReceipt)) return;
|
||||
if (!MountedMemberReady(m_MountedInterceptUnit, order, now))
|
||||
{ ReleaseMountedIntercept(now, "command refused by original crew"); return; }
|
||||
if (partner && !MountedMemberReady(m_MountedInterceptPartnerUnit, partner, now))
|
||||
{ ReleaseMountedIntercept(now, "command refused by original partner"); return; }
|
||||
bool applied;
|
||||
if (partner) applied = m_Convoys.ApplyInterceptPair(m_MountedInterceptTask, order, partner, now);
|
||||
else applied = m_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now);
|
||||
if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
if (!applied) { ReleaseMountedIntercept(now, "physical command refused"); return; }
|
||||
m_MountedInterceptDelivered = true;
|
||||
m_Journal.Write("MOUNTED INTERCEPT DELIVERED", "original armed escorts ordered to remembered road positions", order.ReportId);
|
||||
}
|
||||
protected bool MountedInterceptTaskAvailable(ERR_ConvoyTask task)
|
||||
{
|
||||
if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) return false;
|
||||
// Service admission excludes the whole original convoy, including sibling
|
||||
// holds still in transit. Do not spend a delivered revision on a busy task.
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || vehicle.InterceptToken > 0 || vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) return false;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
if (unit.Group == vehicle.Group && (unit.PendingConvoyOrder || unit.Downlink || unit.LogisticsReservation > 0)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected bool HasMountedInterceptCandidate(float now)
|
||||
{
|
||||
// Avoid native road surveys until one original escort has usable delivered status.
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|
||||
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
if (unit.Group == vehicle.Group && MountedLogisticsEligible(unit) && !ConvoyContact(unit, now)
|
||||
&& MountedInterceptStatus(unit, vehicle, now)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected ERR_HQMountedChoice MountedChoice(ERR_ConvoyTask task, vector destination, ERR_HQUnit excluded, float now, ERR_ConvoyVehicle required = null)
|
||||
{
|
||||
if (!MountedInterceptTaskAvailable(task)) return null;
|
||||
ref ERR_HQMountedChoice best;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (required && vehicle != required) continue;
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (unit == excluded || unit.Group != vehicle.Group || !MountedLogisticsEligible(unit)
|
||||
|| ConvoyContact(unit, now) || !MountedInterceptStatus(unit, vehicle, now)) continue;
|
||||
ERR_HQWatchEntry own = m_Watch.Find(unit.ForceSourceId);
|
||||
float distance = vector.DistanceXZ(own.Position, destination);
|
||||
if (distance > m_Mode.Settings().Runtime("responseRadiusMetres") || (best && distance >= best.Distance)) continue;
|
||||
ref ERR_HQRoute route = m_Network.Route(unit.Radio, true);
|
||||
if (!route) continue;
|
||||
best = new ERR_HQMountedChoice();
|
||||
best.Unit = unit; best.Task = task; best.Vehicle = vehicle; best.Route = route;
|
||||
best.Destination = destination; best.Distance = distance;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
protected bool HasMountedPairCandidate(float now)
|
||||
{
|
||||
// Cheap original/status gate; no geometry or claims before both roles exist.
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
ERR_HQUnit firstUnit;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now)
|
||||
|| !MountedInterceptStatus(unit, vehicle, now)) continue;
|
||||
if (firstUnit && firstUnit != unit && firstUnit.Group != unit.Group) return true;
|
||||
firstUnit = unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected ERR_ConvoyInterceptOrder MountedOrder(ERR_HQMountedChoice choice, ERR_HQBelief belief, float now, int token)
|
||||
{
|
||||
ERR_ConvoyInterceptOrder order = new ERR_ConvoyInterceptOrder();
|
||||
order.Token = token; order.Group = choice.Unit.Group; order.SourceId = choice.Task.Receipt.SourceId;
|
||||
order.VehicleIndex = choice.Vehicle.Index; order.Position = choice.Destination;
|
||||
order.BeliefId = belief.BeliefId; order.BeliefRevision = belief.BeliefRevision;
|
||||
order.ReportId = belief.MessageId; order.ObservedAt = belief.ObservedAt;
|
||||
order.IssuedAt = now; order.ExpiresAt = belief.ObservedAt + 120000;
|
||||
return order;
|
||||
}
|
||||
protected void QueueMountedIntercept(ERR_HQMountedChoice choice, ERR_HQMountedChoice partnerChoice, ERR_HQBelief belief, float now)
|
||||
{
|
||||
int members = 1;
|
||||
if (partnerChoice) members = 2;
|
||||
if (m_MountedTokenCount + members > 256) return;
|
||||
ref ERR_ConvoyInterceptOrder order = MountedOrder(choice, belief, now, 400001 + m_MountedTokenCount);
|
||||
ref ERR_ConvoyInterceptOrder partner;
|
||||
if (partnerChoice) partner = MountedOrder(partnerChoice, belief, now, 400002 + m_MountedTokenCount);
|
||||
if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) return;
|
||||
if (partner && !ERR_HQMountedInterceptRules.Current(partner, belief, now)) return;
|
||||
// IDs and this revision remain spent after refusal; no post-claim fallback.
|
||||
m_MountedTokenCount += members;
|
||||
m_MountedAttemptBeliefs.Insert(order.BeliefId); m_MountedAttemptRevisions.Insert(order.BeliefRevision);
|
||||
choice.Unit.LogisticsReservation = order.Token;
|
||||
if (partnerChoice) partnerChoice.Unit.LogisticsReservation = partner.Token;
|
||||
m_MountedIntercept = order; m_MountedInterceptUnit = choice.Unit; m_MountedInterceptTask = choice.Task;
|
||||
m_MountedInterceptRoute = choice.Route; m_MountedInterceptDelivered = false; m_MountedInterceptReceipt = false;
|
||||
m_MountedInterceptPartner = partner; m_MountedInterceptPartnerReceipt = false;
|
||||
if (partnerChoice) { m_MountedInterceptPartnerUnit = partnerChoice.Unit; m_MountedInterceptPartnerRoute = partnerChoice.Route; }
|
||||
bool admitted;
|
||||
if (partner) admitted = m_Convoys.ReserveInterceptPair(choice.Task, order, partner, now);
|
||||
else admitted = m_Convoys.ReserveIntercept(choice.Task, order, now);
|
||||
if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
if (!admitted) { ReleaseMountedIntercept(now, "reservation refused"); return; }
|
||||
choice.Route.MessageId = m_Journal.Message();
|
||||
choice.Route.Payload = string.Format("original escort %1 to road %2; delivered track %3 revision %4 report M%5", order.VehicleIndex, order.Position, order.BeliefId, order.BeliefRevision, order.ReportId);
|
||||
m_Journal.Write("MOUNTED INTERCEPT QUEUED", choice.Route.Payload, choice.Route.MessageId);
|
||||
if (partnerChoice)
|
||||
{
|
||||
partnerChoice.Route.MessageId = m_Journal.Message();
|
||||
partnerChoice.Route.Payload = string.Format("original partner %1 to road %2; report M%3", partner.VehicleIndex, partner.Position, partner.ReportId);
|
||||
m_Journal.Write("MOUNTED INTERCEPT QUEUED", partnerChoice.Route.Payload, partnerChoice.Route.MessageId);
|
||||
}
|
||||
}
|
||||
protected override void PlanMountedIntercept(float now)
|
||||
{
|
||||
if (!Replication.IsServer() || m_Stopping || !MissionActive() || m_MountedIntercept
|
||||
|| m_MountedAttemptBeliefs.Count() >= 256 || m_MountedTokenCount >= 256) return;
|
||||
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
|
||||
int freeGlobal = globalLimit - Assigned();
|
||||
int freeHunt = ERR_HQPursuitRules.Limit(globalLimit) - HuntingAssigned();
|
||||
if (freeGlobal < 1 || freeHunt < 1 || !HasMountedInterceptCandidate(now)) return;
|
||||
bool pairPossible = freeGlobal >= 2 && freeHunt >= 2 && m_MountedTokenCount <= 254 && HasMountedPairCandidate(now);
|
||||
array<vector> reserved = {}; int first = -1; int second = -1;
|
||||
foreach (ERR_HQUnit known : m_Units) ReserveUnit(known, reserved, first, second);
|
||||
int count = m_Memory.Count();
|
||||
if (count == 0) return;
|
||||
int start = m_MountedSurveyCursor % count;
|
||||
int queries;
|
||||
for (int scanned = 0; scanned < count && queries < 3; scanned++)
|
||||
{
|
||||
int b = (start + scanned) % count;
|
||||
m_MountedSurveyCursor = (b + 1) % count;
|
||||
ERR_HQBelief belief = m_Memory.At(b);
|
||||
if (!ERR_HQMountedInterceptRules.Eligible(belief, now) || MountedInterceptAttempted(belief.BeliefId, belief.BeliefRevision)) continue;
|
||||
if (pairPossible)
|
||||
{
|
||||
vector positionA; vector positionB; queries++;
|
||||
if (ERR_HQMountedInterceptRules.SelectPair(belief, now, reserved, positionA, positionB))
|
||||
{
|
||||
ref ERR_HQMountedChoice bestA; ref ERR_HQMountedChoice bestB;
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
// Enumerate the bounded original escorts: a greedy nearest first crew
|
||||
// can be the only crew within range of the other road position.
|
||||
foreach (ERR_ConvoyVehicle firstVehicle : task.Vehicles)
|
||||
{
|
||||
if (!firstVehicle || (firstVehicle.Index != 0 && firstVehicle.Index != 3 && firstVehicle.Index != 5)) continue;
|
||||
ref ERR_HQMountedChoice candidateA = MountedChoice(task, positionA, null, now, firstVehicle);
|
||||
if (!candidateA) continue;
|
||||
ref ERR_HQMountedChoice candidateB = MountedChoice(task, positionB, candidateA.Unit, now);
|
||||
if (!candidateB || candidateA.Unit.Group == candidateB.Unit.Group || candidateA.Route == candidateB.Route) continue;
|
||||
if (bestA && candidateA.Distance + candidateB.Distance >= bestA.Distance + bestB.Distance) continue;
|
||||
bestA = candidateA; bestB = candidateB;
|
||||
}
|
||||
}
|
||||
if (bestA) { QueueMountedIntercept(bestA, bestB, belief, now); return; }
|
||||
}
|
||||
}
|
||||
// Only pre-admission fallback, sharing the SAME two-survey pass budget.
|
||||
if (queries >= 2) continue;
|
||||
vector destination; queries++;
|
||||
if (!ERR_HQMountedInterceptRules.Select(belief, now, reserved, destination)) continue;
|
||||
ref ERR_HQMountedChoice best;
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ref ERR_HQMountedChoice candidate = MountedChoice(m_Convoys.At(t), destination, null, now);
|
||||
if (candidate && (!best || candidate.Distance < best.Distance)) best = candidate;
|
||||
}
|
||||
if (best) { QueueMountedIntercept(best, null, belief, now); return; }
|
||||
}
|
||||
}
|
||||
override void Stop()
|
||||
{
|
||||
float now;
|
||||
if (GetGame() && GetGame().GetWorld()) now = GetGame().GetWorld().GetWorldTime();
|
||||
ReleaseMountedIntercept(now, "mission stopped");
|
||||
super.Stop();
|
||||
}
|
||||
}
|
||||
+353
@@ -0,0 +1,353 @@
|
||||
// Detached planning choice; the route remains strongly owned before admission.
|
||||
class ERR_HQMountedChoice
|
||||
{
|
||||
ERR_HQUnit Unit;
|
||||
ERR_ConvoyTask Task;
|
||||
ERR_ConvoyVehicle Vehicle;
|
||||
ref ERR_HQRoute Route;
|
||||
vector Destination;
|
||||
float Distance;
|
||||
}
|
||||
|
||||
// One or two original escorts may intercept a road ahead of delivered travel history.
|
||||
// This HQ-owned transport uses the ordinary radio graph and never reads players.
|
||||
modded class ERR_HQ
|
||||
{
|
||||
protected ref ERR_ConvoyInterceptOrder m_MountedIntercept;
|
||||
protected ERR_HQUnit m_MountedInterceptUnit;
|
||||
protected ERR_ConvoyTask m_MountedInterceptTask;
|
||||
protected ref ERR_HQRoute m_MountedInterceptRoute;
|
||||
protected bool m_MountedInterceptDelivered;
|
||||
protected ref ERR_ConvoyInterceptOrder m_MountedInterceptPartner;
|
||||
protected ERR_HQUnit m_MountedInterceptPartnerUnit;
|
||||
protected ref ERR_HQRoute m_MountedInterceptPartnerRoute;
|
||||
protected bool m_MountedInterceptReceipt;
|
||||
protected bool m_MountedInterceptPartnerReceipt;
|
||||
protected int m_MountedTokenCount;
|
||||
protected int m_MountedSurveyCursor;
|
||||
protected ref array<int> m_MountedAttemptBeliefs = {};
|
||||
protected ref array<int> m_MountedAttemptRevisions = {};
|
||||
|
||||
override string Status()
|
||||
{
|
||||
string result = super.Status();
|
||||
if (!m_MountedIntercept) return result;
|
||||
string phase = "queued";
|
||||
if (m_MountedInterceptDelivered) phase = "delivered";
|
||||
if (m_MountedInterceptPartner) result += "\nPaired mounted interception (two original escorts)";
|
||||
return result + "\n" + string.Format("Mounted interception: escort %1; %2; report M%3; road %4", m_MountedIntercept.VehicleIndex, phase, m_MountedIntercept.ReportId, m_MountedIntercept.Position);
|
||||
}
|
||||
|
||||
protected override int Assigned()
|
||||
{
|
||||
int count = super.Assigned();
|
||||
if (m_MountedIntercept) count++;
|
||||
if (m_MountedInterceptPartner) count++;
|
||||
return count;
|
||||
}
|
||||
protected override int HuntingAssigned()
|
||||
{
|
||||
int count = super.HuntingAssigned();
|
||||
if (m_MountedIntercept) count++;
|
||||
if (m_MountedInterceptPartner) count++;
|
||||
return count;
|
||||
}
|
||||
protected override void ReserveUnit(ERR_HQUnit unit, array<vector> reserved, out int first, out int second)
|
||||
{
|
||||
super.ReserveUnit(unit, reserved, first, second);
|
||||
if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);
|
||||
if (m_MountedInterceptPartner && unit == m_MountedInterceptPartnerUnit) reserved.Insert(m_MountedInterceptPartner.Position);
|
||||
}
|
||||
protected bool MountedInterceptAttempted(int beliefId, int revision)
|
||||
{
|
||||
for (int i = 0; i < m_MountedAttemptBeliefs.Count(); i++)
|
||||
if (m_MountedAttemptBeliefs[i] == beliefId && m_MountedAttemptRevisions[i] == revision) return true;
|
||||
return false;
|
||||
}
|
||||
protected bool MountedInterceptStatus(ERR_HQUnit unit, ERR_ConvoyVehicle vehicle, float now)
|
||||
{
|
||||
if (!unit || !vehicle || !unit.StatusSampler || vehicle.Group != unit.Group || !vehicle.Entity
|
||||
|| !unit.StatusSampler.OwnsRoster(unit.Group) || !unit.StatusSampler.ValidateBoundVehicle(vehicle.Entity)) return false;
|
||||
ERR_HQWatchEntry entry = m_Watch.Find(unit.ForceSourceId);
|
||||
if (!entry || entry.Closed || entry.SourceId != unit.ForceSourceId || entry.MessageId <= 0
|
||||
|| !ERR_HQMemoryRules.DeliveryFresh(entry.ObservedAt, now) || !ERR_ForceRules.Finite(entry.LastDeliveredAt)
|
||||
|| entry.LastDeliveredAt < entry.ObservedAt || entry.LastDeliveredAt > now
|
||||
|| !ERR_ForceRules.FiniteVector(entry.Position) || entry.Count < 1 || entry.Count != entry.Strength
|
||||
|| entry.Fit != entry.Count || !entry.MountedAmmo || !entry.MountedAmmo.Complete || !entry.MountedAmmo.Valid()) return false;
|
||||
return entry.MountedAmmo.Weapons > 0 && entry.MountedAmmo.Rounds + entry.MountedAmmo.Chambers > 0;
|
||||
}
|
||||
protected void ReleaseMountedIntercept(float now, string reason)
|
||||
{
|
||||
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
|
||||
ref ERR_ConvoyInterceptOrder partner = m_MountedInterceptPartner;
|
||||
ERR_HQUnit unit = m_MountedInterceptUnit;
|
||||
ERR_HQUnit partnerUnit = m_MountedInterceptPartnerUnit;
|
||||
ERR_ConvoyTask task = m_MountedInterceptTask;
|
||||
// Detach BOTH first: physical release may synchronously unregister a group.
|
||||
m_MountedIntercept = null; m_MountedInterceptUnit = null; m_MountedInterceptTask = null;
|
||||
m_MountedInterceptPartner = null; m_MountedInterceptPartnerUnit = null;
|
||||
m_MountedInterceptRoute = null; m_MountedInterceptPartnerRoute = null;
|
||||
m_MountedInterceptDelivered = false; m_MountedInterceptReceipt = false; m_MountedInterceptPartnerReceipt = false;
|
||||
if (!order) return;
|
||||
if (m_Convoys) m_Convoys.ReleaseIntercept(task, order, now);
|
||||
if (partner && m_Convoys) m_Convoys.ReleaseIntercept(task, partner, now);
|
||||
if (unit && unit.LogisticsReservation == order.Token) unit.LogisticsReservation = 0;
|
||||
if (partner && partnerUnit && partnerUnit.LogisticsReservation == partner.Token) partnerUnit.LogisticsReservation = 0;
|
||||
m_Journal.Write("MOUNTED INTERCEPT END", reason, order.ReportId);
|
||||
}
|
||||
protected override void ReleaseUnit(ERR_HQUnit unit, float now)
|
||||
{
|
||||
if (m_MountedIntercept && (unit == m_MountedInterceptUnit || unit == m_MountedInterceptPartnerUnit)) ReleaseMountedIntercept(now, "original formation released");
|
||||
super.ReleaseUnit(unit, now);
|
||||
}
|
||||
protected bool MountedMemberCurrent(ERR_HQUnit unit, ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
if (!order || !unit || !m_Units.Contains(unit) || unit.Group != order.Group) return false;
|
||||
if (unit.LogisticsReservation != order.Token || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)) return false;
|
||||
return m_Convoys && m_Convoys.InterceptCurrent(m_MountedInterceptTask, order);
|
||||
}
|
||||
protected bool MountedMemberReady(ERR_HQUnit unit, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!MountedMemberCurrent(unit, order)) return false;
|
||||
ERR_ConvoyVehicle vehicle = m_MountedInterceptTask.Vehicles[order.VehicleIndex];
|
||||
return MountedInterceptStatus(unit, vehicle, now) && !ConvoyContact(unit, now);
|
||||
}
|
||||
protected override void TickMountedIntercept(float now)
|
||||
{
|
||||
if (!m_MountedIntercept || !ERR_ForceRules.Finite(now)) return;
|
||||
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
|
||||
ref ERR_ConvoyInterceptOrder partner = m_MountedInterceptPartner;
|
||||
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
|
||||
if (now < order.IssuedAt || now >= order.ExpiresAt || !MissionActive() || m_Stopping
|
||||
|| Assigned() > globalLimit || HuntingAssigned() > ERR_HQPursuitRules.Limit(globalLimit)
|
||||
|| !MountedMemberCurrent(m_MountedInterceptUnit, order))
|
||||
{ ReleaseMountedIntercept(now, "expiry, capacity or original ownership changed"); return; }
|
||||
if (partner && !MountedMemberCurrent(m_MountedInterceptPartnerUnit, partner))
|
||||
{ ReleaseMountedIntercept(now, "partner ownership changed"); return; }
|
||||
if (m_MountedInterceptDelivered) return;
|
||||
ERR_HQBelief belief = m_Memory.GetById(order.BeliefId);
|
||||
if (!ERR_HQMountedInterceptRules.Current(order, belief, now))
|
||||
{ ReleaseMountedIntercept(now, "delivered history changed before command arrival"); return; }
|
||||
if (partner && !ERR_HQMountedInterceptRules.Current(partner, belief, now))
|
||||
{ ReleaseMountedIntercept(now, "partner command expired"); return; }
|
||||
if (!m_MountedInterceptReceipt)
|
||||
{
|
||||
if (!m_MountedInterceptRoute) { ReleaseMountedIntercept(now, "missing radio route"); return; }
|
||||
int delivered = m_MountedInterceptRoute.Advance(now, m_Journal);
|
||||
if (m_MountedIntercept != order) return;
|
||||
if (delivered < 0) { ReleaseMountedIntercept(now, "radio interrupted; authored route retained"); return; }
|
||||
if (delivered > 0) { m_MountedInterceptReceipt = true; }
|
||||
}
|
||||
if (partner && !m_MountedInterceptPartnerReceipt)
|
||||
{
|
||||
if (!m_MountedInterceptPartnerRoute) { ReleaseMountedIntercept(now, "missing partner route"); return; }
|
||||
int partnerDelivered = m_MountedInterceptPartnerRoute.Advance(now, m_Journal);
|
||||
if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
if (partnerDelivered < 0) { ReleaseMountedIntercept(now, "partner radio interrupted"); return; }
|
||||
if (partnerDelivered > 0) { m_MountedInterceptPartnerReceipt = true; }
|
||||
}
|
||||
if (!m_MountedInterceptReceipt || (partner && !m_MountedInterceptPartnerReceipt)) return;
|
||||
if (!MountedMemberReady(m_MountedInterceptUnit, order, now))
|
||||
{ ReleaseMountedIntercept(now, "command refused by original crew"); return; }
|
||||
if (partner && !MountedMemberReady(m_MountedInterceptPartnerUnit, partner, now))
|
||||
{ ReleaseMountedIntercept(now, "command refused by original partner"); return; }
|
||||
bool applied;
|
||||
if (partner) applied = m_Convoys.ApplyInterceptPair(m_MountedInterceptTask, order, partner, now);
|
||||
else applied = m_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now);
|
||||
if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
if (!applied) { ReleaseMountedIntercept(now, "physical command refused"); return; }
|
||||
m_MountedInterceptDelivered = true;
|
||||
m_Journal.Write("MOUNTED INTERCEPT DELIVERED", "original armed escorts ordered to remembered road positions", order.ReportId);
|
||||
}
|
||||
protected bool MountedInterceptTaskAvailable(ERR_ConvoyTask task)
|
||||
{
|
||||
if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) return false;
|
||||
// Service admission excludes the whole original convoy, including sibling
|
||||
// holds still in transit. Do not spend a delivered revision on a busy task.
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || vehicle.InterceptToken > 0 || vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) return false;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
if (unit.Group == vehicle.Group && (unit.PendingConvoyOrder || unit.Downlink || unit.LogisticsReservation > 0)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected bool HasMountedInterceptCandidate(float now)
|
||||
{
|
||||
// Avoid native road surveys until one original escort has usable delivered status.
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|
||||
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
if (unit.Group == vehicle.Group && MountedLogisticsEligible(unit) && !ConvoyContact(unit, now)
|
||||
&& MountedInterceptStatus(unit, vehicle, now)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected ERR_HQMountedChoice MountedChoice(ERR_ConvoyTask task, vector destination, ERR_HQUnit excluded, float now, ERR_ConvoyVehicle required = null)
|
||||
{
|
||||
if (!MountedInterceptTaskAvailable(task)) return null;
|
||||
ref ERR_HQMountedChoice best;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (required && vehicle != required) continue;
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (unit == excluded || unit.Group != vehicle.Group || !MountedLogisticsEligible(unit)
|
||||
|| ConvoyContact(unit, now) || !MountedInterceptStatus(unit, vehicle, now)) continue;
|
||||
ERR_HQWatchEntry own = m_Watch.Find(unit.ForceSourceId);
|
||||
float distance = vector.DistanceXZ(own.Position, destination);
|
||||
if (distance > m_Mode.Settings().Runtime("responseRadiusMetres") || (best && distance >= best.Distance)) continue;
|
||||
ref ERR_HQRoute route = m_Network.Route(unit.Radio, true);
|
||||
if (!route) continue;
|
||||
best = new ERR_HQMountedChoice();
|
||||
best.Unit = unit; best.Task = task; best.Vehicle = vehicle; best.Route = route;
|
||||
best.Destination = destination; best.Distance = distance;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
protected bool HasMountedPairCandidate(float now)
|
||||
{
|
||||
// Cheap original/status gate; no geometry or claims before both roles exist.
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
ERR_HQUnit firstUnit;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|
||||
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered) continue;
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now)
|
||||
|| !MountedInterceptStatus(unit, vehicle, now)) continue;
|
||||
if (firstUnit && firstUnit != unit && firstUnit.Group != unit.Group) return true;
|
||||
firstUnit = unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected ERR_ConvoyInterceptOrder MountedOrder(ERR_HQMountedChoice choice, ERR_HQBelief belief, float now, int token)
|
||||
{
|
||||
ERR_ConvoyInterceptOrder order = new ERR_ConvoyInterceptOrder();
|
||||
order.Token = token; order.Group = choice.Unit.Group; order.SourceId = choice.Task.Receipt.SourceId;
|
||||
order.VehicleIndex = choice.Vehicle.Index; order.Position = choice.Destination;
|
||||
order.BeliefId = belief.BeliefId; order.BeliefRevision = belief.BeliefRevision;
|
||||
order.ReportId = belief.MessageId; order.ObservedAt = belief.ObservedAt;
|
||||
order.IssuedAt = now; order.ExpiresAt = belief.ObservedAt + 120000;
|
||||
return order;
|
||||
}
|
||||
protected void QueueMountedIntercept(ERR_HQMountedChoice choice, ERR_HQMountedChoice partnerChoice, ERR_HQBelief belief, float now)
|
||||
{
|
||||
int members = 1;
|
||||
if (partnerChoice) members = 2;
|
||||
if (m_MountedTokenCount + members > 256) return;
|
||||
ref ERR_ConvoyInterceptOrder order = MountedOrder(choice, belief, now, 400001 + m_MountedTokenCount);
|
||||
ref ERR_ConvoyInterceptOrder partner;
|
||||
if (partnerChoice) partner = MountedOrder(partnerChoice, belief, now, 400001 + m_MountedTokenCount);
|
||||
if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) return;
|
||||
if (partner && !ERR_HQMountedInterceptRules.Current(partner, belief, now)) return;
|
||||
// IDs and this revision remain spent after refusal; no post-claim fallback.
|
||||
m_MountedTokenCount += members;
|
||||
m_MountedAttemptBeliefs.Insert(order.BeliefId); m_MountedAttemptRevisions.Insert(order.BeliefRevision);
|
||||
choice.Unit.LogisticsReservation = order.Token;
|
||||
if (partnerChoice) partnerChoice.Unit.LogisticsReservation = partner.Token;
|
||||
m_MountedIntercept = order; m_MountedInterceptUnit = choice.Unit; m_MountedInterceptTask = choice.Task;
|
||||
m_MountedInterceptRoute = choice.Route; m_MountedInterceptDelivered = false; m_MountedInterceptReceipt = false;
|
||||
m_MountedInterceptPartner = partner; m_MountedInterceptPartnerReceipt = false;
|
||||
if (partnerChoice) { m_MountedInterceptPartnerUnit = partnerChoice.Unit; m_MountedInterceptPartnerRoute = partnerChoice.Route; }
|
||||
bool admitted;
|
||||
if (partner) admitted = m_Convoys.ReserveInterceptPair(choice.Task, order, partner, now);
|
||||
else admitted = m_Convoys.ReserveIntercept(choice.Task, order, now);
|
||||
if (m_MountedIntercept != order || m_MountedInterceptPartner != partner) return;
|
||||
if (!admitted) { ReleaseMountedIntercept(now, "reservation refused"); return; }
|
||||
choice.Route.MessageId = m_Journal.Message();
|
||||
choice.Route.Payload = string.Format("original escort %1 to road %2; delivered track %3 revision %4 report M%5", order.VehicleIndex, order.Position, order.BeliefId, order.BeliefRevision, order.ReportId);
|
||||
m_Journal.Write("MOUNTED INTERCEPT QUEUED", choice.Route.Payload, choice.Route.MessageId);
|
||||
if (partnerChoice)
|
||||
{
|
||||
partnerChoice.Route.MessageId = m_Journal.Message();
|
||||
partnerChoice.Route.Payload = string.Format("original partner %1 to road %2; report M%3", partner.VehicleIndex, partner.Position, partner.ReportId);
|
||||
m_Journal.Write("MOUNTED INTERCEPT QUEUED", partnerChoice.Route.Payload, partnerChoice.Route.MessageId);
|
||||
}
|
||||
}
|
||||
protected override void PlanMountedIntercept(float now)
|
||||
{
|
||||
if (!Replication.IsServer() || m_Stopping || !MissionActive() || m_MountedIntercept
|
||||
|| m_MountedAttemptBeliefs.Count() >= 256 || m_MountedTokenCount >= 256) return;
|
||||
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
|
||||
int freeGlobal = globalLimit - Assigned();
|
||||
int freeHunt = ERR_HQPursuitRules.Limit(globalLimit) - HuntingAssigned();
|
||||
if (freeGlobal < 1 || freeHunt < 1 || !HasMountedInterceptCandidate(now)) return;
|
||||
bool pairPossible = freeGlobal >= 2 && freeHunt >= 2 && m_MountedTokenCount <= 254 && HasMountedPairCandidate(now);
|
||||
array<vector> reserved = {}; int first = -1; int second = -1;
|
||||
foreach (ERR_HQUnit known : m_Units) ReserveUnit(known, reserved, first, second);
|
||||
int count = m_Memory.Count();
|
||||
if (count == 0) return;
|
||||
int start = m_MountedSurveyCursor % count;
|
||||
int queries;
|
||||
for (int scanned = 0; scanned < count && queries < 2; scanned++)
|
||||
{
|
||||
int b = (start + scanned) % count;
|
||||
m_MountedSurveyCursor = (b + 1) % count;
|
||||
ERR_HQBelief belief = m_Memory.At(b);
|
||||
if (!ERR_HQMountedInterceptRules.Eligible(belief, now) || MountedInterceptAttempted(belief.BeliefId, belief.BeliefRevision)) continue;
|
||||
if (pairPossible)
|
||||
{
|
||||
vector positionA; vector positionB; queries++;
|
||||
if (ERR_HQMountedInterceptRules.SelectPair(belief, now, reserved, positionA, positionB))
|
||||
{
|
||||
ref ERR_HQMountedChoice bestA; ref ERR_HQMountedChoice bestB;
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ERR_ConvoyTask task = m_Convoys.At(t);
|
||||
if (!MountedInterceptTaskAvailable(task)) continue;
|
||||
// Enumerate the bounded original escorts: a greedy nearest first crew
|
||||
// can be the only crew within range of the other road position.
|
||||
foreach (ERR_ConvoyVehicle firstVehicle : task.Vehicles)
|
||||
{
|
||||
if (!firstVehicle || (firstVehicle.Index != 0 && firstVehicle.Index != 3 && firstVehicle.Index != 5)) continue;
|
||||
ref ERR_HQMountedChoice candidateA = MountedChoice(task, positionA, null, now, firstVehicle);
|
||||
if (!candidateA) continue;
|
||||
ref ERR_HQMountedChoice candidateB = MountedChoice(task, positionB, candidateA.Unit, now);
|
||||
if (!candidateB || candidateA.Unit.Group == candidateB.Unit.Group || candidateA.Route == candidateB.Route) continue;
|
||||
if (bestA && candidateA.Distance + candidateB.Distance >= bestA.Distance + bestB.Distance) continue;
|
||||
bestA = candidateA; bestB = candidateB;
|
||||
}
|
||||
}
|
||||
if (bestA) { QueueMountedIntercept(bestA, bestB, belief, now); return; }
|
||||
}
|
||||
}
|
||||
// Only pre-admission fallback, sharing the SAME two-survey pass budget.
|
||||
if (queries >= 2) continue;
|
||||
vector destination; queries++;
|
||||
if (!ERR_HQMountedInterceptRules.Select(belief, now, reserved, destination)) continue;
|
||||
ref ERR_HQMountedChoice best;
|
||||
for (int t = 0; t < m_Convoys.Count(); t++)
|
||||
{
|
||||
ref ERR_HQMountedChoice candidate = MountedChoice(m_Convoys.At(t), destination, null, now);
|
||||
if (candidate && (!best || candidate.Distance < best.Distance)) best = candidate;
|
||||
}
|
||||
if (best) { QueueMountedIntercept(best, null, belief, now); return; }
|
||||
}
|
||||
}
|
||||
override void Stop()
|
||||
{
|
||||
float now;
|
||||
if (GetGame() && GetGame().GetWorld()) now = GetGame().GetWorld().GetWorldTime();
|
||||
ReleaseMountedIntercept(now, "mission stopped");
|
||||
super.Stop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
check: /tmp/err-policy-ta4elk_u/check.cpp:427: int main(): Assertion `!f.h.m_MountedIntercept&&!f.h.m_MountedInterceptPartner&&!f.unit.LogisticsReservation&&!f.unit2.LogisticsReservation&&f.service.releases==2' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-pair-hq-0054/tests/test_hq_mounted_dispatch.py", line 25, in <module>
|
||||
run(r'''
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<135 lines>...
|
||||
}
|
||||
^
|
||||
''')
|
||||
^^^^
|
||||
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
|
||||
subprocess.run([str(p/'check')],check=True)
|
||||
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/usr/lib/python3.14/subprocess.py", line 578, in run
|
||||
raise CalledProcessError(retcode, process.args,
|
||||
output=stdout, stderr=stderr)
|
||||
subprocess.CalledProcessError: Command '['/tmp/err-policy-ta4elk_u/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1,19 @@
|
||||
check: /tmp/err-policy-5ef2aq9f/check.cpp:425: int main(): Assertion `f.h.Assigned()==2&&f.h.HuntingAssigned()==2' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-pair-hq-0054/tests/test_hq_mounted_dispatch.py", line 25, in <module>
|
||||
run(r'''
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<135 lines>...
|
||||
}
|
||||
^
|
||||
''')
|
||||
^^^^
|
||||
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
|
||||
subprocess.run([str(p/'check')],check=True)
|
||||
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/usr/lib/python3.14/subprocess.py", line 578, in run
|
||||
raise CalledProcessError(retcode, process.args,
|
||||
output=stdout, stderr=stderr)
|
||||
subprocess.CalledProcessError: Command '['/tmp/err-policy-5ef2aq9f/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1,19 @@
|
||||
check: /tmp/err-policy-ui4sap8x/check.cpp:426: int main(): Assertion `f.h.m_MountedInterceptReceipt&&!f.h.m_MountedInterceptPartnerReceipt&&!f.h.m_MountedInterceptDelivered&&f.service.pairApplies==0&&f.service.applies==0' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-pair-hq-0054/tests/test_hq_mounted_dispatch.py", line 25, in <module>
|
||||
run(r'''
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<135 lines>...
|
||||
}
|
||||
^
|
||||
''')
|
||||
^^^^
|
||||
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
|
||||
subprocess.run([str(p/'check')],check=True)
|
||||
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/usr/lib/python3.14/subprocess.py", line 578, in run
|
||||
raise CalledProcessError(retcode, process.args,
|
||||
output=stdout, stderr=stderr)
|
||||
subprocess.CalledProcessError: Command '['/tmp/err-policy-ui4sap8x/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1,37 @@
|
||||
[
|
||||
{
|
||||
"case": "green",
|
||||
"exit_code": 0,
|
||||
"runtime_assertion": false
|
||||
},
|
||||
{
|
||||
"case": "baseline",
|
||||
"exit_code": 1,
|
||||
"runtime_assertion": true
|
||||
},
|
||||
{
|
||||
"case": "receipt-barrier",
|
||||
"exit_code": 1,
|
||||
"runtime_assertion": true
|
||||
},
|
||||
{
|
||||
"case": "partner-count",
|
||||
"exit_code": 1,
|
||||
"runtime_assertion": true
|
||||
},
|
||||
{
|
||||
"case": "partner-claim-cleanup",
|
||||
"exit_code": 1,
|
||||
"runtime_assertion": true
|
||||
},
|
||||
{
|
||||
"case": "token-domain",
|
||||
"exit_code": 1,
|
||||
"runtime_assertion": true
|
||||
},
|
||||
{
|
||||
"case": "survey-budget",
|
||||
"exit_code": 1,
|
||||
"runtime_assertion": true
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": {
|
||||
"baseline_sha256": "e98e931e010124ba567f415690999b96868f32a18edc7c4e1f21c0775b3c095f",
|
||||
"proposal_sha256": "60d0fef83f43eea0501bf5fe46f369556e22acda6d034908b5965d8820b28893"
|
||||
},
|
||||
"tests/test_hq_mounted_dispatch.py": {
|
||||
"baseline_sha256": "2b2ebf810ef0084bad5f4cfaf3d155ecac0f002b82bbd54d9756a9592d364fa9",
|
||||
"proposal_sha256": "50e8973b730c34a54775e7ba5ae2a72c9a6a101bc9b14a1521c4b5c6d401552e"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
check: /tmp/err-policy-h98tuy3y/check.cpp:414: int main(): Assertion `ERR_HQMountedInterceptRules::selects-after==1' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-pair-hq-0054/tests/test_hq_mounted_dispatch.py", line 25, in <module>
|
||||
run(r'''
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<135 lines>...
|
||||
}
|
||||
^
|
||||
''')
|
||||
^^^^
|
||||
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
|
||||
subprocess.run([str(p/'check')],check=True)
|
||||
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/usr/lib/python3.14/subprocess.py", line 578, in run
|
||||
raise CalledProcessError(retcode, process.args,
|
||||
output=stdout, stderr=stderr)
|
||||
subprocess.CalledProcessError: Command '['/tmp/err-policy-h98tuy3y/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1,164 @@
|
||||
"""Execute actual mounted HQ dispatch methods; radio/physical service are adversarial doubles."""
|
||||
from policy_harness import ROOT, body, run
|
||||
from pathlib import Path
|
||||
import os,re
|
||||
P=Path(os.environ.get('ERR_HQ_MOUNTED_DISPATCH_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c'))
|
||||
names=['protected bool MountedMemberCurrent(', 'protected bool MountedMemberReady(', 'protected ERR_HQMountedChoice MountedChoice(', 'protected bool HasMountedPairCandidate(', 'protected ERR_ConvoyInterceptOrder MountedOrder(', 'protected void QueueMountedIntercept(', 'override string Status(', 'protected override int Assigned(', 'protected override int HuntingAssigned(', 'protected override void ReserveUnit(', 'protected bool MountedInterceptAttempted(', 'protected bool MountedInterceptStatus(', 'protected void ReleaseMountedIntercept(', 'protected override void ReleaseUnit(', 'protected override void TickMountedIntercept(', 'protected bool MountedInterceptTaskAvailable(', 'protected bool HasMountedInterceptCandidate(', 'protected override void PlanMountedIntercept(', 'override void Stop(']
|
||||
def cpp(s):
|
||||
s=s.replace('protected ','').replace('override ','').replace('ref ','').replace('out int ','int& ').replace('super.','Base::').replace('null','nullptr').replace('foreach (','for (')
|
||||
for typ in ['ERR_ConvoyInterceptOrder','ERR_HQUnit','ERR_ConvoyTask','ERR_HQRoute','ERR_ConvoyVehicle','ERR_HQWatchEntry','ERR_HQBelief','ERR_HQMountedChoice']:
|
||||
s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
|
||||
s=s.replace('array<vector> reserved','Array<vector> reserved')
|
||||
for typ in ['Replication','ERR_ForceRules','ERR_HQMemoryRules','ERR_HQPursuitRules','ERR_HQMountedInterceptRules','vector','string']:
|
||||
s=s.replace(typ+'.',typ+'::')
|
||||
for var in ['unit','vehicle','entry','order','task','belief','known','best','bestTask','bestVehicle','route','candidateRoute','own','m_Mode','m_Watch','m_Memory','m_Convoys','m_Journal','m_Network','m_MountedIntercept','m_MountedInterceptTask','m_MountedInterceptRoute','required','firstVehicle','partner','partnerUnit','firstUnit','excluded','choice','partnerChoice','bestA','bestB','candidateA','candidateB','candidate','m_MountedInterceptPartner','m_MountedInterceptPartnerUnit','m_MountedInterceptPartnerRoute']:
|
||||
s=re.sub(r'\b'+var+r'\.',var+'->',s)
|
||||
for name in ['Unit','Task','Vehicle','Route','StatusSampler','MountedAmmo','Combat','Receipt']:
|
||||
s=s.replace('->'+name+'.','->'+name+'->')
|
||||
s=s.replace('GetGame().','GetGame()->').replace('GetWorld().','GetWorld()->').replace('Settings().','Settings()->')
|
||||
s=re.sub(r'(ERR_\w+\*) (\w+);',r'\1 \2 = nullptr;',s)
|
||||
s=s.replace('bool applied;', 'bool applied=false;').replace('bool admitted;', 'bool admitted=false;')
|
||||
s=s.replace('int queries;','int queries=0;').replace('float now;','float now=0;')
|
||||
s=s.replace('Array<vector> reserved,','Array<vector>& reserved,')
|
||||
return s
|
||||
methods='\n'.join(cpp(body(P,n)) for n in names if n in P.read_text())
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <sstream>
|
||||
struct string:std::string{using std::string::string;string(std::string s):std::string(s){} template<class...T>static string Format(const char*fmt,T...v){std::ostringstream out;out<<fmt;((out<<"|"<<v),...);return out.str();}};
|
||||
struct vector{float x=0,z=0;static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}bool operator==(vector b)const{return x==b.x&&z==b.z;}};
|
||||
std::ostream&operator<<(std::ostream&o,vector v){return o<<v.x<<","<<v.z;}
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}void Insert(T v){this->push_back(v);}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}};
|
||||
struct Group{};struct Entity{};struct Sampler{bool roster=true,bound=true;bool OwnsRoster(Group*){return roster;}bool ValidateBoundVehicle(Entity*){return bound;}};
|
||||
struct Ammo{bool Complete=true,valid=true;int Weapons=1,Rounds=20,Chambers=0;bool Valid(){return valid;}};
|
||||
struct ERR_HQWatchEntry{bool Closed=false;string SourceId="crew";int MessageId=10,Count=3,Strength=3,Fit=3;float ObservedAt=1000,LastDeliveredAt=2000;vector Position;Ammo*MountedAmmo=nullptr;};
|
||||
struct ERR_HQUnit{struct Group*Group=nullptr;Sampler*StatusSampler=nullptr;string ForceSourceId="crew";int LogisticsReservation=0;bool eligible=true,contact=false,PendingConvoyOrder=false,Downlink=false;Entity*Radio=nullptr;};
|
||||
struct CombatState{bool Held=false;};struct Receipt{string SourceId="convoy";};
|
||||
struct ERR_ConvoyVehicle{struct Group*Group=nullptr;struct Entity*Entity=nullptr;int Index=0,InterceptToken=0,SupplyToken=0;bool Released=false,Arrived=false,Registered=true,SupplyHeld=false;CombatState*Combat=nullptr;};
|
||||
struct ERR_ConvoyTask{bool Stopped=false,Ready=true;struct Receipt*Receipt=nullptr;Array<ERR_ConvoyVehicle*>Vehicles;};
|
||||
struct ERR_ConvoyInterceptOrder{int Token=0,VehicleIndex=0,BeliefId=0,BeliefRevision=0,ReportId=0;struct Group*Group=nullptr;string SourceId;vector Position;float IssuedAt=0,ExpiresAt=0,ObservedAt=0;};
|
||||
struct ERR_HQBelief{int BeliefId=1,BeliefRevision=2,MessageId=20;float ObservedAt=1000;bool eligible=true,select=true;vector destination{100,0};};
|
||||
struct Journal{int rows=0;int Message(){return 200;}void Write(string,string,int=0){rows++;}};
|
||||
struct ERR_HQRoute{int result=0,calls=0,MessageId=0;string Payload;int Advance(float,Journal*){calls++;return result;}};
|
||||
struct Watch{ERR_HQWatchEntry*entry=nullptr,*second=nullptr;int SquadLimit(int n){return n;}ERR_HQWatchEntry*Find(string id){return entry&&entry->SourceId==id?entry:(second&&second->SourceId==id?second:nullptr);}};
|
||||
struct Memory{Array<ERR_HQBelief*>entries;int Count(){return entries.Count();}ERR_HQBelief*At(int n){return entries[n];}ERR_HQBelief*GetById(int n){for(auto b:entries)if(b->BeliefId==n)return b;return nullptr;}};
|
||||
struct Network{std::map<Entity*,ERR_HQRoute*>routes;ERR_HQRoute*route=nullptr;Entity*denied=nullptr;int calls=0;ERR_HQRoute*Route(Entity*radio,bool){calls++;return radio==denied?nullptr:(routes.count(radio)?routes[radio]:route);}};
|
||||
struct Settings{int cap=3,radius=1000;int Runtime(string s){return s=="supportSquadLimit"?cap:radius;}};
|
||||
struct Mode{struct Settings settings;struct Settings*Settings(){return &settings;}};
|
||||
struct Service{Array<ERR_ConvoyTask*>tasks;bool current=true,reserve=true,apply=true;int pairReserves=0,pairApplies=0;std::function<void()>onReserve,onApply;int reserves=0,applies=0,releases=0;std::function<void()>onRelease;
|
||||
bool ReserveInterceptPair(ERR_ConvoyTask*,ERR_ConvoyInterceptOrder*a,ERR_ConvoyInterceptOrder*b,float){pairReserves++;assert(a->Token!=b->Token&&a->Group!=b->Group);if(onReserve)onReserve();return reserve;}
|
||||
bool ApplyInterceptPair(ERR_ConvoyTask*,ERR_ConvoyInterceptOrder*,ERR_ConvoyInterceptOrder*,float){pairApplies++;if(onApply)onApply();return apply;}
|
||||
int Count(){return tasks.Count();}ERR_ConvoyTask*At(int n){return tasks[n];}
|
||||
bool InterceptCurrent(ERR_ConvoyTask*,ERR_ConvoyInterceptOrder*){return current;}
|
||||
bool ReserveIntercept(ERR_ConvoyTask*,ERR_ConvoyInterceptOrder*,float){reserves++;return reserve;}
|
||||
bool ApplyIntercept(ERR_ConvoyTask*,ERR_ConvoyInterceptOrder*,float){applies++;return apply;}
|
||||
void ReleaseIntercept(ERR_ConvoyTask*,ERR_ConvoyInterceptOrder*,float){releases++;if(onRelease)onRelease();}
|
||||
};
|
||||
struct Replication{inline static bool server=true;static bool IsServer(){return server;}};
|
||||
struct ERR_ForceRules{static bool Finite(float n){return std::isfinite(n);}static bool FiniteVector(vector v){return Finite(v.x)&&Finite(v.z);}};
|
||||
struct ERR_HQMemoryRules{static bool DeliveryFresh(float observed,float now){return std::isfinite(observed)&&std::isfinite(now)&&observed>=0&&now>=observed&&now-observed<=120000;}};
|
||||
struct ERR_HQPursuitRules{static int Limit(int n){return std::min(n,3);}};
|
||||
struct ERR_HQMountedInterceptRules{inline static int selects=0,pairSelects=0;inline static bool pairGeometry=true;static bool Eligible(ERR_HQBelief*b,float){return b&&b->eligible;}
|
||||
static bool Current(ERR_ConvoyInterceptOrder*o,ERR_HQBelief*b,float now){return Eligible(b,now)&&o->BeliefId==b->BeliefId&&o->BeliefRevision==b->BeliefRevision&&now>=o->IssuedAt&&now-o->IssuedAt<=30000&&now<o->ExpiresAt;}
|
||||
static bool SelectPair(ERR_HQBelief*b,float,Array<vector>&,vector&a,vector&c){pairSelects++;a=b->destination;c={a.x+60,a.z};return b->select&&pairGeometry;}
|
||||
static bool Select(ERR_HQBelief*b,float,Array<vector>&reserved,vector&dest){selects++;dest=b->destination;return b->select&&!reserved.Contains(dest);}};
|
||||
struct Clock{Clock*GetWorld(){return this;}float GetWorldTime(){return 3000;}}clock_;Clock*GetGame(){return &clock_;}
|
||||
struct Base{virtual string Status(){return "base HQ status";}int assigned=0,hunting=0,releaseUnits=0,stops=0;Array<vector>destinations;virtual int Assigned(){return assigned;}virtual int HuntingAssigned(){return hunting;}
|
||||
virtual void ReserveUnit(ERR_HQUnit*,Array<vector>&r,int&,int&){for(auto p:destinations)r.Insert(p);}virtual void ReleaseUnit(ERR_HQUnit*,float){releaseUnits++;}virtual void Stop(){stops++;}};
|
||||
struct ERR_HQMountedChoice{ERR_HQUnit*Unit=nullptr;ERR_ConvoyTask*Task=nullptr;ERR_ConvoyVehicle*Vehicle=nullptr;ERR_HQRoute*Route=nullptr;vector Destination;float Distance=0;};
|
||||
struct HQ:Base{
|
||||
ERR_ConvoyInterceptOrder*m_MountedIntercept=nullptr;ERR_HQUnit*m_MountedInterceptUnit=nullptr;ERR_ConvoyTask*m_MountedInterceptTask=nullptr;ERR_HQRoute*m_MountedInterceptRoute=nullptr;bool m_MountedInterceptDelivered=false,m_Stopping=false,active=true;
|
||||
ERR_ConvoyInterceptOrder*m_MountedInterceptPartner=nullptr;ERR_HQUnit*m_MountedInterceptPartnerUnit=nullptr;ERR_HQRoute*m_MountedInterceptPartnerRoute=nullptr;
|
||||
bool m_MountedInterceptReceipt=false,m_MountedInterceptPartnerReceipt=false;int m_MountedTokenCount=0;
|
||||
int m_MountedSurveyCursor=0;
|
||||
Array<int>m_MountedAttemptBeliefs,m_MountedAttemptRevisions;Array<ERR_HQUnit*>m_Units;Watch*m_Watch;Mode*m_Mode;Service*m_Convoys;Memory*m_Memory;Journal*m_Journal;Network*m_Network;
|
||||
bool MissionActive(){return active;}bool MountedLogisticsEligible(ERR_HQUnit*u){return u&&u->eligible&&!u->LogisticsReservation;}bool ConvoyContact(ERR_HQUnit*u,float){return u->contact;}
|
||||
'''+methods+r'''
|
||||
};
|
||||
struct Fixture{Group group,otherGroup;Entity entity,radio;Sampler sampler;Ammo ammo;ERR_HQWatchEntry entry;ERR_HQUnit unit;CombatState combat;Receipt receipt;ERR_ConvoyVehicle vehicle,unused;ERR_ConvoyTask task;ERR_HQBelief belief;ERR_HQRoute route;Watch watch;Mode mode;Service service;Memory memory;Journal journal;Network network;HQ h;
|
||||
Fixture(){entry.MountedAmmo=&ammo;unit.Group=&group;unit.Radio=&radio;unit.StatusSampler=&sampler;vehicle.Group=&group;vehicle.Entity=&entity;vehicle.Combat=&combat;unused.Combat=&combat;unused.Released=true;task.Receipt=&receipt;task.Vehicles={&vehicle,&unused,&unused,&unused,&unused,&unused};service.tasks={&task};memory.entries={&belief};watch.entry=&entry;network.route=&route;h.m_Units={&unit};h.m_Watch=&watch;h.m_Mode=&mode;h.m_Convoys=&service;h.m_Memory=&memory;h.m_Journal=&journal;h.m_Network=&network;}
|
||||
void plan(){h.PlanMountedIntercept(3000);}void queued(){plan();assert(h.m_MountedIntercept&&unit.LogisticsReservation==400001);assert(service.reserves==1&&service.applies==0&&route.calls==0);}
|
||||
};
|
||||
struct PairFixture:Fixture{Group group2;Entity entity2,radio2;Sampler sampler2;ERR_HQUnit unit2;ERR_ConvoyVehicle vehicle2;ERR_HQWatchEntry entry2;ERR_HQRoute route2;
|
||||
PairFixture(){unit2=unit;unit2.Group=&group2;unit2.Radio=&radio2;unit2.StatusSampler=&sampler2;unit2.ForceSourceId="partner";entry2=entry;entry2.SourceId="partner";entry2.Position={200,0};watch.second=&entry2;vehicle2=vehicle;vehicle2.Index=3;vehicle2.Group=&group2;vehicle2.Entity=&entity2;task.Vehicles[3]=&vehicle2;h.m_Units.Insert(&unit2);network.routes[&radio2]=&route2;ERR_HQMountedInterceptRules::pairGeometry=true;}
|
||||
void pair(){plan();assert(h.m_MountedIntercept&&h.m_MountedInterceptPartner);assert(h.m_MountedInterceptTask==&task);assert(unit.LogisticsReservation==400001&&unit2.LogisticsReservation==400002);assert(service.pairReserves==1&&service.reserves==0&&service.pairApplies==0);}
|
||||
};
|
||||
int main(){
|
||||
{Fixture f;assert(f.h.Status()=="base HQ status");f.queued();string queued=f.h.Status();assert(queued.find("base HQ status")!=string::npos&&queued.find("|0|queued|20|100,0")!=string::npos);f.route.result=1;f.h.TickMountedIntercept(4000);assert(f.h.Status().find("|0|delivered|20|100,0")!=string::npos);f.h.Stop();assert(f.h.Status()=="base HQ status");}
|
||||
{Fixture f;f.queued();assert(f.h.Assigned()==1&&f.h.HuntingAssigned()==1);Array<vector>r;int a=-1,b=-1;f.h.ReserveUnit(&f.unit,r,a,b);assert(r.Count()==1&&r[0]==f.belief.destination);f.h.PlanMountedIntercept(4000);assert(f.service.reserves==1);f.h.TickMountedIntercept(4000);assert(f.route.calls==1&&f.service.applies==0);f.route.result=1;f.h.TickMountedIntercept(5000);assert(f.service.applies==1&&f.h.m_MountedInterceptDelivered);f.belief.BeliefRevision++;f.h.TickMountedIntercept(6000);assert(f.h.m_MountedInterceptDelivered&&f.service.releases==0);f.h.TickMountedIntercept(121000);assert(!f.h.m_MountedIntercept&&f.unit.LogisticsReservation==0);}
|
||||
// Task-wide logistics and queued sibling downlinks cannot consume a revision or starve a free farther convoy.
|
||||
for(int kind=0;kind<5;kind++){Fixture f,far;far.unit.ForceSourceId=far.entry.SourceId="crew2";far.receipt.SourceId="convoy2";far.entry.Position={-200,0};f.watch.second=&far.entry;f.service.tasks.Insert(&far.task);f.h.m_Units.Insert(&far.unit);ERR_HQUnit sibling; sibling.Group=&f.otherGroup;f.unused.Group=&f.otherGroup;f.h.m_Units.Insert(&sibling);if(kind==0)f.unused.SupplyToken=123;if(kind==1)f.unused.SupplyHeld=true;if(kind==2)sibling.PendingConvoyOrder=true;if(kind==3)sibling.Downlink=true;if(kind==4)sibling.LogisticsReservation=123;f.plan();assert(f.h.m_MountedIntercept&&f.h.m_MountedInterceptUnit==&far.unit&&!f.unit.LogisticsReservation&&far.unit.LogisticsReservation==400001&&f.service.reserves==1);}
|
||||
// A disconnected nearest escort cannot starve the farther reachable original escort.
|
||||
{Fixture f;Group group2;Entity radio2,entity2;ERR_HQUnit u2=f.unit;u2.Group=&group2;u2.Radio=&radio2;u2.ForceSourceId="crew2";ERR_HQWatchEntry entry2=f.entry;entry2.SourceId="crew2";entry2.Position={-200,0};f.watch.second=&entry2;ERR_ConvoyVehicle vehicle2=f.vehicle;vehicle2.Group=&group2;vehicle2.Entity=&entity2;vehicle2.Index=3;f.task.Vehicles[3]=&vehicle2;f.h.m_Units.Insert(&u2);f.network.denied=&f.radio;f.plan();assert(f.h.m_MountedIntercept&&f.h.m_MountedInterceptUnit==&u2&&f.h.m_MountedIntercept->VehicleIndex==3&&f.network.calls>=2&&!f.unit.LogisticsReservation&&u2.LogisticsReservation==400001);}
|
||||
// Invalid world clock must preserve the exact claim for the next finite tick.
|
||||
{Fixture f;f.queued();f.h.TickMountedIntercept(std::nanf(""));assert(f.h.m_MountedIntercept&&f.unit.LogisticsReservation==400001&&f.service.releases==0&&f.route.calls==0);}
|
||||
// No own vehicle/fresh crew evidence means no native road surveys.
|
||||
for(int kind=0;kind<6;kind++){Fixture f;if(kind==0)f.service.tasks.clear();if(kind==1)f.task.Stopped=true;if(kind==2)f.unit.LogisticsReservation=1;if(kind==3)f.entry.Closed=true;if(kind==4)f.sampler.bound=false;if(kind==5)f.combat.Held=true;int before=ERR_HQMountedInterceptRules::selects;f.plan();assert(ERR_HQMountedInterceptRules::selects==before&&f.service.reserves==0);}
|
||||
// Interrupted pending claims never apply physical orders, and the same delivered revision cannot retry.
|
||||
{Fixture f;f.queued();f.route.result=-1;f.h.TickMountedIntercept(4000);assert(!f.h.m_MountedIntercept&&!f.unit.LogisticsReservation&&f.service.applies==0);f.plan();assert(f.service.reserves==1);f.belief.BeliefRevision++;f.plan();assert(f.service.reserves==2&&f.unit.LogisticsReservation==400002);}
|
||||
{Fixture f;f.queued();f.belief.BeliefRevision++;f.route.result=1;f.h.TickMountedIntercept(4000);assert(f.route.calls==0&&f.service.applies==0&&!f.h.m_MountedIntercept);}
|
||||
// Physical validation changes after radio delivery cannot trigger a stale order.
|
||||
for(int kind=0;kind<7;kind++){Fixture f;f.queued();f.route.result=1;if(kind==0)f.ammo.Complete=false;if(kind==1)f.ammo.valid=false;if(kind==2)f.sampler.bound=false;if(kind==3)f.entry.Fit--;if(kind==4)f.entry.LastDeliveredAt=9000;if(kind==5)f.unit.contact=true;if(kind==6)f.service.apply=false;f.h.TickMountedIntercept(4000);assert(!f.h.m_MountedIntercept&&!f.unit.LogisticsReservation);assert(f.service.applies==(kind==6?1:0));}
|
||||
// Conditional release and recursive unregister preserve unrelated reservations.
|
||||
{Fixture f;f.queued();f.unit.LogisticsReservation=123;f.service.onRelease=[&]{assert(!f.h.m_MountedIntercept);f.h.ReleaseUnit(&f.unit,4000);};f.h.TickMountedIntercept(4000);assert(f.unit.LogisticsReservation==123&&f.service.releases==1&&f.h.releaseUnits==1);}
|
||||
{Fixture f;f.queued();f.h.Stop();assert(!f.h.m_MountedIntercept&&!f.unit.LogisticsReservation&&f.service.releases==1&&f.h.stops==1&&f.service.applies==0);}
|
||||
// Ordinary assigned and pursuit capacities reserve space before any road query or radio action.
|
||||
for(int kind=0;kind<3;kind++){Fixture f;if(kind==0)f.mode.settings.cap=0;if(kind==1)f.h.assigned=3;if(kind==2)f.h.hunting=3;int before=ERR_HQMountedInterceptRules::selects;f.plan();assert(!f.h.m_MountedIntercept&&f.service.reserves==0&&ERR_HQMountedInterceptRules::selects==before);}
|
||||
{Fixture f;f.queued();f.h.assigned=3;f.h.TickMountedIntercept(4000);assert(!f.h.m_MountedIntercept&&f.service.applies==0);}
|
||||
// Admission is based on detached delivered position, not physical entity telemetry; stock/crew/roles guard it.
|
||||
for(int kind=0;kind<14;kind++){Fixture f;if(kind==0)f.vehicle.Index=1;if(kind==1)f.vehicle.Index=2;if(kind==2)f.vehicle.Arrived=true;if(kind==3)f.vehicle.SupplyToken=1;if(kind==4)f.combat.Held=true;if(kind==5)f.unit.LogisticsReservation=300001;if(kind==6)f.sampler.roster=false;if(kind==7)f.entry.Position={2000,0};if(kind==8)f.entry.ObservedAt=4000;if(kind==9)f.entry.LastDeliveredAt=0;if(kind==10)f.ammo.Weapons=0;if(kind==11)f.ammo.Rounds=0;if(kind==12)f.entry.Count=2;if(kind==13)f.entry.Closed=true;f.plan();assert(!f.h.m_MountedIntercept&&f.service.reserves==0);}
|
||||
{Fixture f;f.service.reserve=false;f.plan();assert(!f.unit.LogisticsReservation&&!f.h.m_MountedIntercept);f.service.reserve=true;f.plan();assert(f.service.reserves==1);}
|
||||
{Fixture f;for(int i=0;i<256;i++){f.h.m_MountedAttemptBeliefs.Insert(i+20);f.h.m_MountedAttemptRevisions.Insert(1);}f.plan();assert(!f.h.m_MountedIntercept&&f.service.reserves==0);}
|
||||
{Fixture f;ERR_HQBelief b2,b3;f.belief.select=b2.select=false;b2.BeliefId=2;b3.BeliefId=3;f.memory.entries={&f.belief,&b2,&b3};int before=ERR_HQMountedInterceptRules::selects;f.plan();assert(ERR_HQMountedInterceptRules::selects-before==2&&f.service.reserves==0);assert(f.h.m_MountedAttemptBeliefs.empty());int after=ERR_HQMountedInterceptRules::selects;f.h.PlanMountedIntercept(8000);assert(ERR_HQMountedInterceptRules::selects-after==1);assert(f.h.m_MountedIntercept&&f.h.m_MountedIntercept->BeliefId==3&&f.service.reserves==1);}
|
||||
// Cursor normalization handles track expiry/removal without out-of-range access.
|
||||
{Fixture f;ERR_HQBelief second;second.BeliefId=2;f.belief.select=second.select=false;f.memory.entries={&f.belief,&second};f.plan();assert(!f.h.m_MountedIntercept);f.h.m_MountedSurveyCursor=31;f.memory.entries={&f.belief};f.belief.select=true;f.h.PlanMountedIntercept(8000);assert(f.h.m_MountedIntercept&&f.h.m_MountedIntercept->BeliefId==1);}
|
||||
// Skipped ineligible/already-attempted tracks do not spend the two-road-query budget.
|
||||
{Fixture f;ERR_HQBelief second,third;second.BeliefId=2;third.BeliefId=3;f.belief.eligible=false;f.h.m_MountedAttemptBeliefs.Insert(2);f.h.m_MountedAttemptRevisions.Insert(second.BeliefRevision);f.memory.entries={&f.belief,&second,&third};int before=ERR_HQMountedInterceptRules::selects;f.plan();assert(ERR_HQMountedInterceptRules::selects-before==1&&f.h.m_MountedIntercept&&f.h.m_MountedIntercept->BeliefId==3);}
|
||||
// No eligible reports and no reports both produce zero native road queries.
|
||||
{Fixture f;f.belief.eligible=false;int before=ERR_HQMountedInterceptRules::selects;f.plan();f.memory.entries.clear();f.plan();assert(ERR_HQMountedInterceptRules::selects==before&&!f.h.m_MountedIntercept&&f.service.reserves==0);}
|
||||
// Full bounded memory: every pass has at most two surveys, no failed revision is consumed.
|
||||
{Fixture f;ERR_HQBelief beliefs[64];f.memory.entries.clear();for(int i=0;i<64;i++){beliefs[i].BeliefId=i+1;beliefs[i].select=false;f.memory.entries.Insert(&beliefs[i]);}for(int pass=0;pass<32;pass++){int before=ERR_HQMountedInterceptRules::selects;float now=3000+pass*5000;f.entry.ObservedAt=f.entry.LastDeliveredAt=now;f.h.PlanMountedIntercept(now);assert(ERR_HQMountedInterceptRules::selects-before==2&&f.h.m_MountedSurveyCursor==((pass+1)*2)%64&&f.h.m_MountedAttemptBeliefs.empty());}assert(!f.h.m_MountedIntercept&&f.service.reserves==0);}
|
||||
|
||||
// Paired admission counts and reserves both original slots; one receipt never detours either.
|
||||
{PairFixture f;f.pair();assert(f.h.Assigned()==2&&f.h.HuntingAssigned()==2);Array<vector>r;int a=-1,b=-1;f.h.ReserveUnit(&f.unit,r,a,b);f.h.ReserveUnit(&f.unit2,r,a,b);assert(r.Count()==2&&vector::DistanceXZ(r[0],r[1])>=40);assert(f.h.Status().find("Paired")!=string::npos);
|
||||
f.route.result=1;f.h.TickMountedIntercept(4000);assert(f.h.m_MountedInterceptReceipt&&!f.h.m_MountedInterceptPartnerReceipt&&!f.h.m_MountedInterceptDelivered&&f.service.pairApplies==0&&f.service.applies==0);f.h.TickMountedIntercept(4500);assert(f.route.calls==1&&f.route2.calls==2&&f.h.m_MountedInterceptRoute==&f.route&&f.h.m_MountedInterceptPartnerRoute==&f.route2);
|
||||
f.route2.result=1;f.h.TickMountedIntercept(5000);assert(f.service.pairApplies==1&&f.h.m_MountedInterceptDelivered&&f.h.m_MountedIntercept->ExpiresAt==121000&&f.h.m_MountedInterceptPartner->ExpiresAt==121000);f.h.TickMountedIntercept(6000);assert(f.service.pairApplies==1);f.h.TickMountedIntercept(121000);assert(!f.h.m_MountedIntercept&&!f.h.m_MountedInterceptPartner&&!f.unit.LogisticsReservation&&!f.unit2.LogisticsReservation&&f.service.releases==2);}
|
||||
// Either receipt may arrive first; interrupted/expired/revised/changed ownership never creates half a pair.
|
||||
for(int kind=0;kind<9;kind++){PairFixture f;f.pair();f.route2.result=1;f.h.TickMountedIntercept(4000);assert(f.h.m_MountedInterceptPartnerReceipt&&f.service.pairApplies==0);float now=5000;
|
||||
if(kind==0)f.route.result=-1;
|
||||
if(kind==1)now=121000;
|
||||
if(kind==2)f.belief.BeliefRevision++;
|
||||
if(kind==3)f.unit2.LogisticsReservation=999;
|
||||
if(kind==4)f.unit2.contact=true;
|
||||
if(kind==5)f.entry2.Fit--;
|
||||
if(kind==6)f.sampler2.bound=false;
|
||||
if(kind==7)f.h.assigned=2;
|
||||
if(kind==8)now=34000;
|
||||
if(kind!=0)f.route.result=1;
|
||||
f.h.TickMountedIntercept(now);assert(!f.h.m_MountedIntercept&&!f.h.m_MountedInterceptPartner&&f.service.pairApplies==0&&f.service.applies==0&&!f.unit.LogisticsReservation);assert(f.unit2.LogisticsReservation==(kind==3?999:0));}
|
||||
// Native preparation failure settles BOTH claims. No retry or singleton replacement after commitment.
|
||||
{PairFixture f;f.pair();f.route.result=f.route2.result=1;f.service.apply=false;f.h.TickMountedIntercept(5000);assert(f.service.pairApplies==1&&!f.h.m_MountedIntercept&&!f.unit.LogisticsReservation&&!f.unit2.LogisticsReservation);f.plan();assert(f.service.pairReserves==1&&f.service.reserves==0);}
|
||||
{PairFixture f;f.service.reserve=false;f.plan();assert(f.service.pairReserves==1&&f.service.reserves==0&&!f.unit.LogisticsReservation&&!f.unit2.LogisticsReservation&&f.h.m_MountedTokenCount==2);f.service.reserve=true;f.plan();assert(f.service.pairReserves==1);f.belief.BeliefRevision++;f.plan();assert(f.h.m_MountedIntercept->Token==400003&&f.h.m_MountedInterceptPartner->Token==400004);}
|
||||
// Stop/reentrant unregister detach both owned handles before external release; unrelated claims survive.
|
||||
{PairFixture f;f.pair();f.unit2.LogisticsReservation=999;f.service.onRelease=[&]{assert(!f.h.m_MountedIntercept&&!f.h.m_MountedInterceptPartner);f.h.ReleaseUnit(&f.unit2,5000);};f.h.Stop();f.h.Stop();assert(f.service.releases==2&&!f.unit.LogisticsReservation&&f.unit2.LogisticsReservation==999);}
|
||||
{PairFixture f;f.pair();f.route.result=f.route2.result=1;f.service.onApply=[&]{f.h.Stop();};f.h.TickMountedIntercept(5000);assert(!f.h.m_MountedIntercept&&!f.h.m_MountedInterceptDelivered&&!f.unit.LogisticsReservation&&!f.unit2.LogisticsReservation);}
|
||||
// Two free slots and two IDs are required for pair. Singleton fallback occurs only before claims.
|
||||
for(int kind=0;kind<5;kind++){PairFixture f;if(kind==0)f.h.assigned=2;if(kind==1)f.h.hunting=2;if(kind==2)f.h.m_MountedTokenCount=255;if(kind==3)f.network.denied=&f.radio2;if(kind==4)ERR_HQMountedInterceptRules::pairGeometry=false;int before=ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects;f.plan();assert(f.h.m_MountedIntercept&&!f.h.m_MountedInterceptPartner&&f.service.reserves==1&&f.service.pairReserves==0);assert(ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects-before<=2);}
|
||||
{PairFixture f;f.h.m_MountedTokenCount=256;int before=ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects;f.plan();assert(!f.h.m_MountedIntercept&&ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects==before);}
|
||||
// A valid reversed assignment must win over greedy singleton fallback (same bounded road survey).
|
||||
{PairFixture f;f.mode.settings.radius=800;f.belief.destination={0,0};f.entry.Position={0,0};f.entry2.Position={-800,0};int before=ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects;f.plan();assert(f.h.m_MountedInterceptPartner&&f.h.m_MountedInterceptUnit==&f.unit2&&f.h.m_MountedInterceptPartnerUnit==&f.unit);assert(ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects-before==1);}
|
||||
// Stop during reservation cannot resurrect a job after external callback returns.
|
||||
{PairFixture f;f.service.onReserve=[&]{f.h.Stop();};f.plan();assert(!f.h.m_MountedIntercept&&!f.h.m_MountedInterceptPartner&&!f.unit.LogisticsReservation&&!f.unit2.LogisticsReservation&&f.service.releases==2);}
|
||||
// No mixed-convoy pair; both geometric failures still cost at most two queries, no attempts/tokens.
|
||||
{PairFixture f;ERR_ConvoyTask other=f.task;other.Vehicles[0]=&f.unused;f.task.Vehicles[3]=&f.unused;f.service.tasks.Insert(&other);f.plan();assert(f.h.m_MountedIntercept&&!f.h.m_MountedInterceptPartner&&f.service.pairReserves==0);}
|
||||
{PairFixture f;ERR_HQBelief b2;b2.BeliefId=2;f.belief.select=b2.select=false;f.memory.entries.Insert(&b2);int before=ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects;f.plan();assert(ERR_HQMountedInterceptRules::pairSelects+ERR_HQMountedInterceptRules::selects-before==2&&!f.h.m_MountedIntercept&&f.h.m_MountedAttemptBeliefs.empty()&&f.h.m_MountedTokenCount==0);}
|
||||
|
||||
}
|
||||
''')
|
||||
print('actual mounted HQ dispatch planning, radio, ownership and lifecycle checks passed')
|
||||
@@ -0,0 +1,19 @@
|
||||
check: /tmp/err-policy-se_bpcjv/check.cpp:31: bool Service::ReserveInterceptPair(ERR_ConvoyTask*, ERR_ConvoyInterceptOrder*, ERR_ConvoyInterceptOrder*, float): Assertion `a->Token!=b->Token&&a->Group!=b->Group' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-pair-hq-0054/tests/test_hq_mounted_dispatch.py", line 25, in <module>
|
||||
run(r'''
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<135 lines>...
|
||||
}
|
||||
^
|
||||
''')
|
||||
^^^^
|
||||
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
|
||||
subprocess.run([str(p/'check')],check=True)
|
||||
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/usr/lib/python3.14/subprocess.py", line 578, in run
|
||||
raise CalledProcessError(retcode, process.args,
|
||||
output=stdout, stderr=stderr)
|
||||
subprocess.CalledProcessError: Command '['/tmp/err-policy-se_bpcjv/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1,24 @@
|
||||
from pathlib import Path
|
||||
import subprocess,os,json
|
||||
root=Path('/tmp/erri-pair-hq-0054');rel=Path('addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c');source=(root/rel).read_text()
|
||||
env=dict(os.environ,PYTHONDONTWRITEBYTECODE='1',PYTHONPATH='/home/jakub/devel/escapistsrereintroduced/tests')
|
||||
cases={'green':root/rel,'baseline':root/'baseline'/rel}
|
||||
mutations={
|
||||
'receipt-barrier':(' if (!m_MountedInterceptReceipt || (partner && !m_MountedInterceptPartnerReceipt)) return;', ' if (!m_MountedInterceptReceipt) return;'),
|
||||
'partner-count':(' if (m_MountedInterceptPartner) count++;',' // mutation: partner not counted'),
|
||||
'partner-claim-cleanup':(' if (partner && partnerUnit && partnerUnit.LogisticsReservation == partner.Token) partnerUnit.LogisticsReservation = 0;', ' if (partner && partnerUnit && partnerUnit.LogisticsReservation == partner.Token) partnerUnit.LogisticsReservation = partner.Token;'),
|
||||
'token-domain':('400002 + m_MountedTokenCount','400001 + m_MountedTokenCount'),
|
||||
'survey-budget':('scanned < count && queries < 2','scanned < count && queries < 3')
|
||||
}
|
||||
for name,(old,new) in mutations.items():
|
||||
assert old in source
|
||||
p=root/'mutations'/name/rel;p.parent.mkdir(parents=True,exist_ok=True);p.write_text(source.replace(old,new));cases[name]=p
|
||||
results=[]
|
||||
for name,p in cases.items():
|
||||
env['ERR_HQ_MOUNTED_DISPATCH_SOURCE']=str(p)
|
||||
with (root/(name+'.log')).open('w') as log:r=subprocess.run(['python3',str(root/'tests/test_hq_mounted_dispatch.py')],env=env,stdout=log,stderr=subprocess.STDOUT)
|
||||
text=(root/(name+'.log')).read_text();runtime='Assertion' in text or 'assertion' in text
|
||||
results.append({'case':name,'exit_code':r.returncode,'runtime_assertion':runtime})
|
||||
if name=='green':assert r.returncode==0
|
||||
else: assert r.returncode!=0 and runtime,(name,text[:500])
|
||||
(root/'results.json').write_text(json.dumps(results,indent=2)+'\n');print(json.dumps(results))
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c": "b59016d83cb9d40136b98b4e0067ea490bc553574fc53603593efd5522dbed20",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c": "a37edcf66e43945f331db47fe81cb2e0e767a7aaf8115244dcf9e6295932be98",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": "cee3ae301ac2f211c290450b644a7942c9786a2a40d06f16e2a47efa96fd15b6",
|
||||
"tests/test_hq_mounted_intercept_pair.py": "a8d758b1ff20bb6d1a95a9ff1d38f687e43c819c279f5ea9692e84f766dc4fb9",
|
||||
"tests/test_convoy_intercept_pair.py": "52b55727b7c2441d43530efa701045b9439b9175c71fbd2ae7577f851d2c98ec",
|
||||
"tests/test_hq_mounted_dispatch.py": "3eb793719b21d508ffc294f5a1bf5c5153f5717a1408a8f779f0a4c1ddefe2a6",
|
||||
"tests/test_convoy_column.py": "8c5de7616d4f8c3ed15bcc29bbb57f04dcaaca0626683b81547e2170fc5f9a62",
|
||||
"tools/check.py": "53727a193b89ec4682abfcc429fa73b49cda279510e4747bb91803684d66b342",
|
||||
"docs/mounted-interception.md": "20f80f34919c32535c1d57da7894ecadee6699f7f17b1993a32bed5837342bec"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
actual mounted HQ dispatch planning, radio, ownership and lifecycle checks passed
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": {
|
||||
"before": "60d0fef83f43eea0501bf5fe46f369556e22acda6d034908b5965d8820b28893",
|
||||
"after": "cee3ae301ac2f211c290450b644a7942c9786a2a40d06f16e2a47efa96fd15b6"
|
||||
},
|
||||
"tests/test_hq_mounted_dispatch.py": {
|
||||
"before": "50e8973b730c34a54775e7ba5ae2a72c9a6a101bc9b14a1521c4b5c6d401552e",
|
||||
"after": "3eb793719b21d508ffc294f5a1bf5c5153f5717a1408a8f779f0a4c1ddefe2a6"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
Test audio routed to silent output and muted (stream 732089)
|
||||
{
|
||||
"stopped_unix": 1790982926.9491527,
|
||||
"console_sha256": "6e8f18f79fc5a2835fe76046981374a85a87e6f9d21ab565875209c68b3dc5eb",
|
||||
"end_source_sha256": "d7b2a8a4c7bdca7cd78a23747c3190b0f1c41a36c16bb9d14490bdc40792e648",
|
||||
"source_unchanged_after_run": true,
|
||||
"source_provenance": "launch_snapshot",
|
||||
"launch_id": "8d9f5c42-b18c-46f3-9801-d0edb54c9c33",
|
||||
"launch_source_manifest": "launch-source-sha256.json",
|
||||
"launch_source_metadata": "launch-source.json",
|
||||
"launch_source_sha256": "d7b2a8a4c7bdca7cd78a23747c3190b0f1c41a36c16bb9d14490bdc40792e648",
|
||||
"external_addons_unchanged_after_run": true,
|
||||
"external_addon_count": 7,
|
||||
"external_addon_inputs": "external-addons-inputs.json",
|
||||
"started_unix": 1790982743.645451,
|
||||
"result": "smoke-completed",
|
||||
"result_before_cleanup": "smoke-completed",
|
||||
"cleanup_confirmed": true,
|
||||
"cleanup_report": {
|
||||
"cleanup_confirmed": true,
|
||||
"idle_verified": true,
|
||||
"errors": [],
|
||||
"orphan_cleanup": {
|
||||
"attempted": true,
|
||||
"report": {
|
||||
"scope": "/tmp/erri-headless-compat/pfx",
|
||||
"candidates": [],
|
||||
"term_sent": [],
|
||||
"kill_sent": [],
|
||||
"cleared": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"runner_error": null,
|
||||
"session": "/tmp/erri-pair-normal-fixed-0054/logs/logs_2026-10-03_01-12-25",
|
||||
"rounds_required": 1,
|
||||
"project": "chat",
|
||||
"display": "headless Gamescope 1280x720/30Hz",
|
||||
"dedicated_server": false,
|
||||
"audio": "silent sink and per-test-stream mute"
|
||||
}
|
||||
{
|
||||
"log": "/tmp/erri-pair-normal-fixed-0054/logs/logs_2026-10-03_01-12-25/console.log",
|
||||
"smoke_completions": 1,
|
||||
"assertions_passed": 63,
|
||||
"script_or_runtime_failures": [],
|
||||
"engine_errors": [
|
||||
"01:12:27.205 RESOURCES (E): Wrong GUID/name for resource @\"{0AFDD005CD669892}Language/rhs_localization.st\" in property \"StringTableSource\"",
|
||||
"01:12:27.205 RESOURCES (E): Wrong GUID/name for resource @\"{41FA2A08B6971D4C}Language/wcs_armament_localization.st\" in property \"StringTableSource\"",
|
||||
"01:12:27.206 RESOURCES (E): Wrong GUID/name for resource @\"{84270E09F7A4A7A0}Language/wcs_spacecore_localization.st\" in property \"StringTableSource\"",
|
||||
"01:12:27.206 RESOURCES (E): Wrong GUID/name for resource @\"{E50E557B468C7073}Language/wcs_bmp1_localization.st\" in property \"StringTableSource\"",
|
||||
"01:13:09.482 WORLD (E): Unknown keyword/data 'Parent' at offset 35955(0x8c73)",
|
||||
"01:13:09.482 WORLD (E): Unknown keyword/data 'Parent' at offset 36915(0x9033)",
|
||||
"01:13:09.483 WORLD (E): Unknown keyword/data 'Parent' at offset 42646(0xa696)",
|
||||
"01:13:09.483 WORLD (E): Unknown keyword/data 'Parent' at offset 43645(0xaa7d)",
|
||||
"01:13:09.483 WORLD (E): Unknown keyword/data 'Parent' at offset 44617(0xae49)",
|
||||
"01:13:09.483 WORLD (E): Unknown keyword/data 'Parent' at offset 45590(0xb216)",
|
||||
"01:13:09.609 WORLD (E): Unknown keyword/data 'SlidingTrackMaterial' at offset 19441(0x4bf1)",
|
||||
"01:13:16.026 WORLD (E): Unknown keyword/data 'm_HardLandingVelocity' at offset 133(0x85)",
|
||||
"01:13:16.026 WORLD (E): Unknown keyword/data 'm_DeathLandingVelocity' at offset 163(0xa3)"
|
||||
],
|
||||
"missing_end_screen": [],
|
||||
"scope": "scripted local Workbench integration only; no multiplayer, piloting or FPS claim"
|
||||
}
|
||||
@@ -0,0 +1,720 @@
|
||||
PASS: actual objective overlay lifecycle preserves separate ownership, late readiness/reopen, data refresh, pan/zoom/culling, supported sibling/shared-frame parents, coordinate offsets, native task-clear isolation and terminal cleanup; native rendering remains unverified
|
||||
PASS: actual map element initializes each widget, retries missing widgets, updates position and changes only stale text/icon data; no native image-load claim
|
||||
PASS: actual native fixture root/config ownership and idempotent cleanup preserve foreign map, selector excludes ordinary route, fixed budgets/screenshots/UGC boundaries retained; native operations not executed
|
||||
PASS: actual full Inspect rejects canvas parenting, wrong active container/canvas, omitted origin offset, wrong rendered box and stale coordinates; differing origins and DPI accepted
|
||||
PASS: actual long-warning phases retain visible label through1500ms checkpoint then restore radio before close; synthetic marker separated from unmoved player
|
||||
PASS: actual Center places marker100screenpx left of pan center at both fixture zooms without altering disclosed coordinates
|
||||
PASS: missing, child-only, malformed, zero and duplicate prefab root IDs rejected
|
||||
PASS: delayed scene/inventory arrival, deferred preload, identity checks, stale replies, timeout and observer cleanup
|
||||
PASS: actual inventory manifest, delayed/missing/wrong-owner item links, finite cap, consumed-stock refresh and preload ordering
|
||||
PASS: spawn scene remains held through final acknowledgement and nearby use; leaving, reassignment, disconnect and teardown release it
|
||||
PASS: production prison AI hold/release, repeat activation, authority/faction/player exclusion, dead actor and stop cleanup
|
||||
PASS: actual protected/capped damage plus exact-actor terminal fixture bypass through delayed native damage, fail-closed ownership and untouched ordinary actor; no native claim
|
||||
PASS: actual full planner selection/commit body, 360 equivalence scenarios, orders/reservations/cooldowns identical; one128-unit scan vs >10000 repeated unit visits; no FPS claim
|
||||
PASS: actual observer monitor/recovery/cleanup rejects retained old controller, preserves new camera/editor/hints/timer, restarts once and retains normal recovery; native effects modeled from SDK, no runtime claim
|
||||
PASS: former chat controller releases only its own callbacks and preserves replacement menu; current-owner stop/scope and repeated teardown remain intact
|
||||
PASS: wrong observer fixture selector rejected before runtime/profile setup; no engine launched
|
||||
PASS: actual depot radio/planning/two-leg state transitions, fixed deadline, late proof quarantine, finite import and no adoption after Stop
|
||||
PASS: actual depot HQ lifecycle, existing planner priority, server/mode/owner gates, idempotent polling and owner replacement
|
||||
PASS: actual full depot leaf, exact custody, isolated callbacks, finite reservation, false/unknown and Stop quarantine, once-only delivery and caps; native movement unqualified
|
||||
PASS: actual authored identity capture, READY-only detached provenance, same shared stock admission, completed exact two-leg proof, lifetime caps and Stop no late adoption
|
||||
usage: test_native_mounted_intercept.py [-h] [--steam STEAM]
|
||||
[--external-addons EXTERNAL_ADDONS]
|
||||
--compat COMPAT --profile PROFILE
|
||||
[--smoke {normal-escape-route,escape-route,optional-site-skip,infantry-combat,medical-combat,hunter-faction,unconscious-camera,objective-map,town-garrisons,site-garrisons,radio-interaction,enemy-loadout,aggression,air-logistics,air-assault,air-recon,airfield-markers,opening-tools,mortar-station,generated-sites,hq-local-handoff,infantry-transfer,hq-body,convoy,supply-convoys,mounted-ammo,alarm-hearing,hq-memory,hq-awareness,aircraft-field,aircraft-route,native-end-active,native-end-preparing,aircraft-control,aircraft-cancel,aircraft-translate,map-ownership,mortar-refresh,mortar-salvo,mortar-courier,station-teardown,prison-breach,powered-flight,powered-translate,local-admission,mortar-expiry,mortar-transfer,mortar-fire,patrol-hq,victory,lost-helicopter,player-death,radio-network,hq-response,mortar-inventory,startup-failure,site-survey,cache-loss,extraction-capacity,early-helicopter-loss,extraction-startup-failure,extraction-boundary-failure,transport-loss,transport-survey,lifecycle,objective-ui,recovery,prisoner-kit,medical,casualty-load,casualty-direct,casualty-bench,casualty-remove,environment,environment-restart,patrols,radio-document-loss,radio-document-loss-partial,guard-cancel,casualty-service}]
|
||||
[--maintenance-refuel]
|
||||
[--mounted-swap-eligibility]
|
||||
[--mine-stock] [--mounted-courier]
|
||||
[--mounted-intercept] [--convoy-cover]
|
||||
[--field-terrain-audit]
|
||||
[--field-service-cancel]
|
||||
[--route-service-cancel]
|
||||
[--station-teardown-diagnostics]
|
||||
[--flight-autohover]
|
||||
[--flight-action-inputs]
|
||||
[--flight-input-diagnostics]
|
||||
[--recon-takeover-only]
|
||||
[--body-recovery-only]
|
||||
[--rounds ROUNDS] [--timeout TIMEOUT]
|
||||
[--project {core,chat}]
|
||||
test_native_mounted_intercept.py: error: --mounted-intercept requires Chat --smoke convoy, one round and timeout at least900 seconds
|
||||
usage: test_native_mounted_intercept.py [-h] [--steam STEAM]
|
||||
[--external-addons EXTERNAL_ADDONS]
|
||||
--compat COMPAT --profile PROFILE
|
||||
[--smoke {normal-escape-route,escape-route,optional-site-skip,infantry-combat,medical-combat,hunter-faction,unconscious-camera,objective-map,town-garrisons,site-garrisons,radio-interaction,enemy-loadout,aggression,air-logistics,air-assault,air-recon,airfield-markers,opening-tools,mortar-station,generated-sites,hq-local-handoff,infantry-transfer,hq-body,convoy,supply-convoys,mounted-ammo,alarm-hearing,hq-memory,hq-awareness,aircraft-field,aircraft-route,native-end-active,native-end-preparing,aircraft-control,aircraft-cancel,aircraft-translate,map-ownership,mortar-refresh,mortar-salvo,mortar-courier,station-teardown,prison-breach,powered-flight,powered-translate,local-admission,mortar-expiry,mortar-transfer,mortar-fire,patrol-hq,victory,lost-helicopter,player-death,radio-network,hq-response,mortar-inventory,startup-failure,site-survey,cache-loss,extraction-capacity,early-helicopter-loss,extraction-startup-failure,extraction-boundary-failure,transport-loss,transport-survey,lifecycle,objective-ui,recovery,prisoner-kit,medical,casualty-load,casualty-direct,casualty-bench,casualty-remove,environment,environment-restart,patrols,radio-document-loss,radio-document-loss-partial,guard-cancel,casualty-service}]
|
||||
[--maintenance-refuel]
|
||||
[--mounted-swap-eligibility]
|
||||
[--mine-stock] [--mounted-courier]
|
||||
[--mounted-intercept] [--convoy-cover]
|
||||
[--field-terrain-audit]
|
||||
[--field-service-cancel]
|
||||
[--route-service-cancel]
|
||||
[--station-teardown-diagnostics]
|
||||
[--flight-autohover]
|
||||
[--flight-action-inputs]
|
||||
[--flight-input-diagnostics]
|
||||
[--recon-takeover-only]
|
||||
[--body-recovery-only]
|
||||
[--rounds ROUNDS] [--timeout TIMEOUT]
|
||||
[--project {core,chat}]
|
||||
test_native_mounted_intercept.py: error: --mounted-intercept requires Chat --smoke convoy, one round and timeout at least900 seconds
|
||||
usage: test_native_mounted_intercept.py [-h] [--steam STEAM]
|
||||
[--external-addons EXTERNAL_ADDONS]
|
||||
--compat COMPAT --profile PROFILE
|
||||
[--smoke {normal-escape-route,escape-route,optional-site-skip,infantry-combat,medical-combat,hunter-faction,unconscious-camera,objective-map,town-garrisons,site-garrisons,radio-interaction,enemy-loadout,aggression,air-logistics,air-assault,air-recon,airfield-markers,opening-tools,mortar-station,generated-sites,hq-local-handoff,infantry-transfer,hq-body,convoy,supply-convoys,mounted-ammo,alarm-hearing,hq-memory,hq-awareness,aircraft-field,aircraft-route,native-end-active,native-end-preparing,aircraft-control,aircraft-cancel,aircraft-translate,map-ownership,mortar-refresh,mortar-salvo,mortar-courier,station-teardown,prison-breach,powered-flight,powered-translate,local-admission,mortar-expiry,mortar-transfer,mortar-fire,patrol-hq,victory,lost-helicopter,player-death,radio-network,hq-response,mortar-inventory,startup-failure,site-survey,cache-loss,extraction-capacity,early-helicopter-loss,extraction-startup-failure,extraction-boundary-failure,transport-loss,transport-survey,lifecycle,objective-ui,recovery,prisoner-kit,medical,casualty-load,casualty-direct,casualty-bench,casualty-remove,environment,environment-restart,patrols,radio-document-loss,radio-document-loss-partial,guard-cancel,casualty-service}]
|
||||
[--maintenance-refuel]
|
||||
[--mounted-swap-eligibility]
|
||||
[--mine-stock] [--mounted-courier]
|
||||
[--mounted-intercept] [--convoy-cover]
|
||||
[--field-terrain-audit]
|
||||
[--field-service-cancel]
|
||||
[--route-service-cancel]
|
||||
[--station-teardown-diagnostics]
|
||||
[--flight-autohover]
|
||||
[--flight-action-inputs]
|
||||
[--flight-input-diagnostics]
|
||||
[--recon-takeover-only]
|
||||
[--body-recovery-only]
|
||||
[--rounds ROUNDS] [--timeout TIMEOUT]
|
||||
[--project {core,chat}]
|
||||
test_native_mounted_intercept.py: error: --mounted-intercept requires Chat --smoke convoy, one round and timeout at least900 seconds
|
||||
usage: test_native_mounted_intercept.py [-h] [--steam STEAM]
|
||||
[--external-addons EXTERNAL_ADDONS]
|
||||
--compat COMPAT --profile PROFILE
|
||||
[--smoke {normal-escape-route,escape-route,optional-site-skip,infantry-combat,medical-combat,hunter-faction,unconscious-camera,objective-map,town-garrisons,site-garrisons,radio-interaction,enemy-loadout,aggression,air-logistics,air-assault,air-recon,airfield-markers,opening-tools,mortar-station,generated-sites,hq-local-handoff,infantry-transfer,hq-body,convoy,supply-convoys,mounted-ammo,alarm-hearing,hq-memory,hq-awareness,aircraft-field,aircraft-route,native-end-active,native-end-preparing,aircraft-control,aircraft-cancel,aircraft-translate,map-ownership,mortar-refresh,mortar-salvo,mortar-courier,station-teardown,prison-breach,powered-flight,powered-translate,local-admission,mortar-expiry,mortar-transfer,mortar-fire,patrol-hq,victory,lost-helicopter,player-death,radio-network,hq-response,mortar-inventory,startup-failure,site-survey,cache-loss,extraction-capacity,early-helicopter-loss,extraction-startup-failure,extraction-boundary-failure,transport-loss,transport-survey,lifecycle,objective-ui,recovery,prisoner-kit,medical,casualty-load,casualty-direct,casualty-bench,casualty-remove,environment,environment-restart,patrols,radio-document-loss,radio-document-loss-partial,guard-cancel,casualty-service}]
|
||||
[--maintenance-refuel]
|
||||
[--mounted-swap-eligibility]
|
||||
[--mine-stock] [--mounted-courier]
|
||||
[--mounted-intercept] [--convoy-cover]
|
||||
[--field-terrain-audit]
|
||||
[--field-service-cancel]
|
||||
[--route-service-cancel]
|
||||
[--station-teardown-diagnostics]
|
||||
[--flight-autohover]
|
||||
[--flight-action-inputs]
|
||||
[--flight-input-diagnostics]
|
||||
[--recon-takeover-only]
|
||||
[--body-recovery-only]
|
||||
[--rounds ROUNDS] [--timeout TIMEOUT]
|
||||
[--project {core,chat}]
|
||||
test_native_mounted_intercept.py: error: --mounted-intercept requires Chat --smoke convoy, one round and timeout at least900 seconds
|
||||
usage: test_native_mounted_intercept.py [-h] [--steam STEAM]
|
||||
[--external-addons EXTERNAL_ADDONS]
|
||||
--compat COMPAT --profile PROFILE
|
||||
[--smoke {normal-escape-route,escape-route,optional-site-skip,infantry-combat,medical-combat,hunter-faction,unconscious-camera,objective-map,town-garrisons,site-garrisons,radio-interaction,enemy-loadout,aggression,air-logistics,air-assault,air-recon,airfield-markers,opening-tools,mortar-station,generated-sites,hq-local-handoff,infantry-transfer,hq-body,convoy,supply-convoys,mounted-ammo,alarm-hearing,hq-memory,hq-awareness,aircraft-field,aircraft-route,native-end-active,native-end-preparing,aircraft-control,aircraft-cancel,aircraft-translate,map-ownership,mortar-refresh,mortar-salvo,mortar-courier,station-teardown,prison-breach,powered-flight,powered-translate,local-admission,mortar-expiry,mortar-transfer,mortar-fire,patrol-hq,victory,lost-helicopter,player-death,radio-network,hq-response,mortar-inventory,startup-failure,site-survey,cache-loss,extraction-capacity,early-helicopter-loss,extraction-startup-failure,extraction-boundary-failure,transport-loss,transport-survey,lifecycle,objective-ui,recovery,prisoner-kit,medical,casualty-load,casualty-direct,casualty-bench,casualty-remove,environment,environment-restart,patrols,radio-document-loss,radio-document-loss-partial,guard-cancel,casualty-service}]
|
||||
[--maintenance-refuel]
|
||||
[--mounted-swap-eligibility]
|
||||
[--mine-stock] [--mounted-courier]
|
||||
[--mounted-intercept] [--convoy-cover]
|
||||
[--field-terrain-audit]
|
||||
[--field-service-cancel]
|
||||
[--route-service-cancel]
|
||||
[--station-teardown-diagnostics]
|
||||
[--flight-autohover]
|
||||
[--flight-action-inputs]
|
||||
[--flight-input-diagnostics]
|
||||
[--recon-takeover-only]
|
||||
[--body-recovery-only]
|
||||
[--rounds ROUNDS] [--timeout TIMEOUT]
|
||||
[--project {core,chat}]
|
||||
test_native_mounted_intercept.py: error: --mounted-intercept and --convoy-cover are mutually exclusive
|
||||
PASS: actual native interception witness requires real radio cursor/lineage, original actor identities, fixed movement/hold/expiry/resume and immutable deadline; no movement/delivery injection
|
||||
PASS: actual CLI rejects wrong mode/project/rounds/899s/conflict before runtime; valid flag and terminal branch identify synthetic-observation qualification
|
||||
PASS: visible base hook and actual fixture override delegate once; delivery proof uses retained pre-super route only on successful production transition
|
||||
actual mounted HQ dispatch planning, radio, ownership and lifecycle checks passed
|
||||
PASS: actual mounted intercept eligibility/order chronology, fixed destinations, native road selection bounds, width, segment heading, clearance and settlement delegation; no native driving claim
|
||||
PASS: actual paired arc geometry,40m/80m spacing, separate original polylines, both-position validation and128road/4096segment budget; no native driving proof
|
||||
PASS: actual pair admission, rollback, native allocation/revocation boundaries, per-member ownership, hold and expiry
|
||||
PASS: actual Tick clock initialization admits fractional first ticks and expires without extending the original start
|
||||
PASS: actual finite conservation/deadline/callback policies; read-only selection, bounded once-only inventory submission and no can refill; native transfer not executed
|
||||
PASS: actual exact-context membership, bounded identity/reach gates, native start/cache/eligibility ordering and no duplicate/foreign action start; fuel manager identity and empty-can headroom guards checked
|
||||
PASS: actual detached node/value snapshot and conservation refuse mismatched lengths before indexed reads, retain original identities under native out-array aliasing and log bounded counts once; native cause remains unknown
|
||||
PASS: actual mine evidence sealing/recovery, historical coarse publication, caps, canonical ownership, marker join retry/owner-change/stop; native arming and replicated vector-array transport untested
|
||||
PASS: actual full document recovery retains authored/sealed targets, one shared spent ledger, retry/exhaustion semantics and canonical multi-player ownership alongside supplemental mine intel
|
||||
PASS: actual infantry search suspension, quiet grace, finite resume, expiry/replacement and defensive/ownership boundaries
|
||||
PASS: infantry combat gate precedes radio delivery and preserves non-task transports
|
||||
PASS: actual HQ contact transition wakes only fit squad members, cancels only task orders and logs changes once
|
||||
PASS: actual finite medicine grants, authored stock deduction, full-backpack refusal/pouch fallback, no loot replenishment, unresolved-operation quarantine and ownership/stop guards
|
||||
PASS: only recent native detected/identified threats suspend hunter travel; stale/disarmed/destroyed contacts cannot strand patrols
|
||||
PASS: actual hunter combat transition cancels only its travel once, holds through renewed contact and resumes after quiet; unregistered startup preserved
|
||||
PASS: captured sighting retention, immutable details, expiry, replay rejection, bounded sound uncertainty/cooldown, radio loss, priority and overdue-status fairness
|
||||
PASS: arrow press/release, held keys, opposing keys, disabled/menu input and firearm-only retention
|
||||
PASS: town trigger and hard 700m exclusion for all observers, empty/invalid positions and final-check crossings
|
||||
PASS: exact finite site squads, invalid roster/count rejection, committed-site spacing and safe fallback boundary
|
||||
PASS: mortar couriers use the mobile site team and refuse permanent-only garrisons
|
||||
PASS: omitted preferred mortar depot selects a surviving owned camp/mobile team; no camp, foreign cache and permanent-only sites are unavailable; historical priority retained
|
||||
PASS: real-session mortar deadline, failed-support convoy admission and hunter survivor/recovery gates
|
||||
PASS: native-route retry preserves delivered target, limits attempts and suspends during combat
|
||||
PASS: stale/deleted AI utility cannot receive Complete; mortar HQ preflight withholds unsafe orders; mounted convoy cover never creates infantry Defend
|
||||
PASS: flight crew affiliation and group precede native boarding; preparation remains bounded even while passengers wait
|
||||
PASS: factionless/deleted map targets do not abort perception; faction lookup tolerates teardown; deleting helicopter releases without capture/engine access
|
||||
PASS: shoot adapter waits for a physical round; bounded same-shell retries, interrupted/never-started loads, committed no-retry, order/owner changes and normal native delegation
|
||||
PASS: missing/invalid faction crew defers before any native spawn or allowance insertion; all registered RHS factions have explicit soldier and medic profiles
|
||||
PASS: vehicle radio issued once; pending, refusal and looting cannot duplicate or fabricate a physical radio link
|
||||
PASS: actual vehicle-radio link requires the original seated crew, physical manpack and live uncaptured mission vehicle; looting, dismount, release and teardown remove it
|
||||
PASS: actual hunter release preserves survivors/HQ, releases driving ownership once, delegates capture/teardown, and excludes prison/patrol/mortar from generic objective defence
|
||||
PASS: actual player-entry hook latches original hunter capture before native callbacks; both release layers cancel ownership without new entities/refund; bounded current-owner guards and prior-release policy
|
||||
PASS: real opening road geometry enforces wide azimuths; fresh delivered contact preempts patrol cooldown, respects radio failures and does not starve in-flight interception
|
||||
PASS: actual scoped 10% tighter REGULAR aim error and alert perception; preserves equipment multiplier and restores vanilla behavior when unbound
|
||||
PASS: physical inspection and parent-wreck retention; server-only searched/report delivery unlocks native cleanup once with a 300-second delay
|
||||
PASS: bounded same-group empty-spawn retries, no casualty refill, queue retirement, delivery-gated intel, prepared-team reuse, throttled diagnostics and LOD release
|
||||
PASS: production airborne memory confidence, persistence, ground coverage, confirmed losses, artillery veto, cooldown and invalid-input rules
|
||||
PASS: production cover retains the carried HQ target until its deadline; airport reload waits until the drop pass and cover have finished
|
||||
PASS: air logistics reservation preserves delivered search until radio handoff, excludes guards/combat/in-flight orders, and quarantines unresolved native loads/moves
|
||||
PASS: actual ammunition cargo selector handles empty rifle/machinegun/sniper magazines, preserves loaded variants, excludes auxiliary launchers and tolerates missing native metadata
|
||||
PASS: original 15-minute launch/loss cadence, active/captured timer disarm, saved delay reset, stop, 300m orbit and bounded close-contact standoff at 30/60/120 Hz
|
||||
PASS: actual aircraft-removal notification guards owner/server/active/stop and schedules one full delay without replay extension
|
||||
PASS: actual helicopter delay applies resolved Random values, preserves countdown on save failure, and leaves active flights running
|
||||
PASS: hill prominence/elevation, actual road edges/heights/bends and server-only finite scout admission, pending stock, timeout and no replacements/refills
|
||||
PASS: mortar duty scope excludes players, other crews, stopped/client/inactive states; only destruction releases an active assignment, while the gunner may approach the gun
|
||||
PASS: random mode restrictions, full numeric ranges, independent calendar dates and strict daylight boundaries
|
||||
PASS: actual killais eligibility includes the 1000m boundary, excludes players/retained bodies/dead/non-character entities and checks full 3D distance
|
||||
PASS: actual generation scheduler bounds each tick, enforces deadline/candidate ceiling, validates fallback, commits only accepted positions, skips only dispensable sites, preserves required objectives/intel, and resets the next search budget
|
||||
PASS: terrain placement samples world bounds, retains streaming candidates, bounds waits/work, rejects foreign fallbacks, preserves objectives/intel and validates completion
|
||||
PASS: actual navigation readiness supports static meshes, yields only for loadable streaming tiles and refuses unreachable positions
|
||||
PASS: real sampling handles absent airports and extraction clearance preserves geometry/navigation/flatness while selecting the correct aircraft survey
|
||||
PASS: universal connector, embedded/standalone exports, multiple terrains, stable IDs, native nav configs, missing-nav visibility, path validation and overwrite protection
|
||||
PASS: airfield detection accepts physical runways/towers and bounded authored markers, rejects unrelated props, water and out-of-world markers, and samples terrain height
|
||||
PASS: configured airport parsing, bounded readiness, stale admission rejection and cleanup wiring
|
||||
PASS: automatic default, explicit header/CLI opt-out, editor/authored-mode guards, optional airport override precedence and fresh-round saving guard
|
||||
PASS: singleton identity guards, paired faction deserialization and client world-cleanup wiring
|
||||
PASS: task/map/respawn getters survive delayed old destruction and preserve ordinary-mode behavior
|
||||
PASS: replication hierarchy refuses absent/aliased nodes and establishes delivery dependencies idempotently
|
||||
PASS: standalone old faction delegates collected once, with null/deleted owners ignored
|
||||
PASS: actual terrain takeover startup skips mode-less/menu worlds, always forwards super, retains eligible/unsupported/authored/client/editor semantics
|
||||
PASS: native name callback preserves authored modes, opt-outs and failures; terrain suffix follows world changes, cached resolution and confirmed takeover metadata restores on cleanup without changing scenario resource or overwriting later titles
|
||||
PASS: actual terrain parent traversal, Armenhof/Nogova names, Everon alias, missing-resource fallback, empty names and bounded cycles/depth
|
||||
PASS: saved terrain GUIDs, defaults, restart persistence, clearing, backup recovery, corrupt-file preservation, write failures, and client write denial
|
||||
PASS: startup terrain capture protects client/editor/menu worlds, default behavior, same-map loads and reload-loop recovery
|
||||
PASS: actual transition waits for native startup, disconnects without bans, requires an empty session, retains every loaded mod, bounds waiting and consumes requests once
|
||||
PASS: actual faction resolution reserves explicit roles, samples without replacement, substitutes disabled selections, and rejects duplicate/unknown/missing choices and insufficient pools
|
||||
PASS: Random all submits persistent random roles immediately; manual mixed choices, stale/unauthorized UI guards and debounce preserved
|
||||
PASS: actual content policy gates parent packs, individual factions, faction identity and finite group rotation with vanilla fallback
|
||||
PASS: actual delivered road eligibility, ahead projection without chords, reservations, stock road query bounds, terrain/water/obstruction refusal and clean outputs
|
||||
PASS: actual delivered-task lineage, expiry, old-order retention, native commit, release, road precision expiry and duplicate delivery
|
||||
PASS: actual original-roster local handoff,100m3D bound,busy carrier/budget exclusion,origin interruption including deletedsource,normal radio delivery and per-source replay history
|
||||
PASS: actual visual intake retains source/time/position/id, rejects replay, prefers usable own route, excludes simultaneous callers and conservatively resets negative-sweep dwell
|
||||
PASS: actual fixture local/radio gating, immutable source/time/position, interrupted-report exclusion and exact-owner cleanup
|
||||
PASS: local-handoff runner refuses wrong project/round count/insufficient deadline before runtime work
|
||||
PASS: actual fixture allocator terminal/unsubmitted guard, original-group lookup and both candidate gates
|
||||
PASS: actual report-derived waypoint insertion/order and tighter positive radii; no formation navigation claim
|
||||
PASS: actual Place/Stop waits native pose update, bounds four attempts, preserves original position, restores pending/settled placement and respects ownership/accepted-move quarantine
|
||||
PASS: actual fixture current/original recipient ownership, unresolved transfer cleanup, exact-token release and idempotent Stop
|
||||
PASS: actual fixture phased-cargo readiness, bounded callbacks, captured-stock mixed-caliber selection/continuation, reservation cleanup and component magazine counts
|
||||
PASS: actual inventory-fixture setup uses ordinary prison unlock before constructing its convoy-waiting probe; failed unlock cannot run fixture
|
||||
PASS: actual authored storage anchors, native trace flags, own-hierarchy exclusion, external wall/vehicle obstruction, range, unknown geometry and bounded context refusal
|
||||
PASS: actual convoy binding, finite identities, disjoint tokens, shared report replay/cooldown, original recipients and HQ cleanup
|
||||
PASS: actual finite convoy supply pool; immutable slots, shared demand state, sparse/capped discovery and permanent shutdown
|
||||
PASS: actual HQ rotates finite donors, skips unavailable/contacted trucks, uses delivered reports and dispatches at most one trip per planning cadence
|
||||
PASS: actual original turret identity, native/canonical guards, scoped maximum-skill profile, reset, dismount/reboard, requested-skill preservation and restoration
|
||||
PASS: bounded HQ startup/memory windows and outcome-specific full-route deadlines
|
||||
PASS: explicit escape-route skips only administrator stress, preserving original full-fixture wait/failure paths
|
||||
PASS: normal escape route keeps ordinary catalog and generated placement; historical selectors remain unchanged
|
||||
PASS: actual read migrates old valid settings, preserves explicit disabled state, rejects invalid data; next-round feedback and fixed menu remain honest
|
||||
PASS: actual automatic convoy timing/authority/admission/one-attempt/manual-task/stop policy; native deployment unproved
|
||||
PASS: late authoritative failure reason refreshes open result; detach, mode change and success subscriptions preserved
|
||||
infantry resupply actual rules, radio transitions, owned cleanup and unresolved-operation tests passed
|
||||
PASS: actual resupply Roster preserves permanent release, original slot mapping and bounded ownership
|
||||
PASS: actual designated zero/one original carrier selection, settled startup, shared finite claims, no refund after submission
|
||||
PASS: actual HQ startup grant readiness forwarding is authoritative and fail-closed
|
||||
PASS: actual finite PMN4 designated-zero/one entitlement, pinned original ownership, shared commit, physical custody, callback quarantine/deletion and no retry
|
||||
PASS: actual bounded empty-return and finite delivery; phase-isolated callbacks, exact ownership proof, stop quarantine and original actor/vehicle boundaries
|
||||
PASS: actual shared finite identity reservation; competing consumers, submission permanence, stale cleanup and96-item bound
|
||||
PASS: actual carried-mounted swap, shared committed claim, physical slot reach, exact two-way proof, uncertainty and terminal cleanup
|
||||
PASS: actual mounted pickup, exact finite ownership, pre-submission commit, physical custody, bounded callback and quarantine lifecycle; no native coverage claim
|
||||
PASS: actual mounted courier admission, radio, individual orders, two-stage transfer, reboarding, token cleanup and quarantine lifecycle
|
||||
PASS: actual scoped crew permit and native board completion/failure preserves foreign seats without teleport fallback
|
||||
PASS: actual shared mounted stock/history ownership, atomic exact HQ pair claim, and bounded stopped-owner proof polling
|
||||
PASS: actual carried/mounted early ammunition policy and status-first shared uplink; complete threshold, phases, cooldown, failures requested check-ins and non-urgent detached carried-mine status
|
||||
PASS: actual group radio startup preservation, finite native grants, callback quarantine, frozen selection/actor ownership and bounded observation
|
||||
PASS: actual maintenance native-stock check rejects missing/duplicate/wrong-identity/unowned/depleted/malformed stock and is read-only; native runtime not executed
|
||||
..................
|
||||
----------------------------------------------------------------------
|
||||
Ran 18 tests in 0.242s
|
||||
|
||||
OK
|
||||
PASS: actual radio node rejects current incapacitation, death, ownership and faction changes despite cached inventory; one-second capability refresh remains explicit
|
||||
PASS: Southern finite maintenance stock preserves weapons/clues and optional transport, historical Western fixture stays separate; native capacity/consumption untested
|
||||
PASS: optional transport deferred behind required readiness, bounded once-only spawn/omission, isolated cleanup, occupied-candidate retention and required-default preservation
|
||||
PASS: actual diagnostic fallback runs only on failed missing-context call, derives unique native pilot door, preserves stock eligibility and restores prior index on success/failure; diagnostics mutate no game state.
|
||||
PASS: actual native-prelude cleanup restores only original changed damage, refuses stale/incapacitated/aboard teleport; bounded five-seat/four-door fixture wiring checked. Native execution remains pending.
|
||||
PASS: actual CreateTransport requires successful scoped Configure after handbrake, preserves ordinary vehicles, tracks spawned entity before failures and never treats failed lock registration as ready.
|
||||
PASS: actual preparation Configure guards/idempotence/breach preservation, security eligibility/final current-body server mutation and all scoped action guards; all five seat/four door resource overrides. Native hold, prefab merge, replication and boarding remain unverified.
|
||||
PASS: production vehicle alarm single trigger/replica completion/no replay, fixed deadline/readiness wait, failed audio, dedicated server, moving exact handle, destruction/native end/deletion/clock and mission teardown. Native sound and multiplayer delivery require engine qualification.
|
||||
PASS: production alarm emitter/receiver original time/location, explicit native queue only, 32-entry cap, 50m local witness eligibility, bound perception, native-super preservation; actual engine broadcast/hearing remains unverified.
|
||||
PASS: actual optional candidate barrier, exact callback/custody proof and unchanged mine/primary snapshots; no native transfer claim
|
||||
Native finite mine-stock runner: parser, scope, exclusions and command pass
|
||||
PASS: actual bounded native mounted proof/detached payload/deadline/cleanup methods, production-only setup and isolated runner selector
|
||||
PASS: actual one-existing-convoy candidate readiness, sender-root identity, incomplete cargo refusal and fixed three-candidate bound
|
||||
PASS: actual once-only native dry query, exact before/after slots, native refusal, shutdown; shared bounded discovery and distinct opt-in verdict
|
||||
PASS: actual completion selector and pre-cleanup variant capture preserve separate radio and eligibility verdicts
|
||||
PASS: actual cargo diagnostic stages separate absent/foreign magazines, used/empty/invalid rounds, well mismatch and exact truck storage/attachment without mutation
|
||||
PASS: actual native courier witness rejects missing radio/custody/swap/reboard evidence; CLI opt-in bounded; native success remains unqualified
|
||||
PASS: actual courier constructor/Tick waits for preparation, returns before physical checks, preserves fixed deadline and sticky stop/failure; Enfusion startup awaits native verification
|
||||
PASS: actual arranged native alarm fixture deadline, single emit, natural-route observation, original timestamp/area/detail assertions, natural observer proximity/activation wait, failure cleanup and independent canonical-body restoration; native delivery itself remains unqualified.
|
||||
PASS: actual convoy cover observation/snapshot/uplink/HOLD/RESUME/cleanup transitions; native boundaries mocked, non-convoy runner opt-in refused
|
||||
Enemy optics actual-service ownership, preservation, callback, deadline and bounded-inspection policies passed
|
||||
PASS: actual delivered cover freshness/lineage/replay and canonical executor; owned waypoint-only hold, one resume at preserved route index, invalid ownership/crew/ledger refusal and fail-closed native recreation
|
||||
PASS: actual Stinger show/set/help state precise actor-roll effects; read-only help, bounded values, malformed syntax and failed-save preservation; no rerolls/gameplay edits and no native persistence claim
|
||||
PASS: new/retasked search and radio delivery ammo gates; active tasks and delivered release preserved
|
||||
HQ convoy delivered-report planning and downlink regressions passed
|
||||
PASS: actual own-inventory ammo inspection deduplicates loaded/carried/shared wells, separates chambers/launchers, freezes scalar snapshots and treats unsupported/capped/foreign inventory as unknown
|
||||
PASS: actual carried PMN4 inventory, original-owner guards, bounded deduplication, independent unknown state and detached counts
|
||||
PASS: actual mounted hierarchy sampling, ownership exclusion, deduplication, completeness/caps, quarter threshold and detached validation; no runtime policy or ammunition mutation
|
||||
PASS: WORKBENCH opt-in diagnostics distinguish cycles/cap/storage/manager/mount failures without repeated native traversal
|
||||
PASS: actual HQ original convoy-root admission rejects wrong force/server/group and never supplies replacement lookup
|
||||
PASS: production mounted structural seat scan ignores >128 carried/cargo descendants, shares exact native mount/magazine ownership, preserves attachment seats and exact occupants, rejects unknown/cyclic/oversize structures; native binding still requires qualification.
|
||||
PASS: native body fixture uses existing immutable actors, real production LOS/queued uplink, pre-delivery isolation, actual status overlap, bounded cleanup and no fabricated reports/spawns
|
||||
PASS: actual native fixture lookup selects exact BODY kind/identity/message from bounded evaluations, never sighting-only Get
|
||||
PASS: actual ammo snapshot comparison checks every scalar/category flag; native probe requires real roster rescan, loaded-round lower bound and separate immutable pending/Watch objects
|
||||
PASS: actual delivered slot-union bodies preserve overlap/disjoint evidence, replay idempotence, status subset integrity, full-wipe closure and formation-local bounds
|
||||
test_review_added_allowed_media_invalidates_snapshot (__main__.ReviewTests.test_review_added_allowed_media_invalidates_snapshot) ... ok
|
||||
test_review_added_source_directory_rejected (__main__.ReviewTests.test_review_added_source_directory_rejected) ... ok
|
||||
test_review_loose_root_resource_and_symlink_rejected (__main__.ReviewTests.test_review_loose_root_resource_and_symlink_rejected) ... ok
|
||||
test_review_metadata_media_and_sidecars_remain_allowed_and_bound (__main__.ReviewTests.test_review_metadata_media_and_sidecars_remain_allowed_and_bound) ... ok
|
||||
test_review_missing_link_rejected (__main__.ReviewTests.test_review_missing_link_rejected) ... ok
|
||||
test_review_package_guard_sees_new_scripts (__main__.ReviewTests.test_review_package_guard_sees_new_scripts) ... ok
|
||||
test_review_retargeted_private_link_rejected (__main__.ReviewTests.test_review_retargeted_private_link_rejected) ... ok
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 7 tests in 0.060s
|
||||
|
||||
OK
|
||||
PASS: actual report methods reproduce old starvation; status-first scheduler delivers check-ins and requires 30s without combat or actual visible contacts before negative reports
|
||||
PASS: actual actor group lookup retains controlled character membership when seated/inherited vehicle agents differ; missing control or parent refuses
|
||||
.................
|
||||
----------------------------------------------------------------------
|
||||
Ran 17 tests in 3.504s
|
||||
|
||||
OK
|
||||
PASS: actual convoy motion thresholds, native-only proof boundaries, physical cargo/crew ownership and pre-launch runner restrictions
|
||||
PASS: actual filled rotated-footprint TraceBox and nine terrain/water samples; broad tree/cable bounds alone accepted, actual collision/nonfinite trace refused, nonzero sweep and ENTS/OCEAN flags, bounded diagnostic query and reset
|
||||
PASS: actual road interpolation/projection/orientation, six35m starts,200m forward tail, preserved bends, short/far/degenerate/nonfinite/overdense/hairpin refusal and deterministic candidate ties
|
||||
PASS: actual authenticated convoy dispatch, active/ready admission gates, canonical receipt, safe unconsumed preflight retry, pool refusal, retained partial failure and consumed no-retry
|
||||
PASS: actual native readiness exact membership, one-time per-vehicle ledger admission/reservation, partial readiness retry without duplicate records and one-pass56-item cargo allocation without replenishment
|
||||
PASS: actual per-vehicle readiness reasons, native agent/activity counts, intended-seat diagnostics, five-second throttle and read-only observation
|
||||
PASS: actual convoy dispatch validates before receipt consumption, consumes once before native mutation, retains all12 partial-failure paths, accepts the selected non-USSR enemy, and allocates exact19 bodies without capacity shrinking
|
||||
PASS: own memberless prefab disables automatic spawn before initialization; actual admission rejects populated, immediate or authored-slot templates before finite crew creation
|
||||
PASS: actual native seated-group arrival outside35m, native waypoint removal, idempotent advance, and convoy waypoint idempotence/arrival/deadline, missing/dead/captured/reparented crew refusal, failed native waypoint release, original vehicle/member preservation and no replacement
|
||||
PASS: actual convoy/team Release retains original active survivors and HQ identity, rejects takeover/terminal/unknown roster, preserves exact logistics quarantine and normal eligibility
|
||||
PASS: actual convoy receipt identity/one-use, shared60-body and128-formation caps, outstanding/native-ledger accounting, exact19-body composition, source reuse, mode/client guards and no consumed receipt refunds
|
||||
PASS: actual supply route cycling/arrival/stops, finite startup bounds, contact freshness, dismount hold, quiet interval, interrupted boarding, timeout and lost-truck release
|
||||
PASS: moving-convoy perception boundaries and scoped individual/group abandonment priorities
|
||||
PASS: actual supply startup reasons expose unchanged body budget/requirement, journal only failure changes, preserve retry cadence/caps and report the true final refusal
|
||||
PASS: actual combat start releases only exact intercept claims before owning route/orders; foreign claim defers combat takeover
|
||||
PASS: actual StartOne plus actual pool preserve unconsumed receipt retry, reject consumed replay, skip impossible new-budget surveys, retain equality, retry caps and journal dedup; no FPS claim
|
||||
PASS: actual admission seeds use exact installed loaded prefab, reject replacement/foreign/projectile paths and preserve bounded immutable identity
|
||||
PASS: actual primary-before-mounted one-shot batch, shared96/128 caps, physical snapshot headroom, original ownership, callback refusal and admission pinning
|
||||
PASS: actual bounded HQ query validates only existing original binding without admission or replacement lookup
|
||||
PASS: exact optional PMN4 startup, primary callback independence, original owner,128 capacity/callback limits, terminal no-retry and callback-proven freeze
|
||||
PASS: production convoy spacing, physical arrival, stable follower targets, supply ownership, leader promotion and bounded stuck recovery
|
||||
PASS: convoy combat keeps native manoeuvres for unchanged contacts and refreshes when contact moves
|
||||
PASS: native convoy proof excludes pre-departure movement and ends via explicit fixture reset, not prohibited player death
|
||||
PASS: boarding retries only after approach/seat progress stops, capped at three, with delayed native reissue and completed-team preservation
|
||||
PASS: slow only owned vehicles near fit dismounted infantry; ignore passengers, casualties and released teams/vehicles
|
||||
PASS: restore missing native driving registration only for healthy owned seated crews; preserve existing association and refuse invalid/released vehicles
|
||||
PASS: actual body observer enforces owned physical roster, local death-only candidates, native trace gating, budget/cadence/caps, immutable body-only snapshots, radio retries and confirmed suppression
|
||||
PASS: own initial roster, explicit retained corpse evidence, unconscious/missing/ref transfer guards, fixed strength, no replacement adoption, detached samples, surviving radio sender, immutable convoy root/ledger, mounted capture release and lost-root unknown
|
||||
PASS: delivered-only status snapshots, two missed check-ins/reply deadline, last reported coordinates, episode/replay guards, confirmed-loss idempotence, detached/validated mounted payload replacement 256-record cap and detached carried-mine radio delivery/chronology
|
||||
PASS: actual failed-retask deadline rollback plus assignment cancellation/release preserves radio ownership, native waypoint lifecycle and failed-order retry boundaries
|
||||
PASS: actual force ledger, finite casualties/identity/cap, routes/arrival/elevation/overshoot, server and physical guards, staggered 256-record simulated hour without world queries
|
||||
PASS: actual equipment coordinator registration dedupe, startup delay, five member scans, finite component retries, unregister/stop terminal behavior and 256-group bound
|
||||
PASS: actual percentage selection, native/preferred launcher preservation, failure/removal fallback, pending-operation quarantine, capture, finite two-mine grants and no replenishment
|
||||
PASS: automatic inventory one-shot roll, exact item ownership, finite compatible ammo, rejection, capture/stop, deletion, loot and late callback quarantine
|
||||
PASS: enabled/valid catalog eligibility, category restrictions, content filtering, GUID deduplication across factions and bounded catalogs
|
||||
PASS: actual engineer mine preparation/deadline, original custody/identity, once-only callback removal, cancellation quarantine, withdrawal/clearance, native refusal and owned-action cleanup; native execution untested
|
||||
PASS: actual road mine-site annulus/bend geometry, deterministic bounded candidates, server/query limits, and native ground/water/obstruction rejection; no runtime placement claim
|
||||
PASS: actual HQ mine admission, one site query per tick, maximum two active/32 retained jobs, delivered-only original post, once-per-unit refusal latch, 256-item ceiling and maintenance/teardown ownership
|
||||
PASS: actual objective heading/threat/approach rules, persistent doctrine weight, fresh-contact coverage invalidation, stale-sweep rejection and atomic radio retask ownership
|
||||
PASS: actual bounded delivered-negative coverage, replay/freshness/expiry/invalid guards, oldest eviction, small-circle and nearest-unswept selection, reserved-cell exclusion and cleanup
|
||||
PASS: actual HQ active/pending reservations, interruption, committed replacement, bounded coverage and unchanged force counts
|
||||
PASS: actual bounded pursuit roles/objectives/scores, stale search degradation, physical/busy/home guards, finite lifetime and two-bearing escape gap; no hidden player reads or spawning
|
||||
PASS: actual HQ memory rules/service preserve delivered snapshots, chronology, replay bounds, decay, motion limits, identity and negative-search revisions
|
||||
.......
|
||||
----------------------------------------------------------------------
|
||||
Ran 7 tests in 0.028s
|
||||
|
||||
OK
|
||||
PASS: actual session Tick + journal router delivers each grant completion and new HQ line once, without repeats on empty/unavailable feeds or unauthorized ticks
|
||||
.....
|
||||
----------------------------------------------------------------------
|
||||
Ran 5 tests in 0.389s
|
||||
|
||||
OK
|
||||
PASS: actual current-controller/admin/mode authorization, per-round anti-replay, token-rotation refusal, command/snapshot throttles and client scope; owner-only replies and player-tool cleanup wiring
|
||||
PASS: mission provider registration is idempotent, optional dispatch is ordered, status aggregates, journals preserve cursors, aliases stay with their addons and topic help is bounded
|
||||
PASS: actual menu command routing covers all fifteen settings, rejects unsupported keys, gates companions, and matches unique authored widgets
|
||||
PASS: explicit weapon retention buttons use the same ready/authorized server command path
|
||||
PASS: item editor, preset controls and dynamic item-row layout are structurally valid
|
||||
PASS: catalog requests use authenticated sessions, independent admin page selections, loaded-mod allowlisting, bounded pages and allowlisted item mutations
|
||||
PASS: actual item rules, shared-source conflicts, missing/returning mods, bulk reset, independent named presets, bounds, malformed data and failed-save state preservation
|
||||
PASS: actual summaries preserve queued/delivered meaning, clear counts and IDs; raw records, legacy display, cursor gaps and RPC limits remain intact
|
||||
PASS: actual admin body authorization, god restoration, vanilla item deposit, callback completion/failure, timeout quarantine and stop/revocation paths
|
||||
PASS: same-body revoke/regrant preserves pending insertions and timeout quarantine; new-body binding isolates late callbacks and never inherits god mode
|
||||
PASS: actual Intel commands distinguish status/start/failure, preserve authorization gate and reject malformed requests; spent ledger is idempotent, server-only and bounded
|
||||
PASS: actual reroll snapshot, authority, cancellation, ownership, accepted operations, quarantine, bounded work and stop methods; no native runtime claim
|
||||
PASS: production Intel feedback coalescing, rough secrecy, rate limit, bounded dedupe, local/remote delivery and round cleanup; native UI/RPC not exercised
|
||||
PASS: actual per-squad bounded report selector rotates visible targets, survives ordering/turnover, records only queued attempts, and preserves radio intake ordering
|
||||
PASS: actual action denial/clearing, server request failure, single submitted-result feedback, current rescuer identity and terminal/stop suppression; native UI/RPC pending
|
||||
PASS: actual physical denial precedence and transport-seat occupancy/reservation/access/lock checks
|
||||
PASS: actual initial admission exhaustion and ServerTick grace; new/pending/empty/controller/life guards, normal death preserved
|
||||
......................
|
||||
----------------------------------------------------------------------
|
||||
Ran 22 tests in 0.228s
|
||||
|
||||
OK
|
||||
......................
|
||||
----------------------------------------------------------------------
|
||||
Ran 22 tests in 22.941s
|
||||
|
||||
OK
|
||||
..................
|
||||
----------------------------------------------------------------------
|
||||
Ran 18 tests in 2.639s
|
||||
|
||||
OK
|
||||
.........
|
||||
----------------------------------------------------------------------
|
||||
Ran 9 tests in 2.077s
|
||||
|
||||
OK
|
||||
...............
|
||||
----------------------------------------------------------------------
|
||||
Ran 15 tests in 1.047s
|
||||
|
||||
OK
|
||||
PASS: actual field result/braking observer and Chat dispatch rules; one finite fixed candidate, deferred callback failure and guarded cleanup; no native measurement
|
||||
PASS: actual opt-in terrain audit records one fixed1681-point grid, preserves original survey refusal/success and exact no-allocation scope; malformed heights stop without retry; no native data acquired
|
||||
....
|
||||
----------------------------------------------------------------------
|
||||
Ran 4 tests in 0.544s
|
||||
|
||||
OK
|
||||
PASS: actual field PI/ramp/vector caps, antiwindup, braking hypothesis, immutable admission limits and segment bounds; no native physics tested
|
||||
PASS: actual100m whole-field survey sampling/4096cap, rotated coverage, all-node terrain and native trace refusal; original certificate lineage retained; no native qualification
|
||||
PASS: actual QualifyField preserves original identity/footprint/time, orders complete surveys before issuance, and refuses failed stages/moved body/expired or changed world; native boundaries faked
|
||||
PASS: WORKBENCH first-refusal diagnostics preserve ordinary predicates, query order/count/budget and opt-in scope; no native logging run
|
||||
PASS: actual field startup delegation, immutable lease/target, braking/settling/cancel transitions, early abort, callback scope, physical terminal policy and shared landing demand; native boundaries faked
|
||||
PASS: actual inherited airborne pre-CLIMB cancellation enables verified native autohover once, refuses failed enable and never writes after capture
|
||||
PASS: actual Core maintenance/default/tick/stop methods; empty/populated authority, terminal reentry, no tactical resume or new scheduler; native boundaries substituted
|
||||
PASS: actual finite registration, unique claims/tokens, retention/cancel/stop, readiness gating, capture/expiry retirement and bounded cross-kind order history and typed field/route construction; native boundaries faked
|
||||
PASS: actual WORKBENCH route observer returns exact retained route subtype and refuses absent/field controllers
|
||||
.....
|
||||
----------------------------------------------------------------------
|
||||
Ran 5 tests in 0.577s
|
||||
|
||||
OK
|
||||
......................
|
||||
----------------------------------------------------------------------
|
||||
Ran 22 tests in 0.174s
|
||||
|
||||
OK
|
||||
................
|
||||
----------------------------------------------------------------------
|
||||
Ran 16 tests in 9.395s
|
||||
|
||||
OK
|
||||
......
|
||||
----------------------------------------------------------------------
|
||||
Ran 6 tests in 0.020s
|
||||
|
||||
OK
|
||||
PASS: actual production bridge lookup, lease/world/mode/seat/capture guards, no allocation, native super ordering and read-only fixture hooks
|
||||
...............
|
||||
----------------------------------------------------------------------
|
||||
Ran 15 tests in 0.056s
|
||||
|
||||
OK
|
||||
.............
|
||||
----------------------------------------------------------------------
|
||||
Ran 13 tests in 4.398s
|
||||
|
||||
OK
|
||||
PASS: actual route gain/caps/braking, callback selection, old landing/cancellation control and matching read-only telemetry
|
||||
..............................
|
||||
----------------------------------------------------------------------
|
||||
Ran 30 tests in 0.368s
|
||||
|
||||
OK
|
||||
..............
|
||||
----------------------------------------------------------------------
|
||||
Ran 14 tests in 0.180s
|
||||
|
||||
OK
|
||||
.........
|
||||
----------------------------------------------------------------------
|
||||
Ran 9 tests in 0.103s
|
||||
|
||||
OK
|
||||
PASS: actual gate observer binds native registered action/context and current life, reads accumulated progress, refuses stale bindings and never revives outside original bounds
|
||||
PASS: actual read-only look observer methods enforce explicit scope, pinned native camera/body/identity, normalized pose, terminal cancellation and original sample/time bounds
|
||||
...........
|
||||
----------------------------------------------------------------------
|
||||
Ran 11 tests in 0.062s
|
||||
|
||||
OK
|
||||
.......................
|
||||
----------------------------------------------------------------------
|
||||
Ran 23 tests in 0.031s
|
||||
|
||||
OK
|
||||
PASS: actual initial WAITING task/guide copy, configured duration, unchanged interruption and rounded progress; native task assertion pending
|
||||
........
|
||||
----------------------------------------------------------------------
|
||||
Ran 8 tests in 0.011s
|
||||
|
||||
OK
|
||||
PASS: actual inventory adapter scope, grid placement, sole compatible host, refresh reuse, rollback, foreign hierarchy refusal and exactly-once native delegation; native compatibility pending
|
||||
.................................................................................
|
||||
----------------------------------------------------------------------
|
||||
Ran 81 tests in 0.487s
|
||||
|
||||
OK
|
||||
......
|
||||
----------------------------------------------------------------------
|
||||
Ran 6 tests in 0.065s
|
||||
|
||||
OK
|
||||
.................
|
||||
----------------------------------------------------------------------
|
||||
Ran 17 tests in 0.005s
|
||||
|
||||
OK
|
||||
...........
|
||||
----------------------------------------------------------------------
|
||||
Ran 11 tests in 0.066s
|
||||
|
||||
OK
|
||||
.........
|
||||
----------------------------------------------------------------------
|
||||
Ran 9 tests in 0.302s
|
||||
|
||||
OK
|
||||
PASS: actual observer nonce, admission pins, baseline/menu sequencing, snapshots, bounded clocks/samples, cleanup and one-attempt latch; native untested
|
||||
PASS: actual corridor caps/height/hold and whole-rectangle survey bounds, terrain refusal, native trace refusal and exact certificate lineage; native route unqualified
|
||||
PASS: actual route entry, fixed corridor/target, hover hold, cancellation landing freeze, shared lease/capture/deadline gate and unchanged native startup/tick sequence
|
||||
PASS: finite opt-in route fixture, original native input order, meaningful physical-arrival evidence and guarded cleanup; no native launch
|
||||
PASS: actual Chat route step result dispatch and scoped completion/cleanup wiring; six real early-parser refusal paths leave no prefix/profile output
|
||||
PASS: actual retained-service fixture entry, immutable controller/token/deadline/count bindings, observer getter and cleanup ownership ordering; native route validation pending
|
||||
PASS: actual measured cancellation gate, one-shot retained service Stop, no premature drain, immutable lease, fresh real controller readback and separate physical cancellation result policy
|
||||
PASS: actual rough-intel objective selection, exact precedence, source grid dimensions and unchanged other-phase dispatch; native UI pending
|
||||
.................................
|
||||
----------------------------------------------------------------------
|
||||
Ran 33 tests in 0.118s
|
||||
|
||||
OK
|
||||
...............
|
||||
----------------------------------------------------------------------
|
||||
Ran 15 tests in 0.028s
|
||||
|
||||
OK
|
||||
PASS: actual preparation status scope, persistent updates, spinner restoration, placeholder replacement/deletion/destruction, native observation persistence/timeout; rendering remains native-pending
|
||||
PASS: actual guide ownership and scoped timer cancellation, current-controller preservation/reacquisition, hint retry/dedup and observer boundaries; native UI not exercised
|
||||
PASS: actual native-end shutdown, existing terminal snapshots, reentrancy, unchanged native end data and stale preparation guards
|
||||
....
|
||||
----------------------------------------------------------------------
|
||||
Ran 4 tests in 1.218s
|
||||
|
||||
OK
|
||||
PASS: actual three-state result wording and UI refresh; stale winner IDs never manufacture escape
|
||||
...........
|
||||
----------------------------------------------------------------------
|
||||
Ran 11 tests in 0.101s
|
||||
|
||||
OK
|
||||
PASS: actual immutable target, bounded autohover demand, drift refusal, five-second hold and unchanged qualification/deadline
|
||||
PASS: actual admission attempt budget bounds blocked/missing prerequisites; preparation, spent life and async pending remain protected
|
||||
PASS: delayed admission retry retains controller ownership across same-account recycled connection IDs
|
||||
PASS: actual scene deadline overrides delayed native preload only for rejection; late completion cannot assign, ordinary verified admission preserved
|
||||
PASS: actual action permission matrix unchanged; useful phase/intel/consciousness denials, stale reason cleared, native door denial preserved; no native UI claim
|
||||
.....
|
||||
----------------------------------------------------------------------
|
||||
Ran 5 tests in 0.230s
|
||||
|
||||
OK
|
||||
PASS: actual aircraft cancellation/stage rules, ground-only completion, expiry/capture faults, input ownership and no physical shortcuts
|
||||
PASS: actual exact-instance single-use qualification, native-clock staleness/movement refusal, footprint bounds and ownership-safe ground/fault cleanup
|
||||
PASS: actual optional screenshot helper flag/identity/world/life revalidation, one queued attempt, delayed capture and mission/world cleanup; no native image/presentation claim
|
||||
PASS: actual yard diagnostic exact registered prison ownership, eight native-bound corner projections, no root-sibling escape, bounded duplicate/deep/wide hierarchy traversal; native projection/mask still unmeasured
|
||||
PASS: actual CurrentActor, SpawnPosition and observer-selection methods reject pending/unregistered/replaced/recycled/foreign/dead bodies; canonical selection/cycling, unconscious following and prison/geometry fallback preserved; native boundaries adapted
|
||||
PASS: actual native-map fixture ownership, cleanup and callback evidence fail closed; native execution remains separate
|
||||
PASS: actual shared-map callbacks bind exact map/config/root, preserve replacement UI/camera, defer focus, and remove only owned delayed work
|
||||
PASS: actual Intel/HQ methods and one-shot qualified readiness handoff deliver late actors once; duplicate readiness, accepted spawn, pickup/drop, death and original negative rolls do not mint assignments
|
||||
PASS: Workbench-only read accessor rejects unknown/nonserver identities; Chat observes all distinct ready crew before existing shell-depletion mutation
|
||||
PASS: actual native-event latch survives deselection, requires exact activity/order, detaches before cleanup, preserves failure/timeout and never infers completion from physical shot count
|
||||
.........
|
||||
----------------------------------------------------------------------
|
||||
Ran 9 tests in 0.094s
|
||||
|
||||
OK
|
||||
PASS: actual refresh-fixture gates pin immutable observations/actual downlinks/original activity, reject unaccepted or relabelled commits, stop queueing early and require all queued contacts claimed before replay
|
||||
..........................
|
||||
----------------------------------------------------------------------
|
||||
Ran 26 tests in 0.205s
|
||||
|
||||
OK
|
||||
PASS: actual refresh scheduler/delivery/cancellation/projectile methods reject interrupted or replayed reports, retain committed ownership, preserve immutable snapshots/aim geometry and isolate stopped or replaced orders; native radio/crew behavior remains separate
|
||||
.....................
|
||||
----------------------------------------------------------------------
|
||||
Ran 21 tests in 0.031s
|
||||
|
||||
OK
|
||||
...........
|
||||
----------------------------------------------------------------------
|
||||
Ran 11 tests in 0.032s
|
||||
|
||||
OK
|
||||
PASS: actual full-salvo gates require eight fresh committed physical launches, natural closure and exact finite inventory; reject partial/extra/replayed events
|
||||
PASS: actual reason/availability guidance and refresh matrix; no-target copy omits cycling, hint retry retained, existing command/admin paths unchanged; native UI pending
|
||||
PASS: actual client camera policy closes immediately on healing or mission end; late unconscious metadata cannot keep the view active
|
||||
PASS: actual input dispatch, one press per edge, menu-held suppression; actual revival camera handoff with editor close/retry, missing camera, replacement body and stopped round
|
||||
PASS: first-person pose survives missing samples without drift, follows body motion/rotation, resumes fresh pose and cannot leak between targets
|
||||
................usage: test_native_package.py [-h] [--project {core}] --session SESSION
|
||||
--compat COMPAT [--steam STEAM]
|
||||
[--timeout TIMEOUT]
|
||||
[--external-addons EXTERNAL_ADDONS]
|
||||
[--silent-workbench | --exit-after-init]
|
||||
[--continue-on-failure]
|
||||
test_native_package.py: error: argument --exit-after-init: not allowed with argument --silent-workbench
|
||||
................................
|
||||
----------------------------------------------------------------------
|
||||
Ran 48 tests in 12.016s
|
||||
|
||||
OK
|
||||
.......
|
||||
----------------------------------------------------------------------
|
||||
Ran 7 tests in 0.231s
|
||||
|
||||
OK
|
||||
..............
|
||||
----------------------------------------------------------------------
|
||||
Ran 14 tests in 2.232s
|
||||
|
||||
OK
|
||||
....
|
||||
----------------------------------------------------------------------
|
||||
Ran 4 tests in 0.298s
|
||||
|
||||
OK
|
||||
....
|
||||
----------------------------------------------------------------------
|
||||
Ran 4 tests in 0.053s
|
||||
|
||||
OK
|
||||
PASS: actual finite-courier age, token, exact ownership/callback and delivered-status admission reject stale, replayed, captured, canceled and ambiguous work
|
||||
PASS: actual native-gun ownership gate rejects player/foreign/replaced/dead occupant, faction change, missing seat and missing registry
|
||||
PASS: four-battery courier selection preserves pending trip/report/transfer identity, skips unavailable crews, and selects the next depleted battery without new stock
|
||||
PASS: 64 bounded distinct storage candidates; native gates, five-store barrier and finite-stock ownership retained; native placement pending
|
||||
PASS: 128 coarse+80 local refinement+169 airport grid candidates retain all native clearance gates; real terrain untested
|
||||
PASS: actual prison registration/crossing policies reject missing/duplicate/destroyed initial gate, incomplete ownership/scan, outside-only body, jumps and stale samples; physical exit independent of later gate damage
|
||||
PASS: actual prison door execution blocks direct AI/UI bypass while locked and delegates unchanged native action once after unlock and permission approval
|
||||
..................
|
||||
----------------------------------------------------------------------
|
||||
Ran 18 tests in 1.981s
|
||||
|
||||
OK
|
||||
.....
|
||||
----------------------------------------------------------------------
|
||||
Ran 5 tests in 1.090s
|
||||
|
||||
OK
|
||||
PASS: native local-admission snapshot rejects changed life/key/body/participant/pending state; authentication is not simulated
|
||||
PASS: actual mortar report ledger prevents duplicate response after cancellation, expiry and interleaved reports; native scheduler separate
|
||||
.........
|
||||
----------------------------------------------------------------------
|
||||
Ran 9 tests in 0.014s
|
||||
|
||||
OK
|
||||
....
|
||||
----------------------------------------------------------------------
|
||||
Ran 4 tests in 0.138s
|
||||
|
||||
OK
|
||||
PASS: actual aim renewal requires fresh same-report solve and original-command equivalence; native firing untested
|
||||
........
|
||||
----------------------------------------------------------------------
|
||||
Ran 8 tests in 0.013s
|
||||
|
||||
OK
|
||||
PASS: actual startup/generation/ownership/timeout/callback policies and native-counter wait; integration preserves one-shot allocation and pending-crew cancellation
|
||||
PASS: rapid mortar cycle counts eight physical rounds once and waits for native completion without a ninth aim or shell
|
||||
PASS: actual probe binds native events to fresh immutable commits, permits delayed emission, rejects unrelated expiry closure and premature stop
|
||||
PASS: actual noSplash Load body requires both explicit diagnostic and noSplash, preserves vanilla otherwise, and handles repeated Load/reload
|
||||
PASS: actual fixture method permits one native resilience operation and restores either prior protection state, including failure paths
|
||||
PASS: actual safe-exit boundary policy, native action/door selection, stop/cache cleanup, separate old mechanism and opt-in runner contracts
|
||||
PASS: actual one-shot deadline/stability policies and preparation ownership, fuel, cleanup integration contracts
|
||||
PASS: pending-only cancellation retires copied instance intent, preserves physical/control actors, and stops once
|
||||
PASS: actual short-cast hit, upward support normal, no self support and no dynamic support
|
||||
PASS: actual committed-ticket structure preserves reservation after death, rejects missing bodies/seats and ends at observation expiry
|
||||
PASS: actual pending callback expires an empty-server reservation once, preserves its full lease, and cancels on completion/Stop
|
||||
PASS: rescue lease expiry/reordering, fresh five-second settling, independent rescue ranges and grounded upright aircraft motion boundaries
|
||||
PASS: requested guard population requires complete physical conscious membership before deadline
|
||||
......................................
|
||||
----------------------------------------------------------------------
|
||||
Ran 38 tests in 0.017s
|
||||
|
||||
OK
|
||||
..........
|
||||
----------------------------------------------------------------------
|
||||
Ran 10 tests in 0.002s
|
||||
|
||||
OK
|
||||
.....
|
||||
----------------------------------------------------------------------
|
||||
Ran 5 tests in 0.948s
|
||||
|
||||
OK
|
||||
PASS: authored radio/optional-site bounds and physical horizontal/vertical boundaries
|
||||
...
|
||||
----------------------------------------------------------------------
|
||||
Ran 3 tests in 0.001s
|
||||
|
||||
OK
|
||||
PASS: all phase transitions, terminal states, defence boundaries, stale/future observations, intel discovery
|
||||
PASS: Core document-discovery alias, companion-only alias exclusion, strict integer parsing and setting boundaries
|
||||
PASS: player admission phase/life/identity/controller matrix, preload displacement limits, pending reservation overlap and owner exclusion
|
||||
PASS: actual gate and pre-inventory document guards reject noncanonical/faction/life/disconnect/server mismatches; canonical reconnect and unconscious carried intel preserved
|
||||
PASS: WB-only native fault injection restores both body mutations before checks/returns and requires eligible held-document negative/positive proof; runtime pending
|
||||
PASS: actual discovery coordinates disclose stable 500m cell center until identified, then exact point, including negative/boundary coordinates
|
||||
PASS: actual local admission context/selection/latch, same-round late identity, replacement/mode failure, fresh round and untouched network path
|
||||
PASS: prison search packs 16 unique 3m-spaced inside-wall centers, prioritizes inner/edge positions and never bypasses clearance
|
||||
PASS: physical body occupancy rejects overlap, permits diagonal neighboring slots, and excludes own body and unrelated broad-phase bounds
|
||||
PASS: recovery state excludes dead bodies, preserves reservations, requires controlled rescuers and distinguishes empty/all-dead teams
|
||||
PASS: authority-scoped slow recovery, bleeding pauses resilience, medicine unaffected, player-only lethal damage cap without healing
|
||||
PASS: bleeding severity survives low health, retains armour, causes no knockout and restores the actual damage/blood-loss cap
|
||||
PASS: viable passive recovery survives native delay; bleeding, disabled/full effects and low-blood resilience do not block defeat
|
||||
PASS: every minute round-trips, malformed clocks rejected, defaults limited to added keys, progression bounds and day duration aligned
|
||||
PASS: native settings policy matches calendar boundaries 1900-2050, date/profile parsing is strict, authored defaults preserve old worlds
|
||||
PASS: view-distance bounds, default, save failures, command parsing, temporary ownership, server/platform limits and lifecycle wiring
|
||||
.....
|
||||
----------------------------------------------------------------------
|
||||
Ran 5 tests in 0.521s
|
||||
|
||||
OK
|
||||
.
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.313s
|
||||
|
||||
OK
|
||||
PASS: radio authorization/discovery/phase matrix, exact distance limits, defender handover, interruption, empty samples, clock regression and duration boundary
|
||||
PASS: actual radio feedback policy, initial wait, interruption persistence, resumption and terminal/new-round reset
|
||||
...............
|
||||
----------------------------------------------------------------------
|
||||
Ran 15 tests in 2.015s
|
||||
|
||||
OK
|
||||
..............................................
|
||||
----------------------------------------------------------------------
|
||||
Ran 46 tests in 0.803s
|
||||
|
||||
OK
|
||||
PASS: legacy values preserved, unsupported fields reported, invalid data rejected, existing files protected, runtime bounds aligned
|
||||
PASS: failed initial settings promotion leaves defaults writable after restart; prior/fallback saves survive copy, readback and mismatch faults
|
||||
PASS: real-carrier shortest-hop paths, 500m boundary, three-hop cap, no-manpack failure, three-second originating-call interruption, unchanged downlink hop timing, completed-hop survival, interrupted final uplink, radio loss, capture, motion, timeout, reset and network bounds
|
||||
PASS: migrated aircraft availability policy (controller migration still pending)
|
||||
PASS: migrated salvo offsets are deterministic, spread and bounded (native firing and complete salvo qualification are separate)
|
||||
PASS: 35 prefabs have unique saved root entity IDs
|
||||
PASS: 299 original script entries tracked across 18 unfinished responsibilities
|
||||
PASS: 1 isolated addons, acyclic dependencies, 58 unique resources, exact vanilla/authorized external references
|
||||
All repository checks passed. Native compilation and multiplayer tests are separate requirements.
|
||||
Suite exit 0 programs 294 unchanged True
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c": "b59016d83cb9d40136b98b4e0067ea490bc553574fc53603593efd5522dbed20",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c": "a37edcf66e43945f331db47fe81cb2e0e767a7aaf8115244dcf9e6295932be98",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": "60d0fef83f43eea0501bf5fe46f369556e22acda6d034908b5965d8820b28893",
|
||||
"tests/test_hq_mounted_intercept_pair.py": "a8d758b1ff20bb6d1a95a9ff1d38f687e43c819c279f5ea9692e84f766dc4fb9",
|
||||
"tests/test_convoy_intercept_pair.py": "52b55727b7c2441d43530efa701045b9439b9175c71fbd2ae7577f851d2c98ec",
|
||||
"tests/test_hq_mounted_dispatch.py": "50e8973b730c34a54775e7ba5ae2a72c9a6a101bc9b14a1521c4b5c6d401552e",
|
||||
"tests/test_convoy_column.py": "8c5de7616d4f8c3ed15bcc29bbb57f04dcaaca0626683b81547e2170fc5f9a62",
|
||||
"tools/check.py": "53727a193b89ec4682abfcc429fa73b49cda279510e4747bb91803684d66b342",
|
||||
"docs/mounted-interception.md": "20f80f34919c32535c1d57da7894ecadee6699f7f17b1993a32bed5837342bec"
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
Test audio routed to silent output and muted (stream 731950)
|
||||
{
|
||||
"stopped_unix": 1790982658.1944292,
|
||||
"console_sha256": "c28e72a7c00476445292b5acd0b936fc9aaa247ce77ba1badec1291c06f12b1a",
|
||||
"end_source_sha256": "7248005290380f3f2c7353fbda78240d267ad1d7a57bd2956f48a468dfb0f319",
|
||||
"source_unchanged_after_run": true,
|
||||
"source_provenance": "launch_snapshot",
|
||||
"launch_id": "203c8182-4d98-469f-b683-4f4514a6d012",
|
||||
"launch_source_manifest": "launch-source-sha256.json",
|
||||
"launch_source_metadata": "launch-source.json",
|
||||
"launch_source_sha256": "7248005290380f3f2c7353fbda78240d267ad1d7a57bd2956f48a468dfb0f319",
|
||||
"external_addons_unchanged_after_run": true,
|
||||
"external_addon_count": 7,
|
||||
"external_addon_inputs": "external-addons-inputs.json",
|
||||
"started_unix": 1790982646.6522918,
|
||||
"result": "test-failed",
|
||||
"result_before_cleanup": "test-failed",
|
||||
"cleanup_confirmed": true,
|
||||
"cleanup_report": {
|
||||
"cleanup_confirmed": true,
|
||||
"idle_verified": true,
|
||||
"errors": [],
|
||||
"orphan_cleanup": {
|
||||
"attempted": true,
|
||||
"report": {
|
||||
"scope": "/tmp/erri-headless-compat/pfx",
|
||||
"candidates": [],
|
||||
"term_sent": [],
|
||||
"kill_sent": [],
|
||||
"cleared": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"runner_error": null,
|
||||
"session": "/tmp/erri-pair-normal-0054/logs/logs_2026-10-03_01-10-48",
|
||||
"rounds_required": 1,
|
||||
"project": "chat",
|
||||
"display": "headless Gamescope 1280x720/30Hz",
|
||||
"dedicated_server": false,
|
||||
"audio": "silent sink and per-test-stream mute"
|
||||
}
|
||||
@@ -0,0 +1,978 @@
|
||||
{
|
||||
"addons/core/AI/BehaviorTrees/ERR/MortarRapidFire.bt": "16b263bcb2fb3ae361367a600753a829425620d2e6b350a891c51fba47437ab6",
|
||||
"addons/core/AI/BehaviorTrees/ERR/MortarRapidFire.bt.meta": "f2a44ece28891240e6bfa1d01af29428adcadbe82c80e287eb33cd5e58f1f90a",
|
||||
"addons/core/Configs/ERR/AdminMenus.conf": "a7f889f260e43656136fc159342129e5f48748d28c426f65f9faac37b624cf94",
|
||||
"addons/core/Configs/ERR/AdminMenus.conf.meta": "5faf5ddc0a51c1d0f82d60af00d91edc22fe9c39aac50d8d04b772a2c9271be1",
|
||||
"addons/core/Configs/ERR/Content/Registry.conf": "546a5a407c9e6c25c9157ae4392898fa3f40c04c7956649e62201f9ac0ba187c",
|
||||
"addons/core/Configs/ERR/Content/Registry.conf.meta": "046aa988bf8e972afcf7a60b6f2312f605f016f98625dbbd9cd75ebf9ea88800",
|
||||
"addons/core/Configs/ERR/EveronSites.conf": "b479000b36154a6a3af6b519b086251d981ef63cb86a973d00529499cfd4238c",
|
||||
"addons/core/Configs/ERR/EveronSites.conf.meta": "d5fe68025ef4e7c1e16082cf5873b7c7172eacbfaef4ab29b7e0e3c98f8dca56",
|
||||
"addons/core/Configs/ERR/Factions/MissionVehicles.conf": "5f74f4a7089c7e7b54ad62463159ce8f0bab25a6b85268dc3872ea6001e048fb",
|
||||
"addons/core/Configs/ERR/Factions/MissionVehicles.conf.meta": "00c368b6271d3440cc8bf627b03a384d247635723923fc5ad2104b8b4007b035",
|
||||
"addons/core/Configs/ERR/Factions/US.conf": "4c3da063abb6e3ee39a6e4e0c5a9ffd5eae9d209d58470ea18b45dba5528db1b",
|
||||
"addons/core/Configs/ERR/Factions/US.conf.meta": "a05469aef028e9e17eba545e81e2af2887b59f0fef17dac51a23c7b8155e381b",
|
||||
"addons/core/Configs/ERR/Factions/USSR.conf": "407ea27f80a64c5184c089d43cdd0bdabfc5974c84f1d2a245081fdf2afa3f30",
|
||||
"addons/core/Configs/ERR/Factions/USSR.conf.meta": "057397d97096346791b51b7b13fbe18af218bcf78e9984d9c01b49a2403de611",
|
||||
"addons/core/Configs/ERR/GameOverScreens.conf": "45231061911a1462d45fb6db0fecc2b0793f17b5ae71017174954ff4fbd20236",
|
||||
"addons/core/Configs/ERR/GameOverScreens.conf.meta": "eb654b745c0b97b41c991694569ef4877a3c2315e0826f9556337cd68690843e",
|
||||
"addons/core/Configs/ERR/ObserverInput.conf": "e898d645911e85b6ba902366cb23890d8bb14bbd2c485102e6d49ea728e9b054",
|
||||
"addons/core/Configs/ERR/ObserverInput.conf.meta": "9714dce6c38ee81bcc513c8ebef51d7af63fd15bcdda2e40946937c625dc3ba4",
|
||||
"addons/core/Configs/ERR/Tests/LegacySites.conf": "3864eb638d0ed4dd313013d2d787f975ae6ce4bbe238569919f76b73d5f7ca42",
|
||||
"addons/core/Configs/ERR/Tests/LegacySites.conf.meta": "097ac7daef4fc18344699a8471fdd77f9d158f8c70bab394068d7a08e4777ac0",
|
||||
"addons/core/Configs/ERR/VanillaFactions.conf": "db890b61b48ee15484041fd2245a862d9fa9e7ba21b1229c281ff37c448a35b1",
|
||||
"addons/core/Configs/ERR/VanillaFactions.conf.meta": "ac77725c0c21974991ef47a466b88122e939de8b46fd23e68889e7674cbb2bfd",
|
||||
"addons/core/Materials/ERRM_MapRenderTarget.emat": "9877c5b175c5f30dd80bea36c711b21ab38219aa7075adf9ae79932e1d4c76bc",
|
||||
"addons/core/Materials/ERRM_MapRenderTarget.emat.meta": "5ee1a705f8011a44020ca2691f5ccc17d44bf596fef9a36e98d1f267eb58fab0",
|
||||
"addons/core/Missions/ERR_Everon.conf": "936619df7789a6b4b80bd763f702526e1145ab4d5c300516c07e7e70dfe57d92",
|
||||
"addons/core/Missions/ERR_Everon.conf.meta": "0a23c98bf83873536c65fa17d38dea33a1d7b9e4ab210f90633ff688abcc551f",
|
||||
"addons/core/Missions/ERR_Everon_Base.conf": "b46e1f4aeef383a8dbe7ef2b5c12a55a8111856c52ba103a1e508c01b82dfc61",
|
||||
"addons/core/Missions/ERR_Everon_Base.conf.meta": "9b16267c644d815bcf98a3a3aaff30b93d7224fb24d3a2bac66730a28011f317",
|
||||
"addons/core/Prefabs/ERR/AssaultParachute.et": "b556997989605e2a05982418b7e9f26d12d427eb3ea6e3bc50e85c67ea7aa4b8",
|
||||
"addons/core/Prefabs/ERR/AssaultParachute.et.meta": "e9dea2d533ad1ce6ce689e0e2815c97a6382e1b90c35ce1efa88a885527e8e18",
|
||||
"addons/core/Prefabs/ERR/ConvoyCrew.et": "29a525a29605386dfcf292dc2316e9fc7650ce39db9fbaa4ce4372ad0de66db4",
|
||||
"addons/core/Prefabs/ERR/ConvoyCrew.et.meta": "0a909a15b7dcca34202ded136b3bdbbe8898153c9589e1cce1388433d362da02",
|
||||
"addons/core/Prefabs/ERR/Epinephrine.et": "e09c70b3d47a4dc5b92fd332c8222b99554b28a8a0c93cb5caac18eec4501571",
|
||||
"addons/core/Prefabs/ERR/Epinephrine.et.meta": "f1861f0b0ccace1b7b3d9c29768e37a83bf0c6fae5a411b1bd61d6b2eec7ed89",
|
||||
"addons/core/Prefabs/ERR/Extraction.et": "4d24c7811415c06386205e5bf5c132a23fcf21e96c75c7657ccd5f3ac914ac50",
|
||||
"addons/core/Prefabs/ERR/Extraction.et.meta": "c2a838633001e2e3a202d39cb33aa744ebcd996017ad032359069641b5d13853",
|
||||
"addons/core/Prefabs/ERR/FactionManager.et": "5a7b330f33956cc1781c8dc843e8186f2d37a98653ab36e7c42b9019df3c89f5",
|
||||
"addons/core/Prefabs/ERR/FactionManager.et.meta": "9bf53d5e4bcbe60dd1a4ece1409fc23c349055c860708620f5f6e9716290a97d",
|
||||
"addons/core/Prefabs/ERR/FieldCover.et": "e403d5cb3a5966f0cea81f15e0e825d482ba1a7a27b12498319ba45a414963b0",
|
||||
"addons/core/Prefabs/ERR/FieldCover.et.meta": "83461ffa76244e1d69f20b053a9741ccdf7d127009182bd540a7e5ba6150c3d9",
|
||||
"addons/core/Prefabs/ERR/GameMode.et": "4d3e5e2df28cbb8292c436ae05d4fc7e5ca789a123450a6968d9a718b6ecef20",
|
||||
"addons/core/Prefabs/ERR/GameMode.et.meta": "106769cd34525c9d440057072836cb7401a6752148c40edd7fe61c03bd58328c",
|
||||
"addons/core/Prefabs/ERR/Items/EmergencyRepairKit.et": "0157bc235eeb78a0e1373b561fd22a1dcb225c833ff7c2d4abae688bca0ec92a",
|
||||
"addons/core/Prefabs/ERR/Items/EmergencyRepairKit.et.meta": "da6f065cb2414b5a4cbe9ef0f0b1295b2466f26ad406c0bddb83845d4598efb3",
|
||||
"addons/core/Prefabs/ERR/MortarCrew.et": "21086e886fe3199624208c9a866f883ec109f7d08746c2b56b497e59857fb656",
|
||||
"addons/core/Prefabs/ERR/MortarCrew.et.meta": "fdfd4fcd21a67c3d698fa4aadc4e5c76e665cebf4be86013bc389e493fab63cc",
|
||||
"addons/core/Prefabs/ERR/ObjectiveDocument.et": "5118ecaf9d2038eea44a4b785f05d2e78781dc1387249aa58a6782c7e6752e14",
|
||||
"addons/core/Prefabs/ERR/ObjectiveDocument.et.meta": "eaba9903be147b98a1106e2273e2270773df73b9b22f2cd4e20136a8e281382e",
|
||||
"addons/core/Prefabs/ERR/Patrol.et": "d3eca8fe9197c57a27a285cdb3baf168b037b198d92b39df28a338ae2cfd0c74",
|
||||
"addons/core/Prefabs/ERR/Patrol.et.meta": "7c5da568fa2fcf0709ee2959253cb068d7c7ed7c2467af3b247a7e70efd96b69",
|
||||
"addons/core/Prefabs/ERR/Prison.et": "c0ca5219aa2d6322ba7d094b92000640b259112ff6611a745c0713c8875d09d4",
|
||||
"addons/core/Prefabs/ERR/Prison.et.meta": "2e426ef3de68f4107907854b51e818bc11d84649a01c538e3f18dd9ebec766bb",
|
||||
"addons/core/Prefabs/ERR/PrisonBackpack.et": "ae750f08fc9af645561f59d7e5809bd22f4cec26ca8fa6409d09730765958c91",
|
||||
"addons/core/Prefabs/ERR/PrisonBackpack.et.meta": "7da6aa45467250edc910ad7230dc244d1b041e8a6f55881eb3747ce51f9212ba",
|
||||
"addons/core/Prefabs/ERR/PrisonDoor.et": "b0ff94443fe334032113a663917de23f920a8b73ffadc9c8e9f11b545022b3d2",
|
||||
"addons/core/Prefabs/ERR/PrisonDoor.et.meta": "b8f3db52bf186426026668fbb9d633c3cbd4d438ab301fba880398f6a0e781c2",
|
||||
"addons/core/Prefabs/ERR/PrisonPatrol.et": "dc88c31cf5c415573a874f74e13572f0add9e6122c8fcad507fe82860d789c47",
|
||||
"addons/core/Prefabs/ERR/PrisonPatrol.et.meta": "5528e0d778dce5ed39c3e504c6427625527127b23269d20fbf12ca2bffe52ffe",
|
||||
"addons/core/Prefabs/ERR/PrisonWall.et": "9b410b7c85bb001be4601ad93f750e17b8318b850306d84f29c4dc1b02723d7c",
|
||||
"addons/core/Prefabs/ERR/PrisonWall.et.meta": "0294bc5dbb56f90f8153d62459bb0a6ad91d76a479812bbec9c7db182fdb58d6",
|
||||
"addons/core/Prefabs/ERR/Prisoner.et": "e6d345ac98ad9ec742bf6c3b8686839c3256df9833d31bb4bdf605cd41d0ba5c",
|
||||
"addons/core/Prefabs/ERR/Prisoner.et.meta": "974278ba49fafeed70b619a0067bbc70ae4d534be441c812ce2bef4a5fb3c022",
|
||||
"addons/core/Prefabs/ERR/Radio.et": "99b1d80b49d1e7f9d30aa4283772a36fd66e962369f288f5fe329c7fc911bb1b",
|
||||
"addons/core/Prefabs/ERR/Radio.et.meta": "bc91137b68ba550217914fb8892bb44d7e1920e4a1086f3e506623832a44212f",
|
||||
"addons/core/Prefabs/ERR/RadioCamp.et": "5e6216bf8e6e160da5d9f966c8f7c2081bf6dd16dfac1b89d682a14b3b8a6716",
|
||||
"addons/core/Prefabs/ERR/RadioCamp.et.meta": "3cb8d3d553ab6d8d111a74f721cf4e69ee1011cbb90248b83cdc7275697276bd",
|
||||
"addons/core/Prefabs/ERR/ReconCrew.et": "33953379ceb725bcd0cbe505cd643c94adaed17dbbfa95dadd404d6b8e345715",
|
||||
"addons/core/Prefabs/ERR/ReconCrew.et.meta": "2653e8c5b1cdb857de5badd5959ddd9826a6a298ab3c166628dbb835d7104d90",
|
||||
"addons/core/Prefabs/ERR/ReconHelicopter.et": "c609ee59ab145e0a1936f95726b86df643f5cc425c43924029e9c1abc69ebbd0",
|
||||
"addons/core/Prefabs/ERR/ReconHelicopter.et.meta": "d80f1c3d6a6f9104562b89729996c1c92ad240f08cdcf48d4c390a09d2047c08",
|
||||
"addons/core/Prefabs/ERR/ReconPilotEast.et": "d961f329823b991eafed80d085de1bd89b44a4575be98d8dac385d6b77741ba2",
|
||||
"addons/core/Prefabs/ERR/ReconPilotEast.et.meta": "d73df3c896359a54514d2efcd851f579feca7a2619b76c2a38f607649a31a4f1",
|
||||
"addons/core/Prefabs/ERR/ReconPilotWest.et": "8f8221927269352e55c8026ee5b00e026c281c79af6684b386f24e7dc4675fb8",
|
||||
"addons/core/Prefabs/ERR/ReconPilotWest.et.meta": "4785f23bb492c6af420ee7896ddbfc9767e0db1e06de95207df2dc949a1b1814",
|
||||
"addons/core/Prefabs/ERR/ScoutEast.et": "b4593aab494c4f21637757b3daff4f349da77b4c48ec5e8a4a2917a6a12cf811",
|
||||
"addons/core/Prefabs/ERR/ScoutEast.et.meta": "7c72af632537f687e92f30b52d97fc7d70dadd39d33f37e0b1004f9596f55012",
|
||||
"addons/core/Prefabs/ERR/ScoutGroup.et": "8dc0fce8ee3decc1c367411b00b6d8518df9d8458281cec0881464a2ce906af0",
|
||||
"addons/core/Prefabs/ERR/ScoutGroup.et.meta": "62e43fd152ce57aa4ceb2187a49e4aecdeb9f428b3a1e8318f86bb83dbf8a259",
|
||||
"addons/core/Prefabs/ERR/ScoutRadioPack.et": "f1c4911e2556c0e0caa8b896982190c5e7d0433e850b4610e19442caf9117607",
|
||||
"addons/core/Prefabs/ERR/ScoutRadioPack.et.meta": "825506e3d814d30a52d5dbf64124470641c07a0453543ba71426841e6c783635",
|
||||
"addons/core/Prefabs/ERR/ScoutWest.et": "2681b5a3f8621adb44195f0dbca6f3e79ef7decad8f9d0dd0e1a801ec2549627",
|
||||
"addons/core/Prefabs/ERR/ScoutWest.et.meta": "22a6607b4b8fe606ca75a634a0b2ac3178e7bedae7f9ff01e7a4989f3b39a3da",
|
||||
"addons/core/Prefabs/ERR/SpectatorCamera.et": "cd2dcaca0df4c39816e582c594591a535525ed77827ffa3de24d27b79996544d",
|
||||
"addons/core/Prefabs/ERR/SpectatorCamera.et.meta": "2a16558f93915e31908a674509e9c89976470f78a84a1174a64b94b68b8f77ad",
|
||||
"addons/core/Prefabs/ERR/SupplyCache.et": "4c97b0514fe7e723046f40f0b396f6573b12eb9981ca5df70b9e8aa3cf29323e",
|
||||
"addons/core/Prefabs/ERR/SupplyCache.et.meta": "2f5de3a4aab1e9d0e35521f48dc57b95bd6e449c383dec4f1c258e10295904cb",
|
||||
"addons/core/Prefabs/ERR/SupplySite.et": "f33681281d26676fac88736091834d5a7b87fe0fa01883582542df964dcfc694",
|
||||
"addons/core/Prefabs/ERR/SupplySite.et.meta": "b41944b12eb3528ff7a3ac94258b18c66ef161aa6a38894aee300a1ac4798630",
|
||||
"addons/core/Prefabs/ERR/TerrainConnector.et": "cf9cb28f5a3f41931380864a29ac979aa1d5e843456f564ac7a7de78f88e1aa8",
|
||||
"addons/core/Prefabs/ERR/TerrainConnector.et.meta": "9f37c979ae282aabca29ce84beb0db851ee8b9a19f7f3a64d387c175616127f3",
|
||||
"addons/core/Prefabs/ERR/TerrainTakeover.et": "6206fe2eb4e2c6c1a9e3ddf2898535ae08981c115adc6ddd926017ac20d4b535",
|
||||
"addons/core/Prefabs/ERR/TerrainTakeover.et.meta": "64e953e378ab1d2a75ee7246f876f4ef93ae8a4e3fddb5002d7ba37268cb959d",
|
||||
"addons/core/Prefabs/ERR/Vehicles/SupplyUAZ.et": "29f157849f413261494a63d4d5b0a97e8bbe61cb076efcd59d6c940653f23352",
|
||||
"addons/core/Prefabs/ERR/Vehicles/SupplyUAZ.et.meta": "544c94cf1c26757a10654de7856cd6cc48b821ce3bdc02add13cfe6514269915",
|
||||
"addons/core/Prefabs/ERR/Vehicles/SupplyUAZRearSeats.et": "cd4acdcecad299445952aea0adefc85f2b9e9b43f8186f100e0505c5621b3656",
|
||||
"addons/core/Prefabs/ERR/Vehicles/SupplyUAZRearSeats.et.meta": "123955da03462befb90e293b3bb856d49cc761166611b38ff47cb48ec2fbe95c",
|
||||
"addons/core/Prefabs/Items/Equipment/ERRM_SharedFieldMap.et": "30dee8e9eab459ea32a7d835a9fb89d43df47708057cab829f067b25a24cd581",
|
||||
"addons/core/Prefabs/Items/Equipment/ERRM_SharedFieldMap.et.meta": "59093e9072c50d99c86da7609c87541016da5bcf92b98d99db4c524e1c8e0e8d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Actions.c": "cabb21413c90b252d29708b62228d0679c51c76e39ee0e483953ad7061b2b9d6",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Actors.c": "980d30964845600a261693cc5c5553e1472e8e54867bdc06133ff5c7b36c9128",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AdminMenu.c": "3b96a99832ed40002598a186dd8de1d21410c2e690df37a6319fdbeeeb4eeab0",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AdminPlayerTools.c": "56780d78bf423f54f482d8620f821b8d3eb0199194482b4f12e37f5ec3f55ef5",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AdminProvider.c": "62e05aceb49da07fed7aa89fb6b2f8eb3efc373ac09eff286fba983c3ad570eb",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AdminService.c": "c3e008c8bbf538c5bca3a82a975decc7a283a38543a9d2639d90a81ce592c51e",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AdminSession.c": "89d92f138a201ad7e752e027ba3930a2e0bf647d7b3a2c576346c1467f8bea3a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AirfieldPlacement.c": "0c2f4d31a2abc8f3f90bda20546543f2a1cf9306e9bf93351dda1883b990770e",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_CasualtyGroundSupport.c": "26f0c7ebe8563bb022f7c383674e7d8997b0cc35c7ea321d39d74f2493c1ed9d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_CasualtyLoadingFeedback.c": "e46988b92243df19a5400eca7406d6969b8b1174d8e3efad48fa0a936cbfdd07",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_CasualtyLoadingGameMode.c": "f4cf19440583ac24346831a2d5f1d7f734c031e46dc266c499972329dd0533cf",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_CasualtyLoadingOwner.c": "6346a05ddda030f7d9b6f026f977d5f018ee16002fa519e8c49dcd700c323dc7",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_CasualtyLoadingService.c": "e6f22813f7a04e2c0ea000d054407191f2be8badfa6c51e062e9dbf84b95c67b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ChatClient.c": "4bf937ff74e0483c17fa0103f73bfee3afd8f67a37c0c248b08772a17bfcc349",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ContentDefinition.c": "bc1dd4ed27ec894cf4bd617fb226cb8140b544aaa9ef6d8654e815d81e7f6fd0",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ContentMenu.c": "c82b173496e7524b4570b2afc5493cb970252a19aed740a53011988db7700a01",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ContentPolicy.c": "3a4987345d5a7159234a7e1a08bf5ac40dd5a23031e9b1953fc43a0501d9924b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ContentSettings.c": "572cc36f88255a37e8f0e9f284fb96e07c6c4032506b34b8c77d688f02785400",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_DebugAI.c": "bc5529c60591a6c36c39cdb753b1af948690fc3c3c725f38a8d595b41a499235",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_DebugIntel.c": "a9f6e7e42a3214879a21052e897f9afc0653c12ff15d5bee7766e26a1af6ab17",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_DiscoveryMap.c": "dd350bdda1bdf427ab8cefab623fe60cce5332b062d1a29686fe52f73e9e83f1",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_DiscoveryService.c": "34458b3a4f7062375c9c5b5599308e9cea67fc4a316420836f6694ca2c646ffc",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_DocumentComponent.c": "885c8ab4fc9e64c3f8b40056536096c8c32b011cb340b25ca00353a96da15c69",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_EnvironmentService.c": "c0fae5bf51ccf507bc84d44adc2d1dff021cd83653c0468b7645a4f343e3fc8d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Epinephrine.c": "e04c53c2f665997023b2daee744265cd22f4f0101444e4d9b0efbd114f74fa9a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_EscapeGameOverScreenInfo.c": "6e3c65e55b05cf205040c310b9086faa41b88582c7f9faf49c010afd59da5b07",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ExtractionPlacement.c": "6d388fe21f08b25cbdb5dfa4e198d94a4c80c5092f8d17630a4654e18bf8b3f5",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ExtractionService.c": "91357099b4e0401196b67d48963be54be30e56bdba57cdac67dc20b8ebb883e9",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_FactionCatalog.c": "a5a8c53acf21259e14f59cb919535910f30dabff5a9179c664bc6a7a4d744d6e",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_FactionMenu.c": "3549339d7796e67fc646680493c226e545b6fb95daa0270af9901795bdf8ed50",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_FactionSelection.c": "077c09491f4444fc58f093975fe9b3c294ea1fc966abef847ee5f13aa795ee29",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ForceLedger.c": "0ecaf1c5a52568e751678b0b1236e91e6adb538f8fbf010d38b61a312d1bf253",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ForceSimulation.c": "2bbb8888c752a3ffc042f3059937526d25ae288af5b4db024cc683025a2605ce",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_GameMode.c": "89a29c03ba225acbbb9fa3c58a721aced91a77bc0c77046d697005a6d7c48bb9",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_GeneratedSites.c": "4a23058f27e3ab074df6f8f7bb6f08a8fd26fbadb8943c6fb8d64dcc9c221751",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_InitialGuardRules.c": "8c9d6b1dc4c2af96284e34f487eebb5a221091016ef108006dec60b4e3836558",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Inventory.c": "b63e679d5b650b63a7a91dfc3f42a62f4be58b0338f6df3313e66d29a93bebf6",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_InventoryLayout.c": "1af5df45a0f06d19a017954e9bf4b2d722e2d2389f8ffdeb1d024435b9a08be8",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ItemPreferences.c": "1c4bc25a6ce9b5ee10f82592f955b0019d13ddb4084a783fadb98d267bd0f4fc",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ItemStationTeardown.c": "102e61216e86e47ad243cc19f6f66da1fcd8cc77f8799354ceaeb4a6dc74622e",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_JournalText.c": "87a6c19429f12c5630ed73170ca9a8a6d2255d6cd38b130166a78aacd15f2de0",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_LoadCasualtyAction.c": "1521af55a34110ce1c971b5226a618a9c6d931622abaae4946d9c0d27c05a273",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_LocalAdmission.c": "a076922d1ae966cf5cc67b198166ca6b1288358fb7f641207a4681e6a610a2ae",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Location.c": "5b0b058130ccac02bdc1dcd6474d51a6f45da4dd52ed6c37906692fca3f194b1",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_MissionRules.c": "f7abc38ad8fdc375ce7fa63a65f0bade027d22b37e09de5e3f94ba8c3c63cc41",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeAdminMenuProbe.c": "ec8d04b6550b39b9688ef67b40cb2f14a9d98c8fff62b6dfb8d4a43e19b4e4c6",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeAdminSettingsProbe.c": "73e58561e08e50008d33f81eed606dd4e8109fb69a627e892d28ca468d641f7d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeAggressionProbe.c": "85a34cf60018e90d974cb67ab0f358f3073b6b9f02c563ec200e062f1d249e3b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeAirfieldMarkersProbe.c": "593e59664dbd7330b52674e5d4cc7b70db588cb75a1259ed808b74602501d541",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeBaseSmoke.c": "e811e985048e07365569acde6b5f045d57e9bbf13927f85b74e6e347fd2ef571",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeCasualtyGroundProbe.c": "41f40db3735f8aa535c0061458d9892dcfe972222b8a17238e67b65633c46bc3",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeCasualtyLoadProbe.c": "94c533deea654ec546df3f1ebb7cd213e6cd79b1125d99bab5303ef8e87b018b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeCasualtyRemoveProbe.c": "4cfc06f6d43b709b63caae714155ffc562502e884f49a9825623c6f46972ad36",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeCasualtyServiceProbe.c": "02a9d70d1f4ba20326df199ba41489cfbae12b7bb0aab441361421cee38ad9ea",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeContentProbe.c": "cb0ccfe1e1575f9ed80a8054058b471f3543378db059e6af2fed8eb42a262638",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeContractsProbe.c": "421e4b901afe6118b6bf44e8f678db8ccdf1c83b39da0cf816baa36f7275ccdd",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeDefinitionProbe.c": "904d8d4c3e7b91d49dadfe495fc7b4f02f300e4aaf7efa348df5ca17ec4a6e8a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeDiscoveryMapProbe.c": "9ed32d391d08d937ff4f9f33bd11c3a204ce9a2230c2d72b6761e0320b31f24f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeEndProbe.c": "2dec80c7e247ec5c257407f95d9d5edd42fe0cc44d6af9f724f6ebd10d8a355f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeEnemyLoadoutProbe.c": "0885ca69f6fa7f53414ce1792cfc3c49916886b8173783c634135c13c5d37429",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeEnvironmentProbe.c": "9e2e66ac5d9778a1c65445348e3300cbb90b68b3f669f3ddf1d2b78a858430eb",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeEnvironmentRestartProbe.c": "7a836a206381b8e8de24c92baf141beb30575723675dd6948d738169ed26b507",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeExtractionProbe.c": "4b09385f73206de004f4f3c38a6192fd658a0d2012482dc5a84dff1599ceb972",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeFactionCatalogProbe.c": "e3011fa47b67122d7b2c23af8f91b7a217599d6cc114c4f9de628744b9c948d3",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeFactionSelectionProbe.c": "946879d822e7be2d48e03a3140aa1aad3e3d44dd18507fa3cdedb4c6d7dd81b9",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeFlightFootprint.c": "a8862a04ac784426f431e2e9cd3f1f19f820b38a081674d29549b8b00277c72d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeFlightInputDiagnostics.c": "e231dcbdaefe81beb3bd587cb73be8fb3d3184bc86d18dbaa30a43ca4ba5dcc4",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeGeneratedSitesProbe.c": "83ab84ca4de7b545963896ed9d14e6545c23a2b940515730d74acc2f6b3dd18d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeGuardCancelProbe.c": "bfa1c65cb039e19c3d1891e4a3fd33469e974c97cc7f42128efc31a8f587d514",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeInfantryCombatProbe.c": "3e4c4382ab91f470357268927b954fd101685df6b2244df483f0422d6c0d7da2",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeItemStationTeardownProbe.c": "028588a49fa9207087cd27ec9396979cd932daa071e2490bd0262cd71dae30d9",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeLifecycleProbe.c": "b613a5dbe81c2032fd02d31070ae5631166d14d0b02cdbec3b63b782f0bbc115",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeLocalAdmissionProbe.c": "29c860ca3e51cdaa463f3c23a98ff45ee0499ce9bbc322046e0efbed587f1e09",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeMedicalCombatProbe.c": "3b9c557b249728c518f9d6b5cb24b725ecc32a69ae12df069cdfb0da70a80b95",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeMedicalProbe.c": "4b73e7cf35e9c8fddb089678a320ea58272e1b6611318d0a762754c171eff785",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeObjectiveLossProbe.c": "6b80fd317a8c6e8583bd4337870838a86251e3b8a681d0aa07e8b52e22abfc48",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeObjectiveMapProbe.c": "2d07b895cb74af58454605fb1c536a566733e3001a9375dd0677d5f274bf2a46",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeObjectiveProbe.c": "71fbd2479a70048f83d8b262ad1452f427be2c0afc8b071ef13bff477b9dc29b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeOpeningToolsProbe.c": "0c5d6b96ed5a7cb1cf0d098d534dc7190449ed09339062e6b584729d53a61b4c",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeOptionalSiteProbe.c": "19fc19e22348e6213827f620f4fdb5dc26144ea2ce0032de773e79c1fb155a26",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativePatrolProbe.c": "5a2af71cc4cfccc7dd7c23584398a38290eba1a8894cd46f6967252ff3a7087c",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativePoweredFlightProbe.c": "7ae5d6f85bbbebec214c66bfdf6f53b93c802f109fcd5bcfbad82c5d61d6cb9f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativePreparationStatusProbe.c": "456f4d7c3272b89d5f4c6713617baa5596d846994152356fee9a659839bcd8b5",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativePrisonBreachProbe.c": "4889e0d3799a6a21b9f84bcb2f2ae78dce16269cccd1329f8d1fcc1a23e7c22c",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativePrisonerProbe.c": "e2d1ffe5d269add55124601ff0d1dc032a398c7d51010736e6f0e4cbeb38ae86",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeProgressionIdentityProbe.c": "1d3fe20bce761a1082964bca9bad9ce79d1fa8b04db9b5053bae98da380ae560",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeRadioCampProbe.c": "67f1a088f9b20e5f0f11ed3aa083ffa772bffd7c74d85b7a307344ac3d47e09a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeRadioDefenceProbe.c": "ff1e27a0ce75b123a86e7df31354831880ca62ea90b6c5d7c60d3ba168e1fff0",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeRadioInteractionProbe.c": "7b27552d233813894f65c08eb9f04b547c4ca9ff96a2370a763cb94bd4a555c3",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeRecoveryProbe.c": "d6691f22432b3ce37d07d448aeb3ce7feb5cd09e852e0167b5e4676afd24bb21",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeResultProbe.c": "c9a89cae067f221b9f33965b4d3ab311b78ba79ea383ca65032c4001df44b472",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeSiteGarrisonsProbe.c": "1a5ff8747d7501862085b5e9162043fee3b05e82c73c516ad8093aaada53dc60",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeSiteSurvey.c": "ac0203753ade0cdff6d483132797a80e4520f14546c3f9dea288281cd4cd9c4f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeStationTeardownDiagnostic.c": "172248540932174341882861bb78a9720a6cf89981fcdf9dffe63f7fd4023f2c",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeTimeProbe.c": "4a2c6ffd947334a0a71eac083d10c853c524b00042e9ea0987497c4cee60c3d5",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeTransportProbe.c": "7a3fe04aa1c5ed1061ff63d9d5180e8b925932b62d76c1ffb49bd3dc23530993",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeTransportSurvey.c": "816df9a2a9072f9f7db75e56168d8a8fa1e665ee8f7dfa79af011b354254909d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeVehicleSecurityProbe.c": "59bce863142e4fba4b6fb2f544bb8a58061f0e99b1a38e5ac291cea919563763",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ObjectiveDetails.c": "628ebd166dcb5544c2d18dce489619984887ee18296cf2446fc6d894f89dd5bf",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ObjectiveMapPresentation.c": "f189f8987244e10df61ac577bbbc952ed5c4e6c431a8e3cd192a10fe2e8c3bec",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ObjectiveText.c": "8d75b35ab977be0cb1a2e3d3d1cb8ef875cdbfba69c35784c3d195451ef944c6",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Objectives.c": "b9cd5bb3b193c20401175c4328405be1b121e8a60b7b5999910b6a71f834af8a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c": "682aa5549470c69fc72d261fbfcf9f7a04a41a66e0101b269ccc00bc282ad05d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_OrdinaryGateObserver.c": "1e376ca83c0005cf532539e14d7f47363c47b7c5b03fe3d55405a59208705fd7",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_OrdinaryInputObserver.c": "808a46cc1e4b31b65dbbbcdd55493f594ebb3974bbec38b4a2402334c653c0ef",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_OrdinaryLookObserver.c": "d5a1b636938d65e9d11c78b31487f2924400c34d98e2ec812954a2a224da7dc0",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_OrdinaryScenarioLaunch.c": "a4e7e56d174ebd15b4730202dae4c8ed06144f3f27c23ed49c99e2d297dc421a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerAdminCommands.c": "eca9baa3b07da48a740851583540840f8bf5f65f7be854902a3778f76967faad",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerEscapeResult.c": "3124ab4d2d6d471409a47aba0af528790734df53243c03fe0cdac3e407047c14",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerGuide.c": "e49bab76a015ab545088af7e5eaa3cc45811907db4e5b1c5fa8c18afef34b62f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerMedical.c": "270454d50235bc28df9bba4fde2689934578874c4eca9560afb9f0f6324d7215",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerRoster.c": "c038dac0f749bd29ebc35a4e952904b41a34417ba738904d5efcd1833a263e0b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PreparationStatus.c": "43a406822a0361f916abc363f27e0c578ee94855036981d876bf47c68233c71a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonAI.c": "8709753e86dc2ca777183f08cb8fbb9851e0c0de495ca01520ebcb1b3dfa59b9",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonEscapeService.c": "535cf17e21fc8e62716c7d9da88d976ee2f526501c6a4752b822f1252e742bf4",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonPatrol.c": "284618391ee2da84dbcda4a99ca3c072eae3d67b8a9ef40a86827b9ea00810f9",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_RadioDefenceService.c": "f5f0bf1352141f933d0edb5630e30541e001dc07e1d2c8529b29cc159c126b13",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_RadioFeedback.c": "545262e65e9b3645d9e514a0521e442fe08f0c2e4e7c83ba638c5105d09818db",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_RecoveryService.c": "86f9a7769e3ccb19fc61127fe2fee2ad0dd825487a5cd59f334de8b9741c93cf",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Resources.c": "d0169aac6fd0c38928c47a9002ded0fd8ba558ea3314a600e792d307ac1ca3ee",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c": "81bf30d358c7b100248be17cbc2e6beb2f107300c939aede8a92d273eb7dae1f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ServerItemCatalog.c": "e306b9d179b97d0478f821be3ca606c71908a21a4f87085b37a5fd18c4e3575d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ServerItemsMenu.c": "c0c3d9d1cf4615f38c793f1250315bbdb9349f33afa0841851da89433a303c3e",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Settings.c": "1b466886e9f5fbde1de0be20395e3887046c6b5a9a239dae92cecbfb721714c2",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SiteDefinition.c": "2088c45798f0eced702e0abd97550b8d1fc97ebcc34c3d497978b7424f6c670d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SiteDiscoveryRules.c": "dcbce3b7aee89bfc47a50e9de89209758af1f1bb462b99af67a8ad7a26e9f229",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SiteService.c": "210646e679056f4a46ef454a5abe15bd42d48349aa376ccfbb6b3b2ebeac6812",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SiteSquads.c": "183883783a4e8399cf62851a4cc65a5d091d25131bc53b4323cf17286315b2e0",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnClientScene.c": "b56f99a91cdc53bdb93d320d10839339022d7e61e6b101eee8296271abd7d3cb",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnFinalization.c": "c949642366ad124dd2abc4111cd598eb94aa257fbae01b6fb3a0f1882a23e83e",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnLogic.c": "47a39c951be71bfcf1cc50526d507d0f9d8ef2fc09408697b6fe6b0dc5a529e2",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnPlacement.c": "270bf465808d23627a11e720c6632d4fea35d0c679e63aa64046ef8874d68ceb",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnSceneInventory.c": "76b79d5bd9d1f367e1ed19724302b7d49f8b0ede85cf19e89c452582ef651f8f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnSceneLease.c": "20de7f96bacc9037580d88d4424664b4554b2c082d82e7d29913e9dfe390b594",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Supplies.c": "752e8a56b0b8cde33cf532032cb0a203553017b686faac2e19bee06f7a9c0713",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainCatalog.c": "ba2e68543a8c929492d06f8725a743d26849a630d6f8c09be0f23894f92d0990",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainMenu.c": "1da542ef60ff7c1897b1f12432c636ef6a08cc710bac520ef186589d506e9a54",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainPreference.c": "ec25e5c3a5b97527f9cd70341c10d69d7124048089d26883ac2f8c9b3fd2815d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainSelection.c": "ee75cac068b99e7403f5eaabe556152513cf5d973602cc6e6b8967d25402f6ad",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainSites.c": "63ad027dac5bafb9e93805c361cbb27bf82af0567ea181a760ddedea98204c06",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeover.c": "487f1d004aa4f7a5f58c12c37724b26fcd54fc62b4096d48f3a7436d9958274c",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverClient.c": "458b84b3fb31c5a2bd1b7c3d65f25e0aeab91f5505ef0813231435c8a3c7d2b7",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverManagers.c": "5b7661a5617b7124a10d3c18e087a4e2363a967eecc5c87ecc94573f2abc07f2",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverPresentation.c": "d66a29a44e999cc3f2e56ba9c3c6bc936c027747312e08778ce301c1837f2112",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverSettings.c": "da22775930b13e41d4a71409e37af3bb99e4cc1d94bdea0e3b59fbdcd4d40132",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TimeService.c": "63867760876d523a1794f619118a52c5cd387d5d92756cd286b2c16a348a6c7b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_UnconsciousCameraProbe.c": "66b1c4591b0ecc67cb08418a115bb02ed2b0168b7ae970f1e0a1ea335c59dc5b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_UnconsciousWeapon.c": "24d31bc857486638e55c605283c17cde0cdbd3c5c6021eac1ec93611387b10d1",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_VehicleSecurityActions.c": "bd7d0aeb4abcc9d8044e4888f9a92f9e5fc97c098bdfa3bbad57e1a29697a779",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_VehicleSecurityComponent.c": "2ffec4e646584ec546147fc4b08249825cb32b531aba26467b586d760485adc3",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ViewDistance.c": "8514f72f8207f12570d2317e64094a45507a1647dfd45a1501499ef81e90189f",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AirAmmoMission.c": "0a471a40c8f7dfb797d8d35e41c5b1ff91e8d2ab13b43cf866e46c0e4ab4ed9d",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AirAssaultLoad.c": "5554994115cd7d56152ac78ba53034bf934b71c5634b17d35e96884dbb3b8a24",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AirAssaultSystem.c": "2eaf9816eee3e1cd2ac70744a6ef7ea5a90d0a3eebdf925e384028a7edde6a42",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AirReconSystem.c": "f8a0e3cf4552d8133efd33c2126ea15a7694676773a7047c15e00021c1d9721e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AirSupportPolicy.c": "b5dad011d5a0574a406d6469e0744a9d37700cb41930dada28c5143d556acf53",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftAmmo.c": "2be282406d0c45ab2e35b0ced791b5e494c662588f536701a4e82e6f5fb7c913",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftControlBridge.c": "b8f0141644d8f043c6db4c2d4bb93215c548e5c27ab5468c66e996d62bdf905a",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftFieldEnvelope.c": "0f53c7d9ab167a804eb904d63725decebb53fd5796fdf27864a75abed5679838",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftFieldFlight.c": "c04e264cff9963821852094c451502d31bf9c96d4deae2e8ff8a2373b53b5665",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftFieldRules.c": "30be68f4ca2db7e169f6f0432815821179cddf9159f7b150341dc8eb479bbd17",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftFlight.c": "aa25a8e67a30c48677bf528236402b7f8e5d815dc18df93b878520ad3d1fe8a7",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftFlightEnvelope.c": "8125cef61fb283d0d7c4dd2df3c04b062b448f1101fd48dd53f4f4097df5fef3",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftRouteEnvelope.c": "e8fcef844239c47a1f2f36be420afe943bc450091b05859435406b46c87fa511",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftRouteFlight.c": "1d8d92bf6d97429d68a9f62018232e90cc124cc8d7445efeabe863706a31e2b9",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftService.c": "3f05209227986b5c6bfb193f8f53783cfd4ef083bce898e22257c3e63daed913",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AirfieldSupport.c": "c6d88ede0b1b6bac5a7075b6e2e783ccfa8850210aabd28bfa4bcdb698cc5f87",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AssaultParachute.c": "a93c996059e7166988e9d1b0b498cbfd36f19a1cf8520cb4e9215a8884752e18",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AutomaticEquipment.c": "5832cbd3a43748e77f4e7f1b59361801c6d0445e0e15165e97d7a897b338709e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_BodyRecovery.c": "47d9374095d04f5f1339c220caf518a40938ffc20f891b1b51ae38579e7e1c25",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyBoardingProgress.c": "328f8a5eabb8174b7727eee1d675db83a2eb0be7b8e834db4e950a645ca5d7a3",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyBudget.c": "ca49c274146d56e30cc93fa1652cc34c0fe68ea95725b0b3e33ab3673a6463c4",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyCombat.c": "5c4b7576b28d51edea868cbb91a0ab31a40e6ddb558f1b8a38e07e20fdb2969d",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyMineStock.c": "4497d6b790a45df2b76d18541ddaf299b406def88b868caa29f096849026a42c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyPlacement.c": "4d0e8b6e147486fb65db82a1060f0c5641c1319ba9e418bdec99cb5a422a7fbe",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyRoadPlan.c": "fae404f85cae63e63976938a5fdb73efc988ba25c5846564f12edca04fb88551",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c": "b59016d83cb9d40136b98b4e0067ea490bc553574fc53603593efd5522dbed20",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyStartup.c": "360228fd45aa6ecd9426e36120b5e637d2feee1e3a4a89aac8207526b871c87e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyEquipment.c": "c52d4a3cc0e71eb136487c42df2fcd5bd6da1c33d31dbd3d6429c237dcf4a8d9",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyGunnerSkill.c": "adf8bac5a6f7e40b2f9caff5fe9be590c4e4ce2b2b387ba39e741a3c4f4cc564",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyLoadout.c": "a48f32dbd7f209bf08fce2840aaffc1781c571fc679763be58987f6b082e776c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyMapMarking.c": "0f161fd3e08a54e3e1063679127549e482c40253a022e9d06cd9ee4d740b8625",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyMedicine.c": "05d341a9a000bfa2084057bd67d476d6e35adca6c8518d74c27cd31b551bbb95",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyOptics.c": "759e0a88ff30cb781ee69b8a935865d63ede5b1d97f31d344018f650a967c37f",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyRadio.c": "3a97e5c3de9ff33873d97c6b2d84ae505cfc4f3146e047b928e76ec3b22c8789",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemySkillAwareness.c": "00c450d033e43fc45c9e2cb64a1d81161d123cc594a4292cfc5f68eb6c17ee60",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EngineerMine.c": "e0098577626e719283cf4b40936e7ec038076c4bab9269fbad0b5ad3292e01e3",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EngineerMineSite.c": "b629fecc29711e57f364f2a9c4c3e884b0978d9674311678f2e78b561683681a",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_FiniteMagazineReservation.c": "8278590ced1936d618905b0cbd0b5aebddda238d426a6a2a52e455fc36bbb502",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_GroundDepotHQ.c": "8748325b0810ea053b28d0ed4c00a75fceae1c0d1cd3b30efb4dafdc397632ac",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_GroundDepotLoading.c": "36da884a833e7a29743308490a3ce5ae299f7a451d87205a227163892aca4ed0",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_GroundDepotTransfer.c": "1946db0615130b0039b42ec80db12d9cc89b1b3ca5a7eca7d9112eb69c3bdcea",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c": "5d66c53baf8762e76ae59e28721edaa348014af6d988d42891d9a84a6cf44969",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQAdminProvider.c": "e9bb5f2f3ec270074964fc8607e5a07c23f224308a4446837598d93e201d7775",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQAmmoReporting.c": "95c024cbdd35c02d9b0c3d389d29bb042e5dda6ffd2f5f0d896078274b3adb35",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQAmmoSampler.c": "8bb0ec1422887feaa340307d3c60bae50fc82d424fce6390876e580ff8b75e8b",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQBodyObserver.c": "f59421c1e0c83f0f480d7dd4d048b4b0a41193ed8fe8f2f627d2b6a696420ea1",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQCoverage.c": "8d9668858c47b7e9fee2b12d3ebd4e7cb88146b817cf615c4b0dade26c81b2ec",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQDeliveredCasualties.c": "1f738127e55e843ee2705007cedb5c08741f52f63c75eca7c80242177af82273",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQDoctrine.c": "6335728e89ed40ce11b811f00b61bfea76e94dd3379e4357cbfc438b05522bb0",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQJournal.c": "53563ff54fc4e0656713d6eaec5e88ddf69fd4bfa8ef6a67cfa13380d24031e1",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQLocalAwareness.c": "124260280ac8eda0e920bfcfc86932c9b21df6b1f7a6aa8acfc43feea3da149f",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQLocalHandoff.c": "2dd700dae9d9b232d22ca63ff01af75a3f5cc213ac24b7c0a43ffa38bc0b9ffa",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMemory.c": "ec2047a477fb4273e22ae6bb68e7365f321f55c583b5d2c48c4abf2ac38e3b7d",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMineSampler.c": "2a0fdc4d27e947a1d77b02ae23f757c7fa074eb456f817b7df0766583d757f55",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedAmmoSampler.c": "88ea0fc0e13d7e3c22c7ce8b652816d1d137e4e6e37a72f48f0bdf515b936ae7",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": "60d0fef83f43eea0501bf5fe46f369556e22acda6d034908b5965d8820b28893",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c": "a37edcf66e43945f331db47fe81cb2e0e767a7aaf8115244dcf9e6295932be98",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQNetwork.c": "0b73be993628a55e3b21c8670d540e39c938093dd5cb4d586e800ac530509b25",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQObservation.c": "57ad3f0b68d150791e30a7e4bd7dfedfb1ddd78a1c76f228e67b33ea9cb7144e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c": "ab6ca0e1a7f36c104e664db06a74c80a528cbfd1de599af42e2194ef0a4f4ca3",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQReport.c": "12ea8ba08753d1e6d38b237087c1209cd2f9574b53c3ff0f27c082bed029c9ea",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQRoadIntercept.c": "0897e82d7805ef56f05f2b9c23903280285ea6bfa51796a2df86bb564ece78f7",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQStatusSampler.c": "b937eaed913a5095afa8e678dc1c36b5c805541bf8cd5be927069533d6baed1c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQTask.c": "1147904bae6d72e9de47ce17bdada3dab489de4ed6e781dbf15bdbb30599ff6e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQWatch.c": "abe634cf6003ae920ec1ed18e305ed462920204960894520d3dd11f7e5ef1c33",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HelicopterAvailability.c": "60eaef2f6d4b2fd44861c5a4434ef97e732e40a5ff7401e2da451ed2dd9c2762",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HunterCombat.c": "df1c319d10efd07ed6f971cf0e41d8d7e377b04ea7f0fe143bf94df733fd1d9c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HunterCrewRecovery.c": "e76af0fbf6f436a9c3c9d4c7b779bc08a78f6fe705c08bf6d9757bbeb65ee8eb",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HunterOpeningPlan.c": "0b6275221dab491f148426df05e132579f3acc20de5559226c3b38975618355c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HunterService.c": "79369d5cc30cf628f02df0023157ab423e40497325dfd6a73b218757f2b689d4",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoAccess.c": "ab4262f78d54782c5ecbf5fff4ebe3da16d72329658d7e18c0c35387dc8b3c7d",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoTransfer.c": "99fa26beb0c0869d0cd694500f3bfc0705c2c1cf674dea90d98fb93a59570ef7",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryCombat.c": "5684fcbee128a35107312452297f7ae2bec28aaa1b63f619fa649f36df8ea476",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryMineTransfer.c": "02c399413f80879c22b4891e8ec807c67a6235677095fc4b98bafdc360d6944c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c": "b01fa948f204d49acbfbdfc5fa641b1d914e0eb91081e92a93816ae52ae88b1e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantrySupplyPool.c": "c8e454573cf064ff392291d6feebf1f23be64390b5b8e944c03dd065f9f46b42",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarAimSolution.c": "59e8343d09f7a44871d8d3335d6ad1d02216abb5deed30d38b5ec9ba3f2c0649",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarBattery.c": "157024afc8fd3c1ffeea6ae923ff091e5773b9ffcf5f5864f8040d001e689110",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarCourierTransfer.c": "968e58ca700b4a11e5e04a5c159696f514a0b1bce531210172b913e495f7e2b7",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarFiring.c": "f2597d44c10d10de2462aa5e5450a22b8a5ad0eb99bd0cc09a8cf1ca2dd4a350",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarLogistics.c": "b3624e4a00a2bdef16f6c100bbd79910e3bb8c2ae8ec5fe2ad5aa70bffecd303",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarPrediction.c": "05863b2b4f9f70cdb288f91c722cbea163c13f2a2927dc485482e5bd4fa7d57f",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarReadiness.c": "4c3799b3f2681e977669de0cbfaeb307d9a682b04365bfc9f59079e762965758",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarRefresh.c": "52ae8d9d6cad92f42d68c634091b80a474d2666b6c06d63a8c30a3fab71494c4",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarReportLedger.c": "42a96040b63e56677353e55164ae23031c90f70b4d6673a9f412ad7b19e636ae",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarSalvo.c": "c128850c7a78a4d8fee00ad38f48b265f1fbd1d9b24b092ddc5c205b0f0ad6da",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarStation.c": "cbbd7c86f722b664c85ab7c070ae1131d2ce5c41d1dacb4381bff9c1720a1866",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarSupplyTransfer.c": "ad71084f507ad9c32d3055e7ec6844a1ec5ddf568c0216ab8391e902b56e417c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarSystem.c": "bc290ee8bed299d869598cbf41ec281653e0cc7f3c64c7214613df8a0fc83829",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoPickup.c": "5f7ca5b31f3b405c2a6ca32ff91042f5b4dac6ab9640ce67fb10d7a74236ba69",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoTransfer.c": "0b195784497672fdaf651f336c14f7be98c257f98c85cfbeb4f96fe1a401999b",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MountedMagazineSeed.c": "b42d0aac142907433df8bedc40de24c94813544016cf5f451846f1980250f995",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MountedResupply.c": "c26ad43705765be1c20eca37fbe2978b8c647151f6620ed37141657a4b7dc312",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeAirAssaultProbe.c": "c1d55b338b157b758e6ffb993d5f689029b28b13929797c91e5dcb15b5e561ea",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeAirLogisticsProbe.c": "58fe863775d0ad575cb566c78cef696c9826691cf49ef9adde76376be7316db1",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeAirReconProbe.c": "c1216e0512a0268e4dbb82429e228334401c0c3cac52ba4486ae6dc91e41576d",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeAircraftControlProbe.c": "0cf3e72ccdc42e6ef20fb71d743f0e037a9e69b2e082c720fa705628cf790205",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeAircraftFieldProbe.c": "b64907c7566a894ca2f3c49ede5d972d4087a938da1a0ca87bcbd7c2203b2c52",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeAircraftRouteProbe.c": "8d0dca97e5106d1c84b64992c789e1af0689514beefc027e713ca6ab901fc470",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeMortarCourierProbe.c": "740c8bebbd4128a9311b1167d1058549475d6b46f81c9b6c0d19650f4b50616f",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeMortarFiringProbe.c": "ee570d21bede8baebefc799399712d9582b3e30ebda7299f6c941777f02ad2a2",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeMortarSalvoProbe.c": "fb9ccc6b36247ab3ad828ffbb2556abf5c2838d93ea6cd1ad259c0a015142f5e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeMortarStationProbe.c": "a110f0abc753cb1d2f781e457e637dd5346ecd2e924eb5eb3a5c7dca3d1e2340",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeMortarTransferProbe.c": "399c21940ef10777b46fdb3e905430ce43404ce1de2f4a6bb62f9c61c1a0689c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativePatrolHQProbe.c": "a58f57530e333976dc96fc5a0f9cb6cf0cb069c55788824950c5fb2f965d8e26",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeScoutProbe.c": "41b39b1abe5e51a5b8a428d533cccbfb0dda9d7a570228dc3489408c60543f17",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeTownProbe.c": "ade130f49fc9d868281cee7fde383f084f70ed8d4b876dacebbaed8538e84999",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ObjectiveDefence.c": "83ef91c2118521aedb4c1838b2e47909c80e0950086449b98ced168f1fd87080",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ReconAirports.c": "7551347d776d6e0eec818d6468cacbca7ac53daee7317e0a1618ca3d39ebc762",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ReconCrewLocks.c": "684bc5b9858c7ed90ed2ca6d52fc0e731a7e37a116db776090257b72a63621ab",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ReconFlightComponent.c": "4966dad504b3f8f28a9ffc8ac17cc548b00066fe05213c49c662ea56de34c9d4",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ScoutSystem.c": "63287a252e4e7d5aefce840279f26ce3e1f286cb79cadb32596c37897cec7119",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SettlementSafety.c": "e5c0ebc95521a9a33a3f7d9cca6c551dda862a08b7af20b017877aba9584795b",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyColumn.c": "35678ec625fb0a3c0871e2c373fcff49816440a97a165ed25c8798dd72ef059f",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyService.c": "5b1578afbf6da736453cc9d3872979eddd1649746aa32d3b0c7a9e3cab50057e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoySystem.c": "f09aa817c27d6af0ccfb544f4ab1ae2afafb63d626641e03d87cfe159475d821",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyTactics.c": "dc3acdffc8ca48f11b131f66d30d679ca68f3b3004344bdc78723bc9305915e0",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SupportScenario.c": "a7b46253e1bb37cea7c29bb557fbf43ca207022d0f1cc927349441b2d1615279",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_TownGarrisons.c": "57f862476751087972fc78d0b27ab44e72361e27d32f15d55f9fe7fd7d6bd0e0",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_VehicleAlarmPerception.c": "45ac71d25e10a057a26a5b0fc7128935baaf47f4c0f87208a4420fa52086fd98",
|
||||
"addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelAdminProvider.c": "d63ab5c547021aeb61d398b8a773afeedee4662d3ce15d9fd9d0e9e37262f8b7",
|
||||
"addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelDiscoveryFeedback.c": "62f2c408383f410782d9d41fa50acd6bd8cf66eeb80d2b79f91852b9d2cf8d97",
|
||||
"addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelEvidence.c": "6bbf8d3b83e2c09fd9a23a9a5e5e11d7c29af2e618116bee613562fc45af2b33",
|
||||
"addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelReroll.c": "d8b04de9ea789732cc78ebce9aa60786c86fe46d05f671cf251eefa68a214b59",
|
||||
"addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelService.c": "2911325c79a7a372628b18ffa0e110d11919ce2c2cd7b19277bfd608fdb03dfc",
|
||||
"addons/core/Scripts/Game/ERR/20_Intel/ERR_MineIntel.c": "3ce16cebaa9703b2f9803df7c11614f94de6460a9136dab8f80dcad41ae2b1c2",
|
||||
"addons/core/Scripts/Game/ERR/30_Map/ERRM_NativeMapOwnershipProbe.c": "32418602f085e3edba7343f42a6a691630b533be262002f29248e8ae87a38254",
|
||||
"addons/core/Scripts/Game/ERR/30_Map/ERRM_SharedMapComponent.c": "3a10f6ea25bf88a400bc3e6c339823433845c5db4f2f423835ccf99a1b1a843f",
|
||||
"addons/core/Scripts/Game/ERR/30_Map/ERR_ScenarioMap.c": "75fa1d742129ef7f33c005cd48f56db7f49ee7bde3c5428532b0b501b032b0a3",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_AdminCommands.c": "282fe1463add7af8771aa58507b254e69d2d06f50274d4c77321db073f8394f0",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeAlarmHearingProbe.c": "5dbb6a48909b76cd141e3a79ef01d2e68cafee55153821c4471f85c761fff94f",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeConvoyCoverProbe.c": "d2450140cf9bdf09210473234605b4c0d818ea5b91f9532209166a35c5d13701",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeConvoyProbe.c": "4bdc920e87fa768b0ef0ed893c70ddbbcc9255d74163d7b657fd2ec7c4f14c32",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeHQBodyProbe.c": "811244cc8297817efbfc3ee4edf01c868eae23c88127ba0c72e98ee7422da72f",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeHQMemoryProbe.c": "d8fec6c138bb0f5bdbf99ee82ffc3ac9b44d1045d8833da63f8aa5f39beeb7e5",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeHQResponseProof.c": "0f8b09fd8753234c91fd6baaca0e2f812e409ae1c4be8f340b648ae59b69c2f3",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeInfantryTransferProbe.c": "9b2de4dc22bdadd51b0271219a971dc8dc07f70ed3534f306acf03ed81b29424",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeLocalHandoffProbe.c": "0d4d61c424a16008595f86fa61f3b848caf9bd6e9ebbacea9c10254d88ba378e",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMaintenanceProbe.c": "852b9b397efc3f919c97fec63bc3df1a850cd7833421af8441994afd191951d4",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMortarProbe.c": "ddb892141500c192c0787b011d57dd8c07dd8c9a3ca5fdc407f6c271897bde0a",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMountedAmmoProbe.c": "8d197f7a1398625e5a19db3284b0bf72c6ee6e032843a3e4d9a4e58de42b6825",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMountedCourierProbe.c": "12710e9e5548fe8856f7a52f3566338369a8a1eedf0bd92af16229271872ada5",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMountedInterceptProbe.c": "34b0b1e38a59f14c4e83d93ec412779945388e6f4d697bda2f6cb90f519a6372",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMountedSwapProbe.c": "a6721fcaaccadf8eddb06687c7d587d0a3e377d5d1bb0b8e72e630fede8690ac",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeRadioProbe.c": "84524163c63402755b4f834f74dfe6d8e5c397dc26031f532593c6f79f5a93b1",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c": "254d1192138dd9864048693dd02d78b317e874cef57fc0b13226768e024d58be",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSupplyConvoyProbe.c": "701311de6a2c9fe8f0534a22056323c34072df26595dc70a7d4df45bc06e6c13",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_OrdinaryScenarioLaunchChat.c": "a747af09599ce4d0c52051f18f00c4624d24d137e328341b580b82e896c6bf65",
|
||||
"addons/core/Scripts/GameLib/ERR/00_Core/ERR_DiagnosticNoSplash.c": "0ea1b402f8927b6d68bf6f7e5caa2967f7e3a39147634faab147796b791933b5",
|
||||
"addons/core/Scripts/WorkbenchGame/ERR/00_Core/ERR_ValidateBaseWorldPlugin.c": "49a38c37b51e5318c7dcbcb01543dd9a6300d053f0d39808b3974f4edc45b871",
|
||||
"addons/core/Scripts/WorkbenchGame/ERR/40_Chat/ERR_ValidateWorldPlugin.c": "f567518b54f43b88747df2838cdfd6ebb34605c0919a3e7d271d28dd8b67a9b6",
|
||||
"addons/core/UI/layouts/ERR/AdminMenu.layout": "e8d570f7bf3b8c4067ad81d47bca7f35514b2f8c8748f5fa5f775fbe27debeac",
|
||||
"addons/core/UI/layouts/ERR/AdminMenu.layout.meta": "9deeece996f43b1f1fed6b5ec89279645c7b6424250200a42580cc088142e9bf",
|
||||
"addons/core/UI/layouts/ERR/AdminValue.layout": "cd811af6d2fb187f01c74e20ea07ad85669546b0ad70efae2ce52921ce26cbca",
|
||||
"addons/core/UI/layouts/ERR/AdminValue.layout.meta": "56b3d2335db400c1308e07c4a35d278b23ef32013dfd6dc82951866327428d08",
|
||||
"addons/core/UI/layouts/ERR/ContentRow.layout": "62d2c95814c99d906266fa96c6112e04a7fab1667e1a05e558d1d3974adc2d76",
|
||||
"addons/core/UI/layouts/ERR/ContentRow.layout.meta": "8c4bc13b41e7aee38e0ca640a97813ed1a6964a00e2cff9e1fe4ebd71fc2fdc6",
|
||||
"addons/core/UI/layouts/ERR/ItemRow.layout": "8ac6fbc7fe0ce0ffc3185516197303bb892a4415948f75ae06f3bc289fefcac6",
|
||||
"addons/core/UI/layouts/ERR/ItemRow.layout.meta": "33eaef9c403ea3a030e58ec17f78e46af457048243571ad5856fff04101fb60b",
|
||||
"addons/core/UI/layouts/ERR/PauseModsButton.layout": "d21d436291e2c1b262e33dc228b0d60a1d188dc9e85cfda6d2373f8281431b79",
|
||||
"addons/core/UI/layouts/ERR/PauseModsButton.layout.meta": "9ed6adabd8f53eb9a562d7186789876bba5c4202155ca894c1af866ca47354f3",
|
||||
"addons/core/UI/layouts/ERR/PreparationStatus.layout": "bd269cf8dc6f27735423743dd5b78242550518414448a0256e5e12fa6b92c459",
|
||||
"addons/core/UI/layouts/ERR/PreparationStatus.layout.meta": "75b12176a443bb911fc4ad446982d762201fda42ed38736b79bc5439fca31ccc",
|
||||
"addons/core/UI/layouts/ERRMap/ObjectiveMapElement.layout": "b364752c44896d67dad07e466a04f4504a50813fb36ee7c34c9189ea9677b4e9",
|
||||
"addons/core/UI/layouts/ERRMap/ObjectiveMapElement.layout.meta": "8691fe2eaf46568bca4a9f8128b247a8da810bb87a5546aa7ca3334e740270d6",
|
||||
"addons/core/UI/layouts/ERRMap/SharedMapRenderTarget.layout": "6e83a9c70282aff43b23a08b732739e7a241a31e8abf200498c90a745d3d60dd",
|
||||
"addons/core/UI/layouts/ERRMap/SharedMapRenderTarget.layout.meta": "5c846445cc089e2e08899ba703059d4adb805ff30ab5a16409a3cd59718cbd54",
|
||||
"addons/core/Worlds/ERR_Everon.ent": "22324bc7eb0d80dec8a1f3bd61a9df9201b72f749f4e751766b0c9d0b7d46b87",
|
||||
"addons/core/Worlds/ERR_Everon.ent.meta": "19d1903ca72baa326b4724a6791d0da1104a1e53f3f31dc1ca41a856efd68e83",
|
||||
"addons/core/Worlds/ERR_Everon_Layers/default.layer": "6bcb7fbb27b6282719dae8a37486e518d4cbaea385f2ea9af1918b36bb28b175",
|
||||
"addons/core/addon.gproj": "73e973a1f1c5910393e16f86a3a40d792031bb2fa3fc7f4dd87a06a16c3e34a4",
|
||||
"tests/helicopter_availability_regression.py": "3f256b7f5db246723b75caa2cc247249881cba95bde71ea1c90733404fe19f35",
|
||||
"tests/mortar_salvo_regression.py": "0f5d5f5d33f64ff43f5a2b954fe4eec032a00697f51f09efa10284537a3682db",
|
||||
"tests/test_mission_rules.py": "f534ae447fe1db6ae939430c3b14568d2bf1b84956dca295cce04211411d9ee7",
|
||||
"tests/policy_harness.py": "e861c30037b3e456f0c8ef1c02e9fe1417b601ffb15d2037974715008865e4be",
|
||||
"tests/test_time_policy.py": "45ae5768f6e87009b4074937cbc2e384c59341e905a3f250672bd2a110b2e6c5",
|
||||
"tests/test_radio_defence_policy.py": "48c9c01dc6135a3f6d2187b0974645758040f041c3c2c2c26b14222fc572dfe1",
|
||||
"tests/test_performance_comparison.py": "5f932de9e9898acdd379f26c777a35c884269522dbf8c41991a7a25bf61c369a",
|
||||
"tests/test_release_check.py": "8b6e755ed922ad129ba01c228989610a31f3a79841dc7cdddd4ddbd0e510971b",
|
||||
"tests/test_player_admission_policy.py": "745ec0a9e491ffcf5bec53ebf67ecee8333f4dcfa5d19bafbde31cbd1cad7f8b",
|
||||
"tests/test_recovery_policy.py": "adc4966ab92f0849d08636d93da5bd02b9162d1ad7369b325a1a357903abaa55",
|
||||
"tests/test_admin_setting_policy.py": "69af6f74e262ee4981ac428ac1ab85561f8b365aed5879e20d315ededca9ee22",
|
||||
"tests/test_prison_placement_policy.py": "34539744426f0b30e41883203484ed60606df8ca319324014f07e833bcbf854b",
|
||||
"tests/test_environment_policy.py": "66b86e66f47c4ce397c6093a50a724bbe8dedbf3db53e0dd7d53ff33243a0b52",
|
||||
"tests/test_faction_catalog_scope.py": "c8fab92575f2621f47ed8685243338aa9ea717bb809c2ef9e8e0884af9df263b",
|
||||
"tests/test_environment_restart_runner.py": "05bc09bc45db70ba4a635b65b0a96aa38437ba50c98640b47ed8d0683c3a5f71",
|
||||
"tests/test_inventory_enumeration_policy.py": "95cbfd7c4351cb9a8e1cd6783524be79f77255e5996299919a61dcfce8b0f21a",
|
||||
"tests/test_site_discovery_policy.py": "40fda37b86d469a1cf8fcb7a1fb18abcaab0df44ad09f50018744d42f8f4f649",
|
||||
"tests/test_headless_game_runner.py": "0d84160076ec9961e7315ccd50d6ce45ff971328c298649505fbba6305c22f31",
|
||||
"tests/test_initial_guard_policy.py": "0c80e744db1ce0a0d0746c43d867f5d2f47d9b92bc48d6561ceeb28a204d8743",
|
||||
"tests/test_casualty_loading_policy.py": "cdf2c0abe4b794502f18228b173db47c4093ea062378ec9e3971e0c4f9e3bf1f",
|
||||
"tests/test_casualty_reservation_death.py": "fd68dce04ea48ee2a22fac499c32a5c53847a25e3f14eaba3f50437d2c040f25",
|
||||
"tests/test_casualty_ground_policy.py": "6543c594293d7122242ec68d506a92dc81464d110cdb7496a225555cee338c83",
|
||||
"tests/test_guard_cancellation_policy.py": "f8f134b6d525a3c775677b2a95c562b12aa84ba287318c08c833d684ad0bd9d8",
|
||||
"tests/test_radio_feedback_policy.py": "6b576472f81a35bd4df4dae3f19925b046eedbba262ad67f9bfe1ce9d220a487",
|
||||
"tests/test_extraction_placement_policy.py": "d924f163d98c78989938ca01eca5935891fb454266810f4817cbe49651b84ae2",
|
||||
"tests/test_casualty_remove_probe.py": "e3b8ac386bb8e4a9832215cbbe1cacf7a5bd1314467bd4b07ed4a57b2db75458",
|
||||
"tests/test_casualty_incapacitation.py": "55b5cfb7b3eae496a8cc18888872d11331ecc01afb7326fbee3b8d65047dbe93",
|
||||
"tests/test_diagnostic_no_splash.py": "d99539a8b3277c2f1ac3e3bfa4c897876fe21e704c9ae33c542eed79fc2e9e65",
|
||||
"tests/test_native_mortar_probe.py": "217801a6eddd48f76e99363a72a9812c0f48766ebd2faf8e188f4466ed1e4088",
|
||||
"tests/test_local_admission.py": "0bde82f6d5c31c4187ed0aa1935940b8b9e634868b3a4820eca073580ad136b4",
|
||||
"tests/test_dedicated_preflight.py": "0d654375db6b8eb91d0b92663b50e32776aaa7ecafbfdb83c00f04826df5236e",
|
||||
"tests/test_mortar_aim_renewal.py": "cf228b7f83d247d93277ec4c8644eb1090a20336a7e9ee2b90086c78199a70ca",
|
||||
"tests/test_native_mortar_transfer.py": "3b2a201a73c9ff2509276428988e4a98023c6aa94f8eeb42bc06c6cc604473d2",
|
||||
"tests/test_dedicated_native.py": "f0323b20743221bd1ad46d6462246c2a0e2be84b395e40faa39007ef330aaa3c",
|
||||
"tests/test_mortar_report_ledger.py": "fd2346e06c37a9e7621e1861e0f472b72ac14786fd2bcfbd785b62700d55ffde",
|
||||
"tests/test_native_local_admission_probe.py": "16864bc359ab607dae3919442bd18f191320721411a8156e23be999f24e4454f",
|
||||
"tests/test_powered_flight_policy.py": "86bfd5851be6fe3a187e1fe7fd2b7b0965c891cf29b2260b0df292255c5c547a",
|
||||
"tests/test_prison_escape_policy.py": "d5d54588116eb730a8fda046f25fc4edc89a5345f85774cbc0f0a9b3adb85b98",
|
||||
"tests/test_powered_flight_pad.py": "690c1c6e05f3520a6d4d6c45a0fc5d9184e30a12b24356b3126bdb35c4171b82",
|
||||
"tests/test_item_station_teardown.py": "4e0f508dea6172862be82d6c7f295ede2bfb2412c390a0937a75ab176655f4b6",
|
||||
"tests/test_source_bundle.py": "074ad87ee7ab0b41ab94a7cb28efca66a975c24b8e2574122a13a52a4d491d0a",
|
||||
"tests/test_native_flight_footprint.py": "96923c018608f0c4d24294a9f8e769e2037be1fac8f1d51f4d73463706e134ad",
|
||||
"tests/test_native_flight_input_diagnostics.py": "355e2e45b86b9a46c68efb0563d16bef7a032cd7758169704b4b26ea92bee2bc",
|
||||
"tests/test_mortar_logistics.py": "98cdf38e43eb564163d8b409dfc95c4ae23e775c423651f05698acd52a7ef5a2",
|
||||
"tests/test_mortar_courier_storage.py": "0eb75fbcdcd712ff1c2220dcdd87cff6f3ed1e6e8f1c889916daa6b84a4359f5",
|
||||
"tests/test_progression_identity.py": "2fb3725aa735e00b20ba7a4ac45f8e087d4346c58a176bee091bab0a9755c1df",
|
||||
"tests/test_discovery_map_position.py": "0644aea60d4c546e819ef417771563ab7598e84d5e13db2fa63416ae1fe6ddb0",
|
||||
"tests/test_vanilla_game_control.py": "8ab3777a5bd0f8213efae08bd6db70a05790af992c2abfda8e5e5aa079beebbb",
|
||||
"tests/test_archive_game_control.py": "8126e2be91f9ee8c09fceb82d03bb0d9c51247909189a424c3e24dd2b1ed22ce",
|
||||
"tests/test_flight_action_runner.py": "aa14ebe85fce7c0435f48aceec1d4463b33fe8a5792b4e935b543dff94f4ad02",
|
||||
"tests/test_native_package.py": "0776a1eedede7185aa36c7fe816ea6231e6f475c0f6d210a2c732b23dc6d765e",
|
||||
"tests/test_spectator_guidance.py": "26c02deb1a707f51dcf577a9ccb789bf33f432b41d9404d36a7b1b3c8802298f",
|
||||
"tests/test_native_mortar_salvo.py": "f45625eb92d16adc8b3eb57bc72b77c8829d46c2c7e5bef40d302fc90252ba72",
|
||||
"tests/test_pak_inventory.py": "8f0b55bb295117c226900bd641d25175db1021a365866a5ce6482a78d08c7f41",
|
||||
"tests/test_packed_core_game.py": "ddac4281f74999b1e60c2052bbdee36c97699192cfd5174a88b6f01ee740111e",
|
||||
"tests/test_mortar_refresh.py": "0ac74cff8aa10fb06ecb75d17eb7ca3a6b80bf5194522bffec583a44fc01a419",
|
||||
"tests/test_archive_packed.py": "37712810b7c6fd11a2df35665df81546f8b98682dc74b317ad1f9ad6ad274d86",
|
||||
"tests/test_native_mortar_refresh.py": "60f3c4fcb063506e9bd85e4abd3cd2fc802304bc71933f3e31d88f5b896aaa7b",
|
||||
"tests/test_mortar_activity_completion.py": "a0db3b63962bc95de42ec12ef17c53730464ac7421a4475020957121513586ec",
|
||||
"tests/test_packed_chat_game.py": "d23c099d17a0dda09e5ee28931a4850f9bf6df5a7874581342eacbd16860565e",
|
||||
"tests/test_shared_map_ownership.py": "486eb3a8a04a47878b87ec3952c613542871077af1de27ea7299fa3d72caff1d",
|
||||
"tests/test_native_shared_map_ownership.py": "672d248550339dc6350e548034c50b3440d44b91668457f3efb2d91c8445c3dc",
|
||||
"tests/test_participant_selection.py": "e153f69ffa60d186dc3b1a238df1d48f2a0623b2dd8331d2cbbbdd57cce5529e",
|
||||
"tests/test_ordinary_screenshot.py": "eadd4f8ae2a8057d0ac7bca5e8f4abc260faa10cddfca31b8552d7b49669ab33",
|
||||
"tests/test_aircraft_flight_policy.py": "147a27fb57c0b01cded35eae26af4394d7f41eeeb68856f564972e1c89c629c9",
|
||||
"tests/test_aircraft_flight_envelope.py": "6499ec174ed68dbfc07bd49275eaff8a7e60865615bbaefee354de7233a6ff13",
|
||||
"tests/test_native_aircraft_control.py": "3ffed5429896254d96b67ff3fdd01f65689f526e6a1766cc6874e2e63f2e26f3",
|
||||
"tests/test_aircraft_translation.py": "983fcff4800e777cd1c42126431ca94e7c03bc06b5fb34a243704b8ca55dc36d",
|
||||
"tests/test_action_feedback.py": "1fec11a218b6da69c8c40ce232523582f0ad390f8357e74f24daccf5d46d3814",
|
||||
"tests/test_spawn_retry_budget.py": "c182acfcfd52cd74a94dc3336d7603d14668850a684b758cc7545a98a5d14beb",
|
||||
"tests/test_native_stats.py": "a9ce90c74f0ae7e5c40e451f6b1d60dadde831c3d858710a70929390d5c70f7c",
|
||||
"tests/test_native_end_lifecycle.py": "8b187723c9039e68e3e590c812c91dcffe1e5917d4e450ed798dc177888fda7a",
|
||||
"tests/test_native_end_runner.py": "aed94be9b370a412937303cc5b66f0151c2cd64c3e1358f983f9ea53eb0d1057",
|
||||
"tests/test_escape_result_wording.py": "7b3613cccd4f24d3ef4665dfc1854cfc958a69018a59f9b8090617f0185a82a8",
|
||||
"tests/test_preparation_status.py": "e43cb7ce81232a31d00219004574dc2d1806760e0b6f0c49693725ffcb1b4e9e",
|
||||
"tests/test_headless_display_probe.py": "d4d6c9907d7419ca888d5d9ba7448cbe6f5d2e53810f74c85660dfbdfc9f7e2d",
|
||||
"tests/test_headless_display_runner.py": "f03a908e813c9ca4dc2ff13b3ade08549931e39490b8636742390a304c73974f",
|
||||
"tests/test_rough_intel_feedback.py": "d7395ecc498dda46e1c9a4c8fbf98a64f2bbd013de7806517fb8008756e7b40d",
|
||||
"tests/test_aircraft_route_envelope.py": "08b16d4e7f97db96e3f88af0cfccd9b6bc083f6f25ec33cffd7ec36b18798729",
|
||||
"tests/test_aircraft_route_policy.py": "b717734a07a59ada837f05c36172110bd7d0b5a5a277e8fae55a11dcab3e2aa2",
|
||||
"tests/test_aircraft_route_probe.py": "bd8cb998fef33db8f26342eec0198c54b7715662d333530e2bede572c81f162f",
|
||||
"tests/test_ordinary_input_observer.py": "09cf661ae47f53cb429a7ab17c2b752ae30a04368a260468227b0678148b2434",
|
||||
"tests/test_ordinary_input_runner.py": "1ed373818673a438647fc7074c4c689f5bac0a91e8d55edf29d466b96f6703f8",
|
||||
"tests/test_private_prefix_cleanup.py": "bc6d3fcf6a5ee5b5266bfc4516642d2f73e71dfc870c527d67cb5a47964dede0",
|
||||
"tests/test_runner_private_cleanup.py": "4380874af7553391ea7e1e234c8a3f8e61922e0f16b1bf9bd4821cc479e5edf4",
|
||||
"tests/test_headless_input_proof.py": "55c84de6dac3a9c44bd1ab4113d306544025a405c613f6e7d26550328e84f6c1",
|
||||
"tests/test_headless_input_exercise_runner.py": "0b812647718a3d7145905a3ed17fef97b025d57b1372773d2c1069a3473a9b1f",
|
||||
"tests/test_inventory_control_runner.py": "f325fe1975f7a56fbbb1a6357206d2ac0c9e7c32c396473fdfaf1cb80ad39b11",
|
||||
"tests/test_inventory_layout.py": "8e176f8a87c300b8062a37ee34b22a105978c7d9c6147b506757135f4f48a1ff",
|
||||
"tests/test_radio_waiting_text.py": "2aa54d8126b3fddae2b77d496f56d2d6e6d393c6d5124bdb5598713d9c29d629",
|
||||
"tests/test_relative_look_runner.py": "131d829c60a7bbfe3e047af8e38d255e5ecb9d84b7d2188d14422dde724fa05e",
|
||||
"tests/test_ordinary_look_observer.py": "cd5842e54c9ccab16766637bf2ab4e4e0b98f4d597b5927b0c2ec3f720fa3668",
|
||||
"tests/test_ordinary_look_evidence.py": "1b98be4b69a5e5bacf46791f1e009fa3c73cdaff875c012837c9888443a61633",
|
||||
"tests/test_ordinary_gate_observer.py": "78ff734604724a525809567ecda88f4d5fcd67816a6ba4960b72d0685f1a8b00",
|
||||
"tests/test_ordinary_gate_evidence.py": "2bb3cff082b3fe45f668933a5039fd06a8d31562049cdd41c4f32181136ea5df",
|
||||
"tests/test_prison_approach_proof.py": "ca71ba6c8cc20dad52d2966984676a111004333c24bc408f1ec84312e4208039",
|
||||
"tests/test_prison_approach_evidence.py": "660e03b6f9932947c1aafc4c043c2a2c5bca776433c2146df18e8bb90c606a9a",
|
||||
"tests/test_aircraft_route_demand.py": "96d9a21c49605cb671525f977928a21c57f1d0b441460a2624892006e7057efb",
|
||||
"tests/test_prison_alignment_proof.py": "04c96825ea03b95c3966e3a53ce2c15990d63388386d84f2bff3e6b6838b18c0",
|
||||
"tests/test_prison_alignment_evidence.py": "214af04ff9b0e8ebf0bc97f528233f5d55fccc7623392a65c6dd286ed415e724",
|
||||
"tests/test_aircraft_control_bridge.py": "bae6f1711dcc5f55f7a813af251fc210ed017c58a8067c03d91e936824542ef0",
|
||||
"tests/test_ordinary_gate_choices.py": "0e21f2933a4862eb5fd2f04b3b32dc7de6b2ca72d920aaa332d179edadb932cf",
|
||||
"tests/test_prison_action_selection_proof.py": "1f09fb84b73ea916bb39ef985c58aa6886223e60cd425f929b3a020eef74ebae",
|
||||
"tests/test_prison_action_selection_evidence.py": "1fee0421669721618dc2f9a3039f945d470b83b186661d3b1f00291ff8b78e9f",
|
||||
"tests/test_aircraft_service.py": "9c19a09354ccfe8b41961204d80c3621d11e6704909b7266fa00e6c197bfd20b",
|
||||
"tests/test_aircraft_service_cancel_runner.py": "1220e7c45c50590d12c9a5192eb4482e7e6942d7c00f36d9a79b5ca70bd1429d",
|
||||
"tests/test_command_maintenance.py": "be2c942aa11a5f874448e94f06925f2f1de2210a14881fb7e486842119cb673c",
|
||||
"tests/test_aircraft_field_rules.py": "dbed3a499eeddf2513e3582667ea2bd90fff6ed26057891c7df6e196c3d00e84",
|
||||
"tests/test_aircraft_field_envelope.py": "67e60717a92580e3c4caa4974994cd92a96c98562be805b31319b3585f296639",
|
||||
"tests/test_aircraft_field_runner.py": "822717f77702c335667b226f025aa6525415689b492f9ed42dd256d4c893429d",
|
||||
"tests/test_prison_coarse_facing.py": "fe0ceebb74dba77005d6a7e31baa1a50156cc66f79a1d90eba4db9b27b099acb",
|
||||
"tests/test_prison_held_action_proof.py": "349ee99d36f18fb7c70382ce95f1e9d84d819575a45b6f5429394d1702f1f227",
|
||||
"tests/test_prison_held_action_evidence.py": "bbb032736774bc5db5fab7758faeaf038eeaae9eb49ba706a52f1e9bfe09fd01",
|
||||
"tests/test_prison_held_input_adapter.py": "56fa60ae69bf280e1c2e90e7882fa213914623980b6800a4312c6f1017b8f632",
|
||||
"tests/test_prison_held_runner.py": "d50905e899c2d0d92ca2f4c51dccbf2166ee21b8c6d2d4b218da459b724c5a4c",
|
||||
"tests/test_aircraft_field_flight.py": "ae5e18f0e2efe5ad01523cb1cf9bc64139094aeaac7477a900a9abd8e6ae8f18",
|
||||
"tests/test_aircraft_field_probe.py": "fe4e5b58bbdca0f366fe4c614f7506f2a05fa29fabcde345e322cdb3d62db897",
|
||||
"tests/test_initial_admission_exhaustion.py": "14d6959a1185f154389be1405c49e2f7c1c9a3f27295e26074ac4ec74380057d",
|
||||
"tests/test_casualty_feedback.py": "2d8bada9a3fd7583af4fd4f07033dd9eac28ca3bdf34a4557c8985b7a1714dbc",
|
||||
"tests/test_intel_admin.py": "f2704740a380de8e93c7ceace414744f938442cffc6bca77a3439b4ae36aade6",
|
||||
"tests/test_intel_reroll.py": "d102cff7467323440273eb457b5dfed6610d27f32c52c6fb7801ce93348ef5ae",
|
||||
"tests/test_admin_player_tools.py": "7520c768b760ef3a03dd2f17376077fc17695360a89f91df88af924899a74517",
|
||||
"tests/test_admin_session.py": "6e1850c625e18460a5d64ccbadac992b663a46bd612fcc16b6006f19b3014896",
|
||||
"tests/test_project_schema.py": "0e25ade3ee0083618c1b2837aaaef36ce33663d78f35d862e1dbec256f4d6e40",
|
||||
"tests/test_admin_tick_delivery.py": "8f61a6aaf07b0c41df2f415a1956d8f09fe61a7d4f256775ca62511030937a5d",
|
||||
"tests/test_hq_doctrine.py": "5c18ee72454cf92132dedfb4bf581d174d67fc2e423158347ebe52ca74e8a416",
|
||||
"tests/test_force_ledger.py": "3dc268db304b9465d53056edfcd8cedcc15591b411c55ae8f9bd43e163ff9c49",
|
||||
"tests/test_hq_body_observer.py": "6cae50ef0a2b2fe35845f42e90998c517c061fc417cadfac375bacc89bbe603a",
|
||||
"tests/test_convoy_budget.py": "481364c432715336825466a9ff199edeedaa2d3621daba90c0503addfa6010f7",
|
||||
"tests/test_convoy_dispatch.py": "47e77f544ec6f3f34346dc6b5082f9df0a2d869897e094ca993a92c7315468c0",
|
||||
"tests/test_convoy_admission.py": "0ace42a7e1afee7413e45a3909e7ea5c29dbb8c0e692acc3dc9a35ea7ffd4adf",
|
||||
"tests/test_native_package_external.py": "8eb3eaee401640521c67a51607104a28de89ea9c32b3b6dffd05d9d3026fca3d",
|
||||
"tests/test_convoy_road_plan.py": "62bb655a84f90e934bb894f8e472a8a06905d72e1c0b05bd641e46eb8f4873b4",
|
||||
"tests/test_convoy_placement.py": "dcbf572747a85edbfac341e7e358b1987150a2eb721708a3a025639be228a7c3",
|
||||
"tests/test_actor_group.py": "0d808be40d13866bf8bd983d62ea3a8e7772e9c49a41109fffb02c5352153a4d",
|
||||
"tests/test_external_addons.py": "4e2c3fa2c92661c49ea0c492648b736bebd54f24cd244f3fe27710a0c7a487ab",
|
||||
"tests/test_external_profile_review.py": "a35e9670a8f5eb47d28b4d6afe498e5a58b2ab2dbdc59912a206010ac8dc5240",
|
||||
"tests/test_hq_delivered_casualties.py": "c22ad9e395b36b41e8292b034f170e14b5d13a6a02b2a81dc619dd37110ad905",
|
||||
"tests/test_hq_ammo_dispatch.py": "6c7a4cbaa41ca3bee01082ffab376fc2db5b08757aac88c5e8caf99b39677d0a",
|
||||
"tests/test_convoy_combat.py": "140d5e8c8c978f97f7f15238bf2aa4efc6ca4d9b6370ef5a0c68bf469cec0b9a",
|
||||
"tests/test_native_hq_body_probe.py": "f2b761fadf013d55d4e9c2ae11c5349fc054e767cf950aa632a43b8f4fe1d758",
|
||||
"tests/test_enemy_optics.py": "06361eccef16d60a6f5072f9b126814a8f686c478049760047bb2ae8d19b5c52",
|
||||
"tests/test_native_convoy_cover_probe.py": "68681753a1ab89a8e8e07ba7f701190bf860d07762e5a06e87da7ba606fc0de6",
|
||||
"tests/test_vehicle_security.py": "f08a1cffb0860ac7ad93e94f4cb8b7cc37b8c49b3dee72b1d310fc72d9f95422",
|
||||
"tests/test_vehicle_security_native_probe.py": "977384c5ec07588bc77aa9aeae45b008c02d55d31169e46984a1947e987545c3",
|
||||
"tests/test_vehicle_entry_diagnostics.py": "49e8b4cd8274e08de628e89f4471ddb47823af38d98b464336e8c98a4308dbda",
|
||||
"tests/test_hq_radio_node.py": "2297ae23b7a5fece845c13f16432dece6e2dde55ecc097a5be6311f470dd5242",
|
||||
"tests/test_native_maintenance_stock.py": "7acbb488f7075428c08a10cf0e975ca0d508f3bb6416eddcc2991da93a5fad91",
|
||||
"tests/test_packed_external_game.py": "c5581fa3225bbc5b160fafc26c65783eb7d551489ab101b17f7a6acc1d8d9e63",
|
||||
"tests/test_intel_registration.py": "41cd929a29ea5e6b37d856cfc873c33fa2a8130e3aad30146f522ce217875488",
|
||||
"tests/test_convoy_startup.py": "2c9106023ecb6dcfb5c96c2edc98b51695c325c685b2ced37b999b6cd4504c65",
|
||||
"tests/test_failure_result_refresh.py": "076444d2c740609ea4290de1525cd5d7fbd99beef3c48c134afb581422a3dcfe",
|
||||
"tests/test_convoy_auto_settings.py": "fe4a1e237e31bfac11f07b1986bff859f6b4712aa7759ce9e359e28e5ec2aec1",
|
||||
"tests/test_settings_migration.py": "5a82ba8672b72fb388a95e297d53452e45679ed3b94f5c7d4598455179038aef",
|
||||
"tests/test_native_memory_deadlines.py": "f2d3f0feda862318ab936031487743218a8c8241d192b34e2b1ab63a5aa2cc48",
|
||||
"tests/test_enemy_gunner_skill.py": "67172901ed950517977e535c1237e8eaab10d22d52452bbe05fe16bfbb4089fe",
|
||||
"tests/test_native_infantry_fixture_lifecycle.py": "c20ed9dd152489a8242ec0b97135b0691c9c104783bb843b0715cdd8d7c2e156",
|
||||
"tests/test_infantry_ammo_access.py": "bd80e4405c3f2029553b5e932ec49c6370b8366cd4e9ed03be89393f86c07dbf",
|
||||
"tests/test_infantry_placement_settle.py": "1490a797e016b99a222280f57875cc83096980022d1784495a80b8a406d400f7",
|
||||
"tests/test_infantry_resupply_waypoints.py": "df3596515e0bb6a4cbf1e6f07d56755344415ad1deeffb8a70f71e8ed73a139e",
|
||||
"tests/hq_network_regression.py": "63c7525b26ae6b6e2369307250e89223a77e7e28321daf4309ab76f21ca1bc50",
|
||||
"tests/test_hq_report_selection.py": "4bd4c4800c217584ad9e1b4a37968350eb9a160e0151fc8b0375781736726cc0",
|
||||
"tests/test_hq_local_handoff.py": "0fdbcac1958b0d12296753e7c9d92700079ea6c5c8e33d0e21e8318f997b79e7",
|
||||
"tests/test_hq_local_handoff_intake.py": "c51f0ca082456331eb5856bf6dad864c3852adc20beba9b6829bdef978b11d88",
|
||||
"tests/test_native_local_handoff_probe.py": "5a0904863ea00fd7ad262fcf91d8c1e55036b2041f16b043104f333c37d8c7ba",
|
||||
"tests/test_hq_road_intercept.py": "ba4ce9af49acdbf5c0c04b849761ea36abe2ec7d68ccde1f0cd731fb9e3ed451",
|
||||
"tests/test_prefab_identity.py": "2a1c6e4cb1ead0c7b36b8fffb2ba0c59dc7e025dafaf8344a8158d38fe17582d",
|
||||
"tests/test_prison_ai.py": "557015a4a3191aede258dcd7dee4ec4f555c3efde02331687185ed124cebb4fc",
|
||||
"tests/test_spawn_scene_inventory.py": "f29bee9c629072f51caf66b680013b18f608bbfc5135a1f020e1a2904e89cdd5",
|
||||
"tests/test_spawn_scene_lease.py": "adaf38b259cc36af526884d3d2312ccec0cbe6eca18d7aed2c1a066be8ea0da6",
|
||||
"tests/test_content_policy.py": "260eedd838802647af59f33a15510d2c721aa80a7540d54e47f22734041cb49e",
|
||||
"tests/test_random_settings.py": "75fbcba53ff2331811fb91051bbae1b2c28108fa9477d98372e0eb057655f225",
|
||||
"tests/test_hq_convoy_combat.py": "07a4cd8e3bcf50ced71de05d0713a7944849a38b3f33cdea27f145bd53b9708e",
|
||||
"tests/test_faction_selection.py": "77e4789499b3560e729a115197c5b02d032c291ae1bd44b1cac5a2c2c476e450",
|
||||
"tests/test_generated_sites.py": "111095f40133b025ab67380b6cdcbc25ce0ecbc03379d1a015db6fdb0e5f2b25",
|
||||
"tests/test_debug_ai.py": "7f4b847ba11e8da937eba7be3c4c0ab898b44567f6f553ff842da54c1e7a85c2",
|
||||
"tests/test_mortar_station.py": "6570f18f787b7366ec5341da68dd99acf541cbbc31c6db7a9a3ae967506456ec",
|
||||
"tests/test_scouts.py": "25c6bb1bcc9cb171d12cb1e59b5b278e256a7a4b257b5ce3a1d76d60d459bb59",
|
||||
"tests/test_air_recon.py": "ed8938ac85c2e539bc5cddff339809624bdaecfff63ff4b2ab728d006548dc46",
|
||||
"tests/test_air_support_policy.py": "bedd5437ba0667c371c390cb8c4fb5a3096e8ef6a71c8bac2091264b8d2eebf7",
|
||||
"tests/test_intel_body_inspection.py": "04dd19efafc796f349976d29df77cb71b95615e527ad9add4fceda1f6105fc94",
|
||||
"tests/test_air_logistics_orders.py": "adf7181998f522f8825b71ef10955c9311aba9086e5bb601cfd47599baa46d8a",
|
||||
"tests/test_enemy_awareness.py": "881289417a1e4af219546b113d018e9bc6a402a16cf4bfa7e983ab240f41c2aa",
|
||||
"tests/test_faction_menu.py": "9e6381a4786487962358362b75a2287532a688ab27df756e84801e2bb6dc2e55",
|
||||
"tests/test_hunter_opening.py": "8d791372f465b6242a386d81b8e2136865390cbea5dfdcbfc6bb8b4f717c041d",
|
||||
"tests/test_journal_readability.py": "47764126522e7d7a90100cd9beff741bf49e4d8390111f977919d3e323a5c7df",
|
||||
"tests/test_player_medical.py": "e4b3140a3afc0161db53f261e5480e51f1ca09f85657e25b0efb7dbba3336e3f",
|
||||
"tests/test_gameplay_session_regressions.py": "d9968b06627ce10412e5b1165987de662385d5dcb29090c5626b90fb59df52f2",
|
||||
"tests/test_field_sites.py": "fddb0e44e72fdfe32bb94347a14da64efbd2208e4f252754e80c620fbecda56c",
|
||||
"tests/test_town_garrisons.py": "3674f713d7adc62ec9098f7923991c9f6b1d0edfc1431ccfd82fc8031ddae17f",
|
||||
"tests/test_unconscious_controls.py": "74d4008a5c02ad0087b55030c177c8320b7099c11b97a14d93acf0ad14941356",
|
||||
"tests/test_hq_local_awareness.py": "00c8023f85e88ffc1113353836962c31e8e541a32c9b2f1f66b91eaff5b923af",
|
||||
"tests/test_hunter_combat.py": "240472940515795b205d5caf530392ce416e3e0d2b97e473bb935930f29287d6",
|
||||
"tests/test_enemy_medicine.py": "d13ab1d4ebe81614e00a9a579c6bcc6bd5b2c608187458f414e47f47686d7ad0",
|
||||
"tests/test_infantry_combat.py": "03bc85811c52bd894ab8545794135227fc33b911a51f4e70d89fbeb2673de8ee",
|
||||
"tests/test_hunter_profiles.py": "bdc7898666a7e1078ece35b5dc9bf903d2218843abb24addcea9353fe6b3f7f3",
|
||||
"tests/test_gameplay_review_fixes.py": "5f988d63ea4b2a8a364b181982ddd462baa628ab66e3a35d0fb5ea6f20c50b0b",
|
||||
"tests/test_convoy_column.py": "8c5de7616d4f8c3ed15bcc29bbb57f04dcaaca0626683b81547e2170fc5f9a62",
|
||||
"tests/test_spectator_recovery.py": "8bb59c51df003e24532ad9476af4c8ff3b7f4276dca25e8162b91eb77c7b67c2",
|
||||
"tests/test_server_log_regressions.py": "733084d7d2e33eb96740768c48d9e25bd7f3ffa9081949e38d0776c6822f5748",
|
||||
"tests/test_mortar_load_ack.py": "6eceee33969ec268d904f062bd1ac12ec4269cf87acd235469052b193b048774",
|
||||
"tests/test_body_recovery_lifecycle.py": "b4cc6df60863e12a16ac555ecb9818e318f6325cd989bc97d5bb6ed737857a73",
|
||||
"tests/test_native_infantry_cargo.py": "0d27423c6834503840813327d0d3ba0d675206ca759260ff79cb775adf83e314",
|
||||
"tests/test_native_infantry_setup.py": "8d935cd24ae49ca8a11f9a52fbd73146e567b8f36e0a7acc6565eb3e7d55f1fd",
|
||||
"tests/test_view_distance.py": "ddc6cbeb41485f14a4f2ce23a7a86f1708e7e335bbeecee6309be49fd72e00e7",
|
||||
"tests/test_optional_transport.py": "16c72845dd07f4ade7de361136c04bd264ff75634152e83a19d19a2fdf59bd0e",
|
||||
"tests/test_terrain_adapter.py": "bcc6f469a12399f833651c591b3e1ca9a8e7f49d8b98e223a271fddc245bc4c4",
|
||||
"tests/test_terrain_sites.py": "03ffa5812ae4fa282174f29b5fb3738fdeb261ea26ddbc44b9cd3b3d61c008c1",
|
||||
"tests/test_terrain_airfields.py": "3a6e7151902657a38d8bf0510fb5c1a8049408d4f84d51ca260a0b5d651431e4",
|
||||
"tests/test_vehicle_alarm.py": "1b58d6b35f080003ecab03b23c8da68e0ba2a6a82db4edce89e6031d83f26c82",
|
||||
"tests/test_hq_mounted_binding.py": "40b6af209178cab30b2d6b4420acabf2086532fe0d3f836caaba60e369bdb905",
|
||||
"tests/test_vehicle_alarm_perception.py": "6c8854088c28e2a7b4ee8d38a1f10f66eddfcf9d67afdd460b395d19877b7191",
|
||||
"tests/test_hq_vehicle_seats.py": "8f35fcbd30e51b5576349245a102105926c7df738f3f77abb6ffcd780c6cb62e",
|
||||
"tests/test_native_alarm_hearing.py": "7842886a177aa9fbb6b943b72703c4dafd18d162f26581ce30ce04289e7e9ab5",
|
||||
"tests/test_finite_magazine_reservation.py": "dce435b1dd2d9b1e0ae1f54b5f911ca4207227c4c23586b4f4701c6a6995fc8b",
|
||||
"tests/test_hq_memory.py": "839c7adc63dfab023fafe1547e29caf0ccef52882c5d2fad7b342691b562a41d",
|
||||
"tests/test_supply_budget_admission.py": "3d0e1372f89873470c336038c667df38d2e2e71b3dc2abfddd1215939c448fc5",
|
||||
"tests/test_native_mounted_swap_probe.py": "08674b3c0121756fe94762bd0acd5b6880ba5b234691557851eca5558718e2ea",
|
||||
"tests/test_mounted_initial_stock.py": "cf7d101d8bd31cbdaaa0e7d66f0d8b39aff9b3b70acf51641902c6d0de5be096",
|
||||
"tests/test_spawn_scene.py": "d89ef752906c1cf27c4cc7292997a4d793ddb3b56a6dfbdeff837978350167be",
|
||||
"tests/test_spawn_preload_timeout.py": "db3a3aa42c20f047da90cac6755cb283d15cb3de6a22a57a6e1f352e9b4511dc",
|
||||
"tests/test_hq_uplink_fairness.py": "fc4808a2045522ac6038eef9c37c75ef133bd8af7ffa02c6c1d105b3bfd6c555",
|
||||
"tests/test_hq_pursuit.py": "7ccbf51c7317097d9db6a66cc5be4bec67e542187f0fce5b4f3750bdc4311733",
|
||||
"tests/test_supply_convoys.py": "be4fc6376a77296c6721ce966ea33fd256fe9d76af8705a056a88b32c4f16710",
|
||||
"tests/test_native_mounted_courier_probe.py": "4dadd3157f7da193ce8996b5b08e802f891295ad82a4236366f3018c778c07e7",
|
||||
"tests/test_vehicle_maintenance_stock.py": "e09a6e98f314eef1cf487cbaf8b2185bda950c12329939214ed1ba95713a097a",
|
||||
"tests/test_hq_coverage.py": "d790ae0e5b533f702284e6deeb2c84f7ede5c89bb2b6a68847400e915d50c146",
|
||||
"tests/test_engineer_mine_site.py": "9c01b326f4d171041669f6aff25a1750ab80cec113be4481eeabc471c45ccd80",
|
||||
"tests/test_engineer_mine_hq.py": "822f50e363be3ba03bc5d12748cfc93d83e93552d66797b01afc6460e7ab7d5d",
|
||||
"tests/test_intel_discovery_feedback.py": "49b2fedd8704ec67af60a6bb3dd156ddca28eb334d18b20692ab1e4659f439c5",
|
||||
"tests/test_engineer_mine.py": "3ffd360652ba66d4bb96247496e9b34175cc35c4e5d665f6524cb1cbd563bb73",
|
||||
"tests/test_mine_intel_discovery_integration.py": "bac2c8ec269d6e51a46fc17893de4114a5613f37899a1e044a9132d97d44cc5a",
|
||||
"tests/test_hq_delivered_task.py": "076aecdb0ff0d614cadbffedf9d9f8cbb1383fb94a468bd92050b31251bcbce2",
|
||||
"tests/test_hq_assignment_lifecycle.py": "4cfd15d1655b7cfadcbc08104bd01cdfc9960dc88a95ebc5a9958f6f71ce4aea",
|
||||
"tests/test_hq_task_reservations.py": "aa6c5a38618d4bb469c84f18fb7d1748501b4608bd1929cf7e0a6600d248c0f8",
|
||||
"tests/test_terrain_takeover.py": "12365036f81e0874910f100626d2e26b4b2a83042327a927727fda87f05e80db",
|
||||
"tests/test_ai_stinger_admin.py": "d645813857f85854aeca60c9010689662d7cc43f298256d73ae37b05d74e94c1",
|
||||
"tests/test_hq_convoy_dispatch.py": "945a3cba06694608b4880408a13447ebbbf812d400fba3af5b7a57cb006a1f6d",
|
||||
"tests/test_native_maintenance_refuel.py": "15eb8c0ee75778fce2a1caace045940b63cd253f81d16a081f669643b9fefcae",
|
||||
"tests/test_mounted_ammo_pickup.py": "cc4f70870b10beb2595ac4fb030952cc25be1fe1d9506f54dc3901fd1baecda4",
|
||||
"tests/test_mounted_ammo_transfer.py": "cb354bfc71560f430101aae9421f04d601bc2cb0788500771d66ac750df8ae72",
|
||||
"tests/test_mounted_resupply.py": "c17525ffeca6791b4542ea0c7641e4b220b03bce506fc2fda924aac09e8b24ce",
|
||||
"tests/test_hq_mine_sampler.py": "67186d8f6609c3544a9c60f57af876b046618f9b586b2a56520d3a5c8a0e6e22",
|
||||
"tests/test_hq_status_sampler.py": "b8582fb71b7ac77a23158ccd6230e8124ef0432831204e4b8992dee3a9c10062",
|
||||
"tests/test_convoy_mine_stock.py": "e0073271428f00edf86251c9af5530f39094261b05474058df6b794bd958091f",
|
||||
"tests/test_infantry_supply_pool.py": "6fc0053337bbb889021c6eba608796ef946ff4d30a1b01cf678927293218d9d2",
|
||||
"tests/test_automatic_equipment.py": "f8cb478df7979ab9e35c2bcb084e75b2571d41e21e68c8ab28249e01f5708c92",
|
||||
"tests/test_admin_menu.py": "53286ed86862e4b71614cb3ed687aa68893fc024cce203368bf54589ab92703b",
|
||||
"tests/test_admin_providers.py": "4869c86d2cb8c17a383680d594131f4053a051eb4af369657523e20f68311de6",
|
||||
"tests/test_native_mine_stock_runner.py": "51b30d4d7d7543b1674285448d382dcbca55272a0403ea977d2cf08db6a5290c",
|
||||
"tests/test_native_convoy_mine_stock.py": "f069900030cb644ba9e04adb01a6d847a1afa6248a6c458f547aa4fcbd4b5a40",
|
||||
"tests/test_native_mounted_ammo_probe.py": "dd7169d8127fef52a4815fb4ad8227307df557170da6a22723c765bcd74ba340",
|
||||
"tests/test_hq_mounted_ammo_sampler.py": "b1c630909e5227d0f7bd0d69d7927403fcb0f33e7297d5fa52f96001720f8442",
|
||||
"tests/test_native_player_death_fixture.py": "0d0825ebb948a68ff8b8f5cbdcc7e27bdf940e5af1e277ba1780d03e12f2ba33",
|
||||
"tests/test_enemy_loadout.py": "0cdfd28ce4d9c89223a7f439eaec1cad762cfc6c9e1cc4c3213d4600a3501b37",
|
||||
"tests/test_enemy_equipment.py": "bbc753525bdfe05ea9ee7fcf45f3158307ed56428518b08845e097fbcdf299c0",
|
||||
"tests/test_infantry_mine_transfer.py": "55caf2735f2d86aa4cc474ef2d3998f847935e0a0b0d881d5390b2039a055704",
|
||||
"tests/test_infantry_mine_coordinator.py": "2fdb2138685a329cef11557973450bd5189d3d7d657dac5cc51b3df2530fab6b",
|
||||
"tests/test_enemy_radio.py": "698c3ea5e1a3680d5b0438c50a341e53dd09f08d2f1f935e59667bc72ae83765",
|
||||
"tests/test_hq_planner_budget.py": "c9e6b85a3d25267c187f503f903136a16aee382e9d842f61ff88c550b5bb4e51",
|
||||
"tests/test_ground_depot_lifecycle.py": "927a6f15560e177c838322e18ef555ce7db3acde0651d1a43353a2c6e805f3a8",
|
||||
"tests/test_ground_depot_loading.py": "61b31dc3c2a6cf6bf0aef67fe3ec92b4b2d51ee93fe47e7cc8dc77f2235747af",
|
||||
"tests/test_ground_depot_transfer.py": "fc37626765919dd449432d445a17ec25a390ed687807571a1c5e2570c4ecddb7",
|
||||
"tests/test_ground_depot_provenance.py": "33880dbf308dd4f338cc4e4bb04449d662cc2fe0941721a819528b7f7a6decd7",
|
||||
"tests/test_native_observer_selector.py": "bafdca52e25cc45d94f2fc306c078e5e59d48f2e2540248acc3822b42668e937",
|
||||
"tests/test_chat_client_ownership.py": "c559dbda1af996455038da9eeb5ffcfc6332da9b93082da8ad3071f5d72b8bdf",
|
||||
"tests/test_mine_intel.py": "db64129001c0010ad11389ff764356893ac7c4fbf45e7c82aa56bbe4c75f4929",
|
||||
"tests/test_objective_map_element.py": "b02e59f977aa81462649282f99e70b0b6320b31ba0ea46f586e9ac0c5d15b884",
|
||||
"tests/test_objective_map_presentation.py": "2418f4c0f370bd436b316e56d5ead97ff4b6aa7cdfb3777c9d2af22486148268",
|
||||
"tests/test_native_objective_map_probe.py": "ef0663aa7d620e06fda63fef578abb397a9dcd8fe5336af5445056e32e19aa28",
|
||||
"tests/test_hint_delivery.py": "6066e2b00e54553ac298a3f9b37060a23851f5ca5d4f98eb25e502b2c44240a6",
|
||||
"tests/test_observer_ownership.py": "8e485c3d2e6ae90df82cbc7829996138db8e9a5a4a42a5e2bcaa3bffa61368de",
|
||||
"tests/test_hq_ammo_reporting.py": "bdd4a30440501a2861aca80c951098c48cf999023eaaabd9a2eef82174f8173d",
|
||||
"tests/test_hq_ammo_sampler.py": "e9c2d147eb1547be471df7459f1be815cdc9ef0a64c9a4ae4ef58936424dbc55",
|
||||
"tests/test_hq_multiconvoy_supply.py": "e58d1d6eba48b2c5c6073d54c76302b2a7250ad22176fa94c386e1bc2ce65588",
|
||||
"tests/test_hq_watch.py": "51d5a728b0be83ff957c0df61289d14f90aba109b49365e457c9b200d5ede36a",
|
||||
"tests/test_infantry_ammo_transfer.py": "0d2ebe4d5c18a4187745784013d07df621b8495967d0c01f39bd5a68ae574d26",
|
||||
"tests/test_infantry_resupply_coordinator.py": "f9cf7d583a25a12cec9f5f80539b2590449560331f4ef34e33ea2db283c5d54a",
|
||||
"tests/test_infantry_resupply_planning.py": "9929ee0f5a3272c348ee329355b6ba67fdcb745fc9ce1f377f6c989eae91bf9f",
|
||||
"tests/test_mounted_supply_integration.py": "f52acf7a16c9d5273894eb5a4afe7657a7c97de2ed72ba06671d25e163eea5d7",
|
||||
"tests/test_hq_mounted_intercept.py": "444411a6a97ebaff69a056302f444f986daa5f6f7f792e622fdbcca731f939c8",
|
||||
"tests/test_hq_mounted_dispatch.py": "50e8973b730c34a54775e7ba5ae2a72c9a6a101bc9b14a1521c4b5c6d401552e",
|
||||
"tests/test_terrain_presentation.py": "05f070c5730a9f7f0aae9487a313342785a42682c9beebc3b10cac270b514f18",
|
||||
"tests/test_terrain_selection.py": "80bd8eec34f58ca139efa298509f0fa9dc01b7d35e02d2d62cb593b131c19211",
|
||||
"tests/test_native_mounted_intercept.py": "24810c0a22b221604e1fd8e6596f2d409e558699c5070fef7aae1f74a4fc2f5d",
|
||||
"tests/test_server_item_catalog.py": "c5aae32a55552095e49d9e7884ef88e1cdb4bdb257f850af67d387793c327e74",
|
||||
"tests/test_item_preferences.py": "1c52bec3aeb47b224adb3ee4385b68d4ec00214bc9f5e08fffb932af07f785b0",
|
||||
"tests/test_mortar_transactions.py": "5db4ec737355a7ae995240dc93f1dce146e07c0110c2da2c23451f180b134d26",
|
||||
"tests/test_native_convoy_probe.py": "c81046b5b3f7734f2f53dead61b01a3d81c96e841b7f9a4478c05702b29bde07",
|
||||
"tests/test_convoy_lifecycle.py": "fc18679882f1666a38587d151aa368241c5af115f3a8269dca81eb2617eb8afc",
|
||||
"tests/test_hunter_lifecycle.py": "4d228ec10c3d5f02ecf0eb755ca2773e3496a2c25a1e1c355e6ec79ee2ef53b6",
|
||||
"tests/test_convoy_survivors.py": "415cf9f5d23cce363e483d4bfd0292b58ab1fb3d9e727f9581271aa65c1c3109",
|
||||
"tests/test_hunter_capture.py": "d88b3a830c350e27a08fe5977cf85247dff140b3dc9135062eb5e7774b400790",
|
||||
"tests/test_hq_mounted_intercept_pair.py": "a8d758b1ff20bb6d1a95a9ff1d38f687e43c819c279f5ea9692e84f766dc4fb9",
|
||||
"tests/test_convoy_intercept_pair.py": "52b55727b7c2441d43530efa701045b9439b9175c71fbd2ae7577f851d2c98ec",
|
||||
"tests/native/inventory_control/README.md": "29f7086ee223b1cfe657932c7f236a1640876503f69f6f401451c72a5452695d",
|
||||
"tests/native/inventory_control/addon.gproj": "18dd7db85574119fac5057091c4d9991b247855f67f06bb86cf440f429b5911f",
|
||||
"tests/native/inventory_control/Prefabs/ERRIC_InventoryMode.et.meta": "6b4f66f296d15e7e5f82928cf1bdfa58f085421ae9b6f24afc5ac922d2a8781e",
|
||||
"tests/native/inventory_control/Prefabs/ERRIC_InventoryMode.et": "98408ab80347058f5b79b1103b849b19beaeb97a9ff81662b59fe6100ddd6bf8",
|
||||
"tests/native/inventory_control/Worlds/ERRIC_Inventory.ent.meta": "ca0420504016fa2aa7220712d13b7bf7748832d8d58b085afc4373f4abeb1339",
|
||||
"tests/native/inventory_control/Worlds/ERRIC_Inventory.ent": "22324bc7eb0d80dec8a1f3bd61a9df9201b72f749f4e751766b0c9d0b7d46b87",
|
||||
"tests/native/inventory_control/Scripts/Game/ERRIC/ERRIC_InventoryControl.c": "8707f35ecf48813bc4e72c599fe2527c44807493496218d18e89aa8739eb59f9",
|
||||
"tests/native/inventory_control/Scripts/WorkbenchGame/ERRIC/ERRIC_InventoryControlPlugin.c": "000d8fa3aa6412a5adb1561333f840f27185dc42fa61569c311b0607df0f2420",
|
||||
"tests/native/inventory_control/Worlds/ERRIC_Inventory_Layers/default.layer": "80a5f0b6c1bc8761469ea69978967dffc070ba9f98c5125430a78a506684c39e",
|
||||
"tools/native_command.py": "883c5955018e43c9451e3e6cb56e9cc8491c77920be3264478612a82c248cbbe",
|
||||
"tools/release_check.py": "65df1bff33638a583905ba5f6c583a095b9f7e38186b6f29d70b72a61525589e",
|
||||
"tools/reference_guard.py": "545b67111927d73bf2020f291528ea6f2f687a994b3e37bf567f239c32c8cd78",
|
||||
"tools/native_report.py": "bb75ef5fe551f45776d441f15a49ccad4daf19abca505187f9b2d30102538658",
|
||||
"tools/archive_native.py": "445736e18e645f1d6fac0aaf837627e94179d295c21a6a67a8e84b5b468758f9",
|
||||
"tools/compare_performance.py": "568acfee2525b8924f3e7a39784e8641f76d63bb3703dcce8251ab9ac9bb1854",
|
||||
"tools/source_manifest.py": "c1a985348bad16f3b5b69ef7e545b6341eca298d8ae08111b8340b31535cc199",
|
||||
"tools/native_provenance.py": "d4777f1f6ffed6993f7009ee2af41b100e8e0138d60ebfdcc5404ff2128f8e2a",
|
||||
"tools/dedicated_preflight.py": "ee56e05a15ada3bda80fecd74a024d141dcc3df00416821ac2c1a29ff308123f",
|
||||
"tools/dedicated_native.py": "35e0c1402be1d54f1b4345de8ac4fa7fc73f8cdfd09c7cc6f832a39dfb33e621",
|
||||
"tools/source_bundle.py": "e8b06f9340a8d67da992373f4642385a028acebe9aa1b3752f62cab95c8fce98",
|
||||
"tools/native_package.py": "32195bf9b43024028e9931a99885d0a131b28ba1b77b32e9d6ed70b914625559",
|
||||
"tools/pak_inventory.py": "95b0b64ab1f89a2142385ca596016fa131b49d933e31aef42d9bf18e2ec01383",
|
||||
"tools/archive_packed.py": "99cec180db72dc93c1ea8256bc316d42c23ab02843d5331a9d228b1a06a71d57",
|
||||
"tools/parse_native_stats.py": "32aa0935d63e59ace29fcdb1a02eaa850898b21ee4cce011a21d364e3dd69c75",
|
||||
"tools/headless_display_probe.py": "4ec3851835474eb4d8b84fc74c20d08567d92906ce832643d911f1d062abf0bd",
|
||||
"tools/private_prefix_cleanup.py": "336889ddfbfe8032eef584d8e9e222d85e33a039441f8fe9c4904e1db8ee10e9",
|
||||
"tools/headless_input_proof.py": "1952fb2ba577b80420133b9698b6d718d25211c86f853de8d22e0acb15f9f9c3",
|
||||
"tools/headless_inventory_control.py": "194121b64ee39c31cc25935afea8ee9dddbce04cbf26aaf058e25d02ee9828fe",
|
||||
"tools/ordinary_look_evidence.py": "3b6a0415352295e15e988ff78659e81981001e1ee03430a5b4c2df1518dd9a31",
|
||||
"tools/ordinary_gate_evidence.py": "5f02596c92b945b28081d861e466f01897875489e673bf0feafa6e4ebdae1708",
|
||||
"tools/prison_approach_proof.py": "fcd2936fa5a67ba6a970c56e8e4d04cc76bff6ffc6087ac95085716fb5fa673f",
|
||||
"tools/prison_approach_evidence.py": "12f1618b16f845d987ba928c8d95f2d448bc6a600fb76f8870b2ae8fd4a660cb",
|
||||
"tools/prison_alignment_proof.py": "37bd011642ca96dd0279e90cce785fc689002011b3977055f15c0dfad4161bed",
|
||||
"tools/prison_alignment_evidence.py": "67d5675c13b2de75110c53b99d1bd88e0eb0c36673488312711b03fec32e4235",
|
||||
"tools/ordinary_gate_choices.py": "59a0055d44f2f5b27c530406e5cbd46847357285d2d260d08e5c34cc53b7a795",
|
||||
"tools/prison_action_selection_proof.py": "00d397bf9b13405a2612678ec866f09d452acb4079f2072eb97c79a7fdf650da",
|
||||
"tools/prison_action_selection_evidence.py": "668b1d6ff86903ddeb702fd0c49d8d8110cd58559062d6d650f2962e4e321ab6",
|
||||
"tools/prison_held_action_proof.py": "450f03c6dbb4960d9f15a0f9c43ae5a18ab00594181e23321a2eea9b59ed47fd",
|
||||
"tools/prison_held_action_evidence.py": "971a630d663291d50c407dbadf6005e17ea035db88b5358e5ec1f6b24e6824fb",
|
||||
"tools/project_schema.py": "cc1527c5f68fb75711208f293f4751c206fedc725934d9db7d4b7fe539f60cec",
|
||||
"tools/external_addons.py": "c389b76c4d5f0f763420cd7d564d3ff5ebb7350cac10d2a26164c17f59b72dde",
|
||||
"tools/headless_game.py": "e7cab472ee51497343174cb460022b3e4591f148d4acbc6e6f881fc68c0fda1b",
|
||||
"tools/packed_external.py": "46203c078a82e825ec2e82c2495e6e165aa7ddc92368d2fe7786d87b957d3096",
|
||||
"tools/import_settings.py": "8b41ea8fb45e5c3840dad67f9d2494cba5dfa1e0e67d41e334fdd04e515a68a1",
|
||||
"tools/prefab_identity.py": "26088826275e84dcac2dbac771faf0552545791d30e9110df1ce88ee0d5a81ad",
|
||||
"tools/terrain_adapter.py": "d5ca50a5dc3ab4ba8b7d8cf2c72a9c3579b6567efbad5572acab991267116417",
|
||||
"tools/check.py": "53727a193b89ec4682abfcc429fa73b49cda279510e4747bb91803684d66b342",
|
||||
"tools/headless_workbench.py": "57f472b7a7642f35e6c2f0e2546631ee941a6f1ea6a5f85365ef59042d674264",
|
||||
"tools/desktop/kwin-right-monitor.js": "ff2b6f7b9d1b1eb9f92b61b39df18f1ddc44be5540e18b1fc1829eb2fc1fb1a2",
|
||||
"terrain-profiles/nogova.json": "4456c27cd7a4b2fef329051beedf3ccd683c94a684580bb07a35f16a9c59e743",
|
||||
"terrain-adapters/nogova/addon.gproj": "08442b784c8ead8362c19f084a9812e70783069da7740cbff7eef21f9a07e084",
|
||||
"terrain-adapters/nogova/terrain-adapter.json": "f2bdbb3ee3e03300f03d8ab184dd59f1b016c2eacb70b8873ab6fe5dbf1137d3",
|
||||
"terrain-adapters/nogova/resourceDatabase.rdb": "b12cdbacbd4fed63f460a19697b2ae8d1cbde7f9e725b73264ba3acd3584b2dc",
|
||||
"terrain-adapters/nogova/Worlds/ERR_nogova.ent": "6a2d45449ceedd13632a53c2ad612d155d3fc76b029febff7e3a5350d66952a2",
|
||||
"terrain-adapters/nogova/Worlds/ERR_nogova.ent.meta": "d6c41b4e1251829bbbb079a699a94a318b1a94e9eeade6e3fd4a019e0967c118",
|
||||
"terrain-adapters/nogova/Missions/ERR_nogova.conf": "60d542d81e48c15c838fe267e43e75273c7ec805d3a6031162dbb26fdab62889",
|
||||
"terrain-adapters/nogova/Missions/ERR_nogova.conf.meta": "cb6b7feabed57784ff41ac6d82f37df05f0e831b3f87bd18b1612332c61e21c5",
|
||||
"terrain-adapters/nogova/Worlds/ERR_nogova_Layers/default.layer": "2e9f780b1c2cacf5e34de4eaa7aa5010c0b4c81232e54246f8f466b32f909f5e",
|
||||
"terrain-integrations/nogova/terrain-adapter.json": "c6cb3d243ba68946add810256501322026e757027ff7e83cda40a02471dfb1a1",
|
||||
"terrain-integrations/nogova/Worlds/ERR_nogova.ent": "6a2d45449ceedd13632a53c2ad612d155d3fc76b029febff7e3a5350d66952a2",
|
||||
"terrain-integrations/nogova/Worlds/ERR_nogova.ent.meta": "d6c41b4e1251829bbbb079a699a94a318b1a94e9eeade6e3fd4a019e0967c118",
|
||||
"terrain-integrations/nogova/Missions/ERR_nogova.conf": "60d542d81e48c15c838fe267e43e75273c7ec805d3a6031162dbb26fdab62889",
|
||||
"terrain-integrations/nogova/Missions/ERR_nogova.conf.meta": "cb6b7feabed57784ff41ac6d82f37df05f0e831b3f87bd18b1612332c61e21c5",
|
||||
"terrain-integrations/nogova/Worlds/ERR_nogova_Layers/default.layer": "2e9f780b1c2cacf5e34de4eaa7aa5010c0b4c81232e54246f8f466b32f909f5e",
|
||||
"docs/addons.json": "ccda7c1aa6bf60524cd983ff863e178270445ef193e7418967b11cc49eb366d4",
|
||||
"docs/migration-provenance.json": "e671a9ec4dd69097a7a7f974c8a1ab0985b8070724847cfafe2ede0af88e7bee",
|
||||
"docs/dependency-audit.json": "d5e6cbcdc59c0a25e5748c590d6a195da9c97e8296119e07185f4c360bdfc4ae",
|
||||
"docs/architecture.md": "4dc8f223a793b36c92c5a70de9d2114d0e70fe36ddef873dbaef96ffa0bfc8eb",
|
||||
"docs/migration.md": "04387d3dad83ebdba4e4076fcc8ddc903a16836a3d0bf79d504d9006e6e7a17d",
|
||||
"docs/runtime-testing.md": "82a255aeb0c4cafa34b9fce9839147739c72e2e163dad18658c01cc0ee199faa",
|
||||
"docs/validation.md": "2810c18d2445c27c50f94ef92a781f9281cb3bd88ab9235834194abce3266262",
|
||||
"docs/acceptance.json": "b58e56be792c7ac2cb792a70e0b560989e59b1607fe9533f6f852e2171df9408",
|
||||
"docs/native-initial-snapshot.json": "94ca2c1fcb003823b7edc719d923df1d2387543f975fbca7ad1fb5ad3628889e",
|
||||
"docs/native-followup-snapshot.json": "156e5de03b28b0cb717e2d832afbc6dd9bfa966c28d2f9b9416415e7643ecfc3",
|
||||
"docs/original-resource-inventory.json": "bc52a981c42f933134e637a027d08a0924abb19784782731590b7ef0b0e2e73f",
|
||||
"docs/base-replacement.md": "b8af82e651366198a93824b7569430faed4f0e04018a5438a92942b98bfece25",
|
||||
"docs/site-authoring.md": "7a2e66e75ca3fdab43bd730f16e864c31909ab42fe060b00715caa13b8352ce7",
|
||||
"docs/original-feature-coverage.json": "fa260dc8f7ef94e327c9d0f6a888070c608a19f75384bbefd335915383cbfaa4",
|
||||
"docs/original-feature-coverage.md": "8386a0f56e11041915ec023a078dce627be5bb3732bf148b7d9955f19342cacc",
|
||||
"docs/configuration.md": "c89d23793e2cf8d8a9611efcc7dd584a80b568b30d9919ae8f9c6a96a6077069",
|
||||
"docs/extraction.md": "215c96cbc004b3817a70ee40f30b181a72e78123e69e16a3585ed4d8d566837d",
|
||||
"docs/mission-time.md": "29d9a18c7acef0587764becbd04c2c63684644a05c7b3b2fe77defe1881ae7a0",
|
||||
"docs/native-errors.md": "f0cbc674803f5254db0c823bce2df437cad121756e19f6c658087d5d2fddb349",
|
||||
"docs/ground-transport.md": "febce039866e290f1cdadf53ae2f6422387b1a4778264649c04727ee954c2ab6",
|
||||
"docs/radio-defence.md": "82c9c30ac75688b17fd7a5bc52e3e0b9843f2711442d4b8e5274687382c8bf06",
|
||||
"docs/performance-comparison.md": "063d46961611efb49478e462f44443b45eafa28b8ff3ca08f799b1652ba1a91f",
|
||||
"docs/release-qualification.md": "7bdf42f51fe3a4c1d6187d0e6f5248ac9a68ce16873281909bd96e1cea6c406b",
|
||||
"docs/player-lifecycle.md": "35fd066a50ddb2bcc78d4a448811e0763263cb0605161ccf43713b5e4a5f9cf0",
|
||||
"docs/base-gameplay-gaps.md": "8fb9ce4bf3d36507fdcd03af9681657c473bdf1f1d188380e7e4a83d47b10625",
|
||||
"docs/objective-widget-lifecycle.md": "3ec0ee8c74b2a3117356477a16125a077dfbf010050e53280a00769b710958a0",
|
||||
"docs/prisoner-loadout.md": "0b27f5fce4af457bea7133e461dbc37d8fa73fe126c8c3de90d723b358af389a",
|
||||
"docs/native-medical-fixture.md": "3c915a74afbcac1dfa45f0c1e9d8d4801f33704cac9ce1b66355121805ecfbb1",
|
||||
"docs/recovery.md": "026b513982530b8d65a6a1459e8707cd4abaf56450abc00f8449cf01fc660d0d",
|
||||
"docs/admin-discovery-setting.md": "a140f09cd1f27635824a9fa27e88e99259fc0030ff3228dd1376e2e949edbc1c",
|
||||
"docs/environment.md": "2a58f8a969348e42e09e45b906a3d61fb65d887ae2814547605a01987747ab6c",
|
||||
"docs/faction-catalog-source-hashes.json": "46060bf2bfe6c39517e3140acb004f9b129c26695a7ee890b4aae571b3f0da76",
|
||||
"docs/mission-faction-catalogs.md": "2ce3e49404d103a4c372ab0386de3bcf831bee87d95b1092372345a3d32ef8d2",
|
||||
"docs/medical-kit-teardown-ownership.md": "9f5c6636bfef46802800a4cfce27833fd1251067be892d95f4ce4f07469fef1a",
|
||||
"docs/site-patrols.md": "5956f70a5ebea4167819a43c2a15f8b6d6460bc724b000f989d5ad5265d48d56",
|
||||
"docs/environment-restart-fixture.md": "1c1bd3872a67c5a59c12c1293d075d52fe0b563fcd5e9e10a1ce1850c7efcea9",
|
||||
"docs/inventory-identity.md": "02092960cf937b175c5a190b426b84e0383104fa71f324c8b3a0ceb4fbe01164",
|
||||
"docs/radio-document-loss.md": "64e6dbc3a62ffd164321f7b3c1a80de05a74c26b1541bb67f6fd3513983d5fdb",
|
||||
"docs/ordinary-game-startup.md": "e979300482ad716c7a8cee532dbe3ca73b7d265b9e94fe02bd3729359551c7b2",
|
||||
"docs/guard-source-hashes.json": "874845def1458901d61adeb090a464bbee7c7b18816406ae3780ff48fe855266",
|
||||
"docs/initial-guard-readiness.md": "5f2b45f3744f1f0316f198225a34e25d93eb4795bb05d6cfded8de1f1a116c18",
|
||||
"docs/casualty-loading.md": "cddb8b1aece65dd0e3366a493e22ece02f49b1ae5d626cb9931a188551f007a4",
|
||||
"docs/native-guard-cancellation.md": "880b545b8451a86f16ef47e0a378d081c8fecb1175bf9a8a122bf853ff9b816c",
|
||||
"docs/casualty-ground-support.md": "9ffa8df2c88e51d77c29d12a23df8032d8858b0c82ff079d476f773cb6b5825e",
|
||||
"docs/casualty-ground-source-sha256.json": "90ffa7d0c61e3823922ca9d4773e3d12fd5e9a1760a35c3b29fd12a53642f83a",
|
||||
"docs/guard-cancellation-fix.md": "41f8bd169de714df4df4a61189d47a37f9be2f1b1554edc99c29d8a1fa4aaf19",
|
||||
"docs/guard-cancellation-source-hashes.json": "a1bf169c021931cdcba835d52e420df44e5dd2ef351257f0be10f63c8727afd4",
|
||||
"docs/radio-feedback.md": "3aaaecb318e2f737321bd08f9deb9323af153f2ae9ed196b55104c0616dd2be8",
|
||||
"docs/current-status.md": "027a565076981f9985019baab93413ef127d7fbb5cd336b55e33a402f4e59af2",
|
||||
"docs/initial-extraction-placement.md": "643b50d231dd9ceb49b81fd8f061cd114a9618fd1ad5a6f88724c0199a5a304c",
|
||||
"docs/initial-extraction-placement-source-sha256.json": "a44a32cedc54a70f635bf5840e7b29d35850a2212fcf64b1fd36ca096f10fffa",
|
||||
"docs/native-casualty-remove.md": "a91a9a638b19d4e8759a7168e196bf1a50bbfb328e7812d88e329166c06e69db",
|
||||
"docs/casualty-remove-source-sha256.json": "4799a5843315fe5b5b2e933e8068a8b4bae6d924a49438eccb7f68ffc8e12be9",
|
||||
"docs/ordinary-launch-callback-fix.md": "b74b8d0d011f25c367ea07b37e1a5ef488a2b378542ddca63ff9e981e649b874",
|
||||
"docs/casualty-incapacitation.md": "2811fa294de0348fbdf8e78b0c66bc31a59dd8f1e43fe16af8da87cb7e789820",
|
||||
"docs/ordinary-diagnostic-splash.md": "6e07a87a03df81dd5c9d6c0f9602b26e08ec7fd7cf941b91f2b8007bd9d33e6a",
|
||||
"docs/ordinary-splash-source-hashes.json": "b2e277e3487c2b790bce741527555bb74f6b9820b9e5626950ab709533d9fa97",
|
||||
"docs/ordinary-identity-diagnostics.md": "62c34ac6c10331025792d1785272a1605e655ca3bcbcec9ffb4852968925251e",
|
||||
"docs/native-mortar-firing.md": "0a340bb6f074531633e8b1b1189082346f4149db7f8484c08f16207767994be9",
|
||||
"docs/finite-mortar-operations.md": "d277ae850de9c33b410343b1dd498fecbe9f5c87f7c4b74829f07ca894603224",
|
||||
"docs/mortar-transactions-source-sha256.json": "dabad1cbc6eb3dd6f66b3e9ece574edcabff94adceeb2494b1adb2147608dbc0",
|
||||
"docs/local-singleplayer-admission.md": "f7fec9a8ab119d006818b518684315e28c96c7f63a1d075922a5c8af2864a5ae",
|
||||
"docs/dedicated-preparation.md": "7462af696bdb65d32e764cd98a354c9059772252bfa22d3c8aff23bd2e636c67",
|
||||
"docs/mortar-aim-renewal.md": "c26041505ea6af56146dfc9f52aa2b0cc3955ded565fdcb76892a83d1fe2e56d",
|
||||
"docs/mortar-aim-renewal-sources.json": "ceb9162fc1a4fdabf44ce7aeeda5f6d91bfbab6c6d1eb1666ce4661f1a540513",
|
||||
"docs/native-mortar-transfer.md": "7d5086b8268c213375d56ed2dc3994daf7065b2076742d0af588c8ffce5f753c",
|
||||
"docs/dedicated-native-diagnostic.md": "37d3ccf17776f5c2c2f2c8bdb87165451cc4c14eef4173162b89378f67693cd6",
|
||||
"docs/native-local-admission-probe.md": "c2794b2475d94f2756921d98f676aa8224f6fa3bc95bb14ce45c2bb7648e1566",
|
||||
"docs/dedicated-resource-audit.md": "92dbd16d7cd1b018abc5e5bd0c12bf0859f5c7af8194477eaab835e2f3fe4256",
|
||||
"docs/mortar-report-deduplication.md": "cdeeee20885c947e10507b30a2dd19b086f7aa863730c266b574cc756d466bcb",
|
||||
"docs/native-powered-flight.md": "049186db16d1b18f5d51e6eaee3e5e4c9efad9e578774501f14288781b82b972",
|
||||
"docs/mortar-expiry-evidence.md": "fb980772ef0a04568ab98457b889d6af4f3ec216382b1452e6d66eb1165345ef",
|
||||
"docs/discovery-map-usability.md": "b70b210884b642db9e28c4eefb4bde4449858089c2d8b47c3dd3209d9ba2296a",
|
||||
"docs/native-hq-response-proof.md": "9d3e43088e4874a1f6d2edc853bc2f9dfdddbfd9df3c54802dbf499a74f9910c",
|
||||
"docs/prison-breach-progression.md": "31797bfa36556cb744390ec0c7c8d7a404fe91f18a2c20ec1c0e4f122648e184",
|
||||
"docs/powered-flight-pad-survey.md": "0347bf1cfa94855ee26f22c4fa10339baa566ea10d0dee86f9190ac8a33383d2",
|
||||
"docs/item-station-teardown.md": "7a2eb028f767ec52214f375f79ed162f9ee7432a08f4229afdebe90ed338e37e",
|
||||
"docs/development-source-bundle.md": "7cd8dbc04a9cfe87ff7479283477e788552484ea1b83ffdee4893620f1df31ec",
|
||||
"docs/native-flight-footprint.md": "ef178fb48a0fce61d4eb6fa1f5533e0107f9ee6b24a26655fe17ba7449f4c776",
|
||||
"docs/native-flight-input-diagnostics.md": "4dffc58b20b2e05f3fc3d700f3feec79cad2ceacbd5fcaf9bdaabf6574de23d3",
|
||||
"docs/finite-mortar-courier.md": "96306ab4f0313b9b403f7a37b9007bf83b237de433e0cbdf1b1de361d0b2e738",
|
||||
"docs/finite-mortar-courier-source-evidence.json": "970ed87a005fe31382ffda5da77b3f18c944a57d6d1477bf9289403b39d75054",
|
||||
"docs/progression-identity.md": "be443ecba1d0c7f104dae20886c1622286f768d182d69907d4859429fd07fc84",
|
||||
"docs/vanilla-game-startup-control.md": "ff0f24a4921dd3d7c52e2895c1905e107e28824d628924125ba7e6b6b97a3ab6",
|
||||
"docs/normal-splash-audit.md": "f377cfa81ccd49c49f5f63a9d15194b063aea3554afddb934ad3679707ff6874",
|
||||
"docs/native-packaging.md": "6f83aa58d8692f0d84bb8384d3fcf812f8f1f3ef899b20aff839db0a4e2e91e8",
|
||||
"docs/spectator-guidance.md": "ad805edd230933a80950f8702b1a65a177a4f80d525f8c38d96bb74a89f38dad",
|
||||
"docs/native-mortar-salvo.md": "4358b187a9e2f518259535ce23a2f601b2242982ef0d2ad6cbe4528d1edd9819",
|
||||
"docs/pak-inventory.md": "b50157dc0dc5e4630a1626c94abc0037e8fa5db6a0973a06f3f815100b141a18",
|
||||
"docs/packed-core-startup.md": "4c9e8c6d47bb1a0ce6146070ddf4a40e1e7ad0dc710ea60f03a45b2641a85040",
|
||||
"docs/mortar-report-refresh.md": "80d7709a20930e78d138cdf792de9ead5368040ef03a94975c0222d182a6dfac",
|
||||
"docs/native-packaging-control.md": "5e1df445af29ff9d161e2a385ca90cecf283f668ee3806fc0207b55c8774cb1f",
|
||||
"docs/packed-evidence.md": "cf67a65201cc34c7316e3b677c29f301f757a81ebdb0eff83e4f705739c8a691",
|
||||
"docs/native-companion-packaging.md": "e9fc608d900c3849958eb6224f46f5619a5cd5c2e78d47536a5e3bb8d3edc31f",
|
||||
"docs/mortar-activity-completion.md": "388756d09696d1b2bf5277a945a57edd07b792b40be8ebadb709fa8d2f62ea86",
|
||||
"docs/shared-map-ownership.md": "43d8134ce6d1e6f6561634a5b18053f6295f7d7d5a575b4831995ca254f283db",
|
||||
"docs/intel-registration.md": "0568ca6f283532d2ebfc28bbcb11202b72a7ed5ac011ea66ed772272e5e87e70",
|
||||
"docs/native-shared-map-ownership.md": "c3119a4742ec35f914de6f25751316fcbfdd794dd8b395285b8b22a5fb1ecb9c",
|
||||
"docs/native-powered-translation.md": "b1efa32020ca713fe95f20a714853a67bb5b64a09781ba5d1d7cadefd06b7a93",
|
||||
"docs/aircraft-flight-controller.md": "3df786d7ce4ec6d6b52804b2a2ca86a9980fdfd203aac7eadb204458b54d88b3",
|
||||
"docs/prison-yard-surface.md": "4e881eb8eababe03c4bcb86e46fab7bca47ddd1d3ed4cd570e2bdb2e0ceadd41",
|
||||
"docs/native-aircraft-control.md": "eed101ab213e180822fc6967264efefaa75c99ee97f433fc019da45d47e33cba",
|
||||
"docs/native-server-statistics.md": "c5256d62bc6632788057df57fe5daa1fe738904bf1042e0e1bb7785aae66f4b6",
|
||||
"docs/native-end-lifecycle.md": "84bacfc43eb6011b565ca1da1db325d96098be1d4434d24b3c8a3736a0098924",
|
||||
"docs/hint-delivery.md": "35c5d8a2d1e9eda99c2f82315171fd95ca0faabdf7c1b342a45fca7db6e22755",
|
||||
"docs/preparation-status.md": "bca0997a65e5f85076d6c516be22130e5207371f8b06e74a88a140e2f1bfc837",
|
||||
"docs/headless-display-probe.md": "08a5627abe39206610d0641323947367e6631e1f87e4826789f56eae2e6b8719",
|
||||
"docs/aircraft-route.md": "c23d28a742546cc774df045c3668406b77c573bb4183aa3eef333dc6b26821f7",
|
||||
"docs/ordinary-input-observer.md": "a068702b345fd9645367606a766d57912fb5d7c6dbf615d4ba5b731b4b817d90",
|
||||
"docs/headless-input-proof.md": "da0053ff71264178de63417b28a5e2fd53d641d0188b83e5bb7c75a95773a408",
|
||||
"docs/inventory-slot-diagnosis.md": "d661908156717cd8f3545a721374a6d82c3755859267f951cd3a7625e891bd80",
|
||||
"docs/inventory-layout.md": "f22bf592a0f560ac3db2de5526d8937f899af28006b873ada4d42acd7920f088",
|
||||
"docs/prison-input-proof-plan.md": "5003c8cc190ebf9bb3c0e6b53138480b91c38c4bea2d063e01b55dc934ce7cfd",
|
||||
"docs/ordinary-look-observer.md": "20a17b23bd1c057f2c6af96d24078362c132689e87ea41d331a44f0dde54345f",
|
||||
"docs/prison-approach-design.md": "2cc363678200287d9bc13a25f5b3f8fdd749d266b1734640079cd11c29e97084",
|
||||
"docs/ordinary-gate-observer.md": "75c2ad994dbdcd8cfed1c29c83d6a73de677edc98700327e36868ae8169731c1",
|
||||
"docs/private-pointer-edge-audit.md": "29eef2bbb8ff932523f0ff501a6b3609c171224544a6b7e02461aa1f519a8cc0",
|
||||
"docs/prison-lock-alignment-plan.md": "c857bb12700a5a6be0b63735d38efe6a9c03062ab10a450bac09fed66192434b",
|
||||
"docs/prison-held-action-design.md": "89af762f9d33e8a0cc4f461ff6cf0b55036df84a05ca26fda8292d9d9a7647d7",
|
||||
"docs/prison-action-selection-design.md": "9505c865248904c21a8096a53a18baaf11799a615968c6047dcc1ab2773637b1",
|
||||
"docs/prison-approach-reliability.md": "2d6e1e243788beb27cdb6bfe1afa5a15384931625ccc075f8ebd25e9f5135596",
|
||||
"docs/native-packaging-next-audit.md": "9746148ebc0ccb5c49ccbe7aa55ed6f8a509b95ba39c83bcaca706ff88e4a454",
|
||||
"docs/aircraft-service.md": "601bd40cdce1de39cbef3cff373b17cb77371493370bf57fa20de937c50c7b1f",
|
||||
"docs/aircraft-hq-integration-plan.md": "c6d2e5dfea4379c0b2b57cc028b7555f62d47661eb9afbf6611a3dd509179238",
|
||||
"docs/aircraft-field-route-plan.md": "a44e8f670363b85543d19fb508adc6840fc9f02b3cdd80737d36872ed6e3732c",
|
||||
"docs/prison-held-action.md": "3d1360d19d73d13189b4351e2c2fdb50da1e97a393f9f17191d4531921f09d7c",
|
||||
"docs/playtest-build.md": "f9f4d763268f24b3acd80c58b8e24f2a3ae30fc7730ee6ee32a1e762a523ea82",
|
||||
"docs/owned-ai-intel-migration.md": "be048099213bbf2daca4d38a9126f2f4d68e267894163f2ac641209a92f2c7e2",
|
||||
"docs/chat-integration.md": "28a94422f4339ef47a549540518c14afc419add48bd25b9aa98cdb9e3297e181",
|
||||
"docs/external-addons.json": "c340fe315efa9bad37ec9f249a259f51b833e66ea6f38bd774f1573753216ece",
|
||||
"docs/external-addons.md": "8deff9567694245390cccd8a22176c6d7791ab56686d93ee732841f111f6d3b8",
|
||||
"docs/ai-equipment-migration.md": "819ed66d7f68a83ca979ec0d78dc8dc8b9538e44317d5c5e70904a15cf488be6",
|
||||
"docs/convoy-bmp-seat-audit.json": "54811861f71c320c026b4d870cc4773a1afd16f9d7b894d709fff99db7959b21",
|
||||
"docs/ai-feature-migration.md": "3f5d301e48d9dac57be8ea0bb1e67a897c90462a9289d9227f02e58bb9648f07",
|
||||
"docs/ai-feature-migration.json": "68dfb0b523b650fc82648953d3ed390dc68617d0ed9cc5bce86145b3e7b99eea",
|
||||
"docs/convoy-migration.md": "1cf205d1ea3579a8300f75fb31b260f73f3267269c67925196d13ee548e1e461",
|
||||
"docs/hq-ammunition.md": "4e9e78855e33d6381ebf89a5586062d264d5a2c6a862faa7f9b58f1f1e10480f",
|
||||
"docs/ai-migration-progress.md": "3af61e888d2de139fac21b9dc9163ecd8b83bebd29921906559687d938dc0986",
|
||||
"docs/vehicle-security.md": "3ba235ec441d4215925b275a7a4f261d8851285deb7d0e4474958d3a78501193",
|
||||
"docs/vehicle-security-vanilla-sha256.json": "16ee4be00be331023668238f5f0272050f83031ee627f8c0fa234f88552aec5a",
|
||||
"docs/vanilla-resources.json": "d80edf67d429b578051cad1630d3c44fae70714d14a277e40294242f9b439783",
|
||||
"docs/vehicle-maintenance.md": "94362113ce91d248485b2ce25f9f99e60e55d1ac50d6d6dd728cba1fc797043c",
|
||||
"docs/vehicle-maintenance-source-evidence.json": "c84283c91ef554d8c6ded79ae9aecdfe00ad6922a150d4733260dc7a5a3d2fa2",
|
||||
"docs/radio-equipment-provenance.json": "ec67aeec4ec116dda41aba4f3f2448e739859b8f4082d548a5deb7992371f021",
|
||||
"docs/hq-originating-call.md": "ca1bbaa5f511b65a392d6dd6e361b6898a85b201f219f8e0bce25c30cef27a77",
|
||||
"docs/packed-external-development.md": "6e7cd3e3efc143de42ba44febe9b03811f7c263d4000593dfacaf28fcc3ef287",
|
||||
"docs/convoy-startup.md": "754b2ef659749d3307809f314eb2fcb4b16506c03e019f191ee5cf4e68a630aa",
|
||||
"docs/infantry-resupply-access.md": "8eb812945ba95660501ec3fd6626a354eadf9e68eddffb34bc66071e250e5fca",
|
||||
"docs/hq-local-handoff.md": "8225ba1ea2cef47930a4e04fea82a87dab92a8761349c3b7da5c918079ef675b",
|
||||
"docs/road-interception.md": "521b0cc50b29c8d3f60dacc184c2fe6554d83779367492e176ee3f60d82801be",
|
||||
"docs/single-mod.md": "31c628fb5bcf00486bddba527a8955e3f996cf31f9342b6e0bb2bdb314e38c19",
|
||||
"docs/multiplayer-prefab-ids.md": "b9dc13f6d5448871959d2f7872847af8824f21ec773f2683ed117d43c1a2a77d",
|
||||
"docs/prison-opening.md": "be60fee1542185bfae38cba9bacf1f181acf2a032af33d5eb853468a036cc54a",
|
||||
"docs/content-packs.md": "c543cbec225d6feb722c5f0a7bc848e3bda265bf89092bf5629b71d1ee4bf0ed",
|
||||
"docs/supply-convoys.md": "b1fcfc475da1e295af3f9129472fde5ea4003f9590b598fdacba01969b41ae62",
|
||||
"docs/generated-sites.md": "0caf082c45e4ac84af5749ff6554312f6db66bfcb7e3485611c0040bb9d2196d",
|
||||
"docs/mortar-stations.md": "eda8ce9201fc1fda4c777174dd83aec93e6b5aad35c3632aef7494969ca539df",
|
||||
"docs/prison-patrol-and-debug-intel.md": "c02f57e65981eb74773fc4db2a9a82ac80b5b45276735ad5cf2af4a767caf172",
|
||||
"docs/airfield-extraction-and-map-icons.md": "5f21746c0b685811992765e6909f83d4a47eb6ff11b17974405136bb5d858350",
|
||||
"docs/mortar-intel-and-prison-map.md": "116f87df517804919363836ea577ac032e9bdcebf33da83c19ca1323e1731076",
|
||||
"docs/radio-camp-security.md": "ffd6f794601dcfe46e3c373aa7a868782366fa6e234b189d81af78a40d84fc81",
|
||||
"docs/hill-scouts.md": "8d6ff1a57334fd915925396558c9f8fb915fbb1f8689fb72e7bad3ddc7367a9f",
|
||||
"docs/search-helicopter.md": "6220eb11e64245a580d85bad683baa07f0ae499b5d7673e973bc401e87907482",
|
||||
"docs/hq-air-support.md": "b090de3a96b93f67987c27de182d5c05ee1890e98816c6a5441a7e54d626614d",
|
||||
"docs/aggression-ai.md": "62c2c8aeb23eaf085b54e2e480ce066588fd2a4df2e1a5180333f7107bc2ebdd",
|
||||
"docs/enemy-loadouts.md": "3d00ed31e6a169658a5d950c12b278cccdd7501c791b18d6ab8b2ffb90d06cef",
|
||||
"docs/gameplay-recovery.md": "ae1ffa270ab27afe9e2542be2f0b96b6a8527f4ad243ba4247e1f772b242f9f1",
|
||||
"docs/field-sites.md": "9c3928a04b4b242dcf806d7b20ecf2f4cc636aff9a31ed90d9fd75cfaf776aa0",
|
||||
"docs/town-garrisons.md": "c58e6b669997f8e1763a3da289e6ac6b777b15f0852eb2cd81b4c83fe2783eb4",
|
||||
"docs/ai-local-awareness.md": "9583de1d5233dde3cd259d464e0d5a859de9a69b24725acc2d141822fd3f9908",
|
||||
"docs/response-medics-combat.md": "17192d50d1a406de0547ce30a19eb8d9b216730d6c07ba2a89939687096faf1e",
|
||||
"docs/infantry-combat-priority.md": "a2609f8596d58d320f088b0f5c2e5de8ced6d94365ae1a8407fe763ea2da5db7",
|
||||
"docs/gameplay-review-2026-09-27.md": "ee66710a5fdefc1d1ced769e15119f6f1449fb9268b9a62733a8b2f9b733bc71",
|
||||
"docs/helicopter-standoff.md": "d804e148383f71d4457e773f62664b99d3e28401ba756f3fe6e447419aaa4350",
|
||||
"docs/airborne-validation-2026-09-27.md": "54b4655df8a449da5abf6ff04097119f8b6ae2ce69613b57e9dca3cd6f821000",
|
||||
"docs/mortar-rapid-fire.md": "412ef3c1cda90f774f9134db1ab150cb1e1d09189598e1beaecf5d1395b04a98",
|
||||
"docs/convoy-coordination.md": "095fa1f425688ad920e86363c88490aac6bd623f0dbe8dd2f039674cdc5908ff",
|
||||
"docs/server-log-fixes-2026-09-29.md": "44f69289ae12f1f38b71045d613c2500da6b7b3adbbdfec7166d7b216bfbf576",
|
||||
"docs/body-recovery-fixes-2026-09-29.md": "ce9f2978e3f2da13fab0bbbf88150b1f6dc7400a74c6ba7418f8a8b704c6f82b",
|
||||
"docs/infantry-resupply.md": "2fb21a42c768319368d28781b04c3fa4945f22277f0b76be1ed34a74b77b85b2",
|
||||
"docs/resupply-fixes-2026-09-30.md": "9e21d5b29f20350a2ee8c7b1fdc839c199e5c98777e88b310f14f7a583d70cdd",
|
||||
"docs/mission-checkpoint-2026-09-30.md": "48104b9c2a089404ed93da20d15a0e57283004bc8dfe0cea0b7477909791c7d0",
|
||||
"docs/combined-candidate-2026-09-30.md": "c53428454b2a6ea16f5e691fd4333f6f1bf2a6ad882f0fd05504f8e71b414d92",
|
||||
"docs/multiconvoy-resupply.md": "90e699e15914461392808802ec6029382dab1a2fc1a63eb0ab534557565661a4",
|
||||
"docs/hq-chronology-and-admission-2026-09-30.md": "1254daf6a44f27df8917805ce506d2005379641d9e4c10d9b396f5f0464fa1c9",
|
||||
"docs/empty-magazine-return.md": "d08e227c4b7d273687e9aca6645cdcdef8d6a88843ba00fb938fecfd4c8df154",
|
||||
"docs/next-gameplay-increments.md": "ee1a22dd3a79acf95fd3e59aad4599788a0f10f22eb4dbe1577875a651a18430",
|
||||
"docs/terrain-adapters.md": "70cfd9f50ef5e339de7ae98f48bf501eb4feb018d6a5739b465cf7407fcfcd95",
|
||||
"docs/nogova-publishing.md": "1a80072599a3e9bd460091c4b3ea39ff78324304730494641c94deb2b00adab7",
|
||||
"docs/mission-hq-fixes-2026-10-01.md": "5379ddb403f7c3611b2e92e0a9b4c043278fe136e28f93293dae1027ddc30ab9",
|
||||
"docs/player-route.md": "57165a47ef27c2fbeada41c2a68e3567f1e2ceef99fd1ac1ca89277fdc0041d5",
|
||||
"docs/terrain-takeover.md": "3504c5bcf820adcdfc4640f90ec2ea4347ac96b4b425d11658b2868c21bd5db1",
|
||||
"docs/ground-depot-loading.md": "6913d5e05d779a1c6c3149ad5eea46555c685ce4e35490f05bd1f1ef89ce1ded",
|
||||
"docs/objective-map-presentation.md": "eccf546db122634ad3528207070b5aaa4005e72bd615f7ddd477d08481875305",
|
||||
"docs/mounted-interception.md": "20f80f34919c32535c1d57da7894ecadee6699f7f17b1993a32bed5837342bec",
|
||||
"README.md": "6f1b1fdc61fd7ba17861a284c5ebec05be642c0e116ba28306d8e16b8760af4e",
|
||||
".gitignore": "b965eff620081f103bc18f8e6a37c670629e1275261448ea3af22688695edc2b"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"existing_paths_checked": 10406,
|
||||
"expected_changed_paths": [
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c",
|
||||
"docs/mounted-interception.md",
|
||||
"tests/test_hq_mounted_dispatch.py",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c",
|
||||
"tests/test_convoy_column.py",
|
||||
"tools/check.py"
|
||||
],
|
||||
"unexpected_changes": [],
|
||||
"original_tracked_files_checked": 533,
|
||||
"original_tracked_unchanged": true,
|
||||
"index_diff_sha256": "670073db5d7531aca4466cc9d2827d10a46bfcfbd35f27ac97724de9d2fd3725",
|
||||
"existing_package_unmodified": true
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"feature": "Two original convoy escorts dispatched to spaced positions on one road from delivered HQ memory",
|
||||
"authored_files": 420,
|
||||
"changed_production_files": [
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c"
|
||||
],
|
||||
"regression_programs": 294,
|
||||
"native_assertions": 63,
|
||||
"native_completions": 1,
|
||||
"native_script_failures": 0,
|
||||
"native_engine_errors": 13,
|
||||
"strict_native_failure_retained": true,
|
||||
"native_archive": "docs/evidence/native-2026-10-03-mounted-pair-normal",
|
||||
"cleanup_source_external_unchanged": true,
|
||||
"original_tracked_files_unchanged": 533,
|
||||
"prior_paths_preserved_except_allowlisted": 10406,
|
||||
"candidate_files_unchanged": 429,
|
||||
"index_diff_sha256": "670073db5d7531aca4466cc9d2827d10a46bfcfbd35f27ac97724de9d2fd3725",
|
||||
"first_native_compile_failure": "docs/evidence/native-2026-10-03-mounted-pair-compile",
|
||||
"native_compiler_fix": "Renamed new choice.Vehicle field to choice.Escort; native Vehicle type collision. Dispatch adapter changed correspondingly.",
|
||||
"package_refreshed": false,
|
||||
"release_qualified": false,
|
||||
"limits": [
|
||||
"Native run proves compilation and ordinary local escape integration, not paired physical driving/holding/rejoining.",
|
||||
"Native AddWaypoint/ClearOrder operations are not transactional; callbacks cancel exact owned claims after commit begins.",
|
||||
"Human escape, real multiplayer/JIP/reconnect/twelve-player play and comparative FPS remain unverified.",
|
||||
"Existing development package predates paired interception and remains unchanged."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
from pathlib import Path
|
||||
import sys,hashlib,json,subprocess,importlib.util,time,traceback,os
|
||||
R=Path('/home/jakub/devel/escapistsrereintroduced');B=Path('/tmp/erri-pair-root-0054');sys.path.insert(0,str(R/'tools'))
|
||||
sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
from source_manifest import addon_manifest
|
||||
def capture_inputs():
|
||||
result=addon_manifest(R)
|
||||
for folder in ['tests','tools','terrain-profiles','terrain-adapters','terrain-integrations']:
|
||||
result.update({str(p.relative_to(R)):sha(p) for p in (R/folder).rglob('*') if p.is_file() and '__pycache__' not in p.parts and p.suffix != '.pyc'})
|
||||
result.update({str(p.relative_to(R)):sha(p) for p in (R/'docs').iterdir() if p.is_file()})
|
||||
for name in ['README.md','.gitignore']: result[name]=sha(R/name)
|
||||
return result
|
||||
inputs=capture_inputs()
|
||||
(B/'suite-inputs.json').write_text(json.dumps(inputs,indent=2)+'\n')
|
||||
removed_overrides={key:os.environ.pop(key) for key in list(os.environ) if key.startswith('ERR_')}
|
||||
original=subprocess.run;programs=[]
|
||||
def track(args,*a,**kw):
|
||||
entry=None
|
||||
if isinstance(args,list) and len(args)>1 and str(args[1]).startswith(str(R/'tests')+'/'):
|
||||
entry={'path':str(Path(args[1]).relative_to(R)),'exit':None};programs.append(entry)
|
||||
try:result=original(args,*a,**kw)
|
||||
except subprocess.CalledProcessError as error:
|
||||
if entry is not None:entry['exit']=error.returncode
|
||||
raise
|
||||
if entry is not None:entry['exit']=result.returncode
|
||||
return result
|
||||
subprocess.run=track
|
||||
spec=importlib.util.spec_from_file_location('repository_check',R/'tools/check.py');module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module)
|
||||
code=0;started=time.monotonic()
|
||||
try:module.check()
|
||||
except Exception:traceback.print_exc();code=1
|
||||
unchanged=capture_inputs()==inputs;code=code or int(not unchanged)
|
||||
(B/'suite-result.json').write_text(json.dumps({'exit':code,'seconds':round(time.monotonic()-started,2),'inputs_unchanged':unchanged,'removed_override_names':sorted(removed_overrides),'regression_programs':len(programs),'programs':programs},indent=2)+'\n')
|
||||
print('Suite exit',code,'programs',len(programs),'unchanged',unchanged)
|
||||
raise SystemExit(code)
|
||||
+717
@@ -0,0 +1,717 @@
|
||||
// Independent physical adaptation of the owner's supply-convoy extension.
|
||||
// HQ must dispatch an authored route using a finite canonical pool receipt.
|
||||
class ERR_ConvoyResources
|
||||
{
|
||||
static ResourceName VehiclePrefab(int index)
|
||||
{
|
||||
if (index == 1) return "{3336BE330C4F355B}Prefabs/Vehicles/Wheeled/Ural4320/Ural4320_ammo.et";
|
||||
if (index == 2) return "{16C1F16C9B053801}Prefabs/Vehicles/Wheeled/Ural4320/Ural4320_transport.et";
|
||||
if (index == 4) return "{1DC01AAACFFA8B63}Prefabs/Vehicles/Tracked/BMP1/BMP1_Base.et";
|
||||
if (index == 0 || index == 3 || index == 5) return "{0B4DEA8078B78A9B}Prefabs/Vehicles/Wheeled/UAZ469/UAZ469_PKM.et";
|
||||
return "";
|
||||
}
|
||||
static ResourceName GroupPrefab() { return "{7A968AB297475FA7}Prefabs/ERR/ConvoyCrew.et"; }
|
||||
static ResourceName Soldier()
|
||||
{
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
if (mode && mode.FactionSelection() && mode.FactionSelection().Ready() && mode.FactionSelection().Active("enemy") != "ussr") return mode.FactionSelection().Soldier();
|
||||
return "{DCB41B3746FDD1BE}Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_Rifleman.et"; }
|
||||
static ResourceName Rifle() { return "{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et"; }
|
||||
static ResourceName Magazine() { return "{BBB50A815A2F916B}Prefabs/Weapons/Magazines/Magazine_545x39_AK_30rnd_Ball.et"; }
|
||||
static bool Available()
|
||||
{
|
||||
array<ResourceName> required = {GroupPrefab(), Soldier(), Rifle(), Magazine(), ERR_Resources.MoveWaypoint()};
|
||||
for (int i = 0; i < 6; i++) required.Insert(VehiclePrefab(i));
|
||||
foreach (ResourceName prefab : required)
|
||||
{
|
||||
Resource resource = Resource.Load(prefab);
|
||||
if (!resource || !resource.IsValid()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_ConvoyRules
|
||||
{
|
||||
static bool EmptyCrewTemplate(SCR_AIGroup group)
|
||||
{
|
||||
// The vanilla faction base authors six queued soldiers. Only our memberless
|
||||
// template may receive finite seat occupants; never retire another spawn job.
|
||||
return group && !group.GetSpawnImmediately() && group.GetAgentsCount() == 0
|
||||
&& (!group.m_aUnitPrefabSlots || group.m_aUnitPrefabSlots.IsEmpty());
|
||||
}
|
||||
static bool Plan(array<vector> positions, array<vector> angles, ERR_ForceRoute route)
|
||||
{
|
||||
if (!positions || !angles || positions.Count() != 6 || angles.Count() != 6 || !route || route.Looped() || route.Count() < 2 || !route.Seal()) return false;
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
if (!ERR_ForceRules.FiniteVector(positions[i]) || !ERR_ForceRules.FiniteVector(angles[i])) return false;
|
||||
for (int j = 0; j < i; j++) if (vector.DistanceXZ(positions[i], positions[j]) < 18) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static bool GroundClear(array<vector> positions, array<vector> angles)
|
||||
{
|
||||
if (!positions || !angles || positions.Count() != 6 || angles.Count() != 6) return false;
|
||||
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
|
||||
for (int i = 0; i < positions.Count(); i++) if (!placement.Clear(positions[i], angles[i][0])) return false;
|
||||
return true;
|
||||
}
|
||||
static bool PartOf(IEntity entity, IEntity root)
|
||||
{
|
||||
int depth;
|
||||
while (entity && depth++ < 32)
|
||||
{
|
||||
if (entity == root) return true;
|
||||
entity = entity.GetParent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static bool Captured(Vehicle vehicle, SCR_AIGroup group)
|
||||
{
|
||||
if (group && group.GetPlayerCount(true) > 0) return true;
|
||||
if (!vehicle) return false;
|
||||
array<int> ids = {}; GetGame().GetPlayerManager().GetPlayers(ids);
|
||||
foreach (int id : ids)
|
||||
{
|
||||
IEntity player = GetGame().GetPlayerManager().GetPlayerControlledEntity(id);
|
||||
if (player && PartOf(CompartmentAccessComponent.GetVehicleIn(player), vehicle)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static bool CollectSeats(IEntity root, array<BaseCompartmentSlot> seats)
|
||||
{
|
||||
array<IEntity> pending = {root};
|
||||
int cursor;
|
||||
while (cursor < pending.Count() && cursor < 128)
|
||||
{
|
||||
IEntity entity = pending[cursor++];
|
||||
if (!entity) return false;
|
||||
SCR_BaseCompartmentManagerComponent manager = SCR_BaseCompartmentManagerComponent.Cast(entity.FindComponent(SCR_BaseCompartmentManagerComponent));
|
||||
if (manager)
|
||||
{
|
||||
array<BaseCompartmentSlot> local = {}; manager.GetCompartments(local);
|
||||
foreach (BaseCompartmentSlot seat : local) if (seat && !seats.Contains(seat)) seats.Insert(seat);
|
||||
}
|
||||
IEntity child = entity.GetChildren();
|
||||
while (child)
|
||||
{
|
||||
if (pending.Count() >= 128 || pending.Contains(child)) return false;
|
||||
pending.Insert(child); child = child.GetSibling();
|
||||
}
|
||||
}
|
||||
return cursor == pending.Count();
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_ConvoyVehicle
|
||||
{
|
||||
int Index;
|
||||
Vehicle Entity;
|
||||
SCR_AIGroup Group;
|
||||
IEntity Driver;
|
||||
ref array<IEntity> Members = {};
|
||||
ref array<BaseCompartmentSlot> Seats = {};
|
||||
AIWaypoint Waypoint;
|
||||
int RouteIndex;
|
||||
bool Released;
|
||||
bool Registered;
|
||||
// Latched only for an exact tracked hunter at authoritative player entry.
|
||||
bool PlayerCaptureObserved;
|
||||
ref ERR_ConvoyCombatState Combat = new ERR_ConvoyCombatState();
|
||||
int SupplyToken;
|
||||
bool SupplyHeld;
|
||||
int InterceptToken;
|
||||
bool InterceptActive;
|
||||
AIWaypoint InterceptWaypoint;
|
||||
ref ERR_ConvoyInterceptOrder InterceptOrder;
|
||||
ERR_ConvoyReceipt InterceptReceipt;
|
||||
ERR_ForceRecord InterceptRecord;
|
||||
ERR_ForceRoute InterceptRoute;
|
||||
protected AIWaypoint m_InterceptWaypoint;
|
||||
protected Vehicle m_InterceptEntity;
|
||||
protected SCR_AIGroup m_InterceptGroup;
|
||||
protected IEntity m_InterceptDriver;
|
||||
protected ref array<IEntity> m_InterceptMembers = {};
|
||||
protected ref array<BaseCompartmentSlot> m_InterceptSeats = {};
|
||||
protected int m_InterceptToken;
|
||||
protected int m_LastInterceptToken;
|
||||
protected vector m_InterceptPosition;
|
||||
protected float m_InterceptExpiresAt;
|
||||
protected int m_InterceptRouteIndex;
|
||||
|
||||
bool Arrived;
|
||||
float LegDeadline;
|
||||
float NextReadinessNote;
|
||||
|
||||
// Pending downlink owns a claim only; normal authored travel continues.
|
||||
bool InterceptCrew(ERR_GameMode mode)
|
||||
{
|
||||
if (!mode || !CrewValid(mode) || !Entity || !Group || ERR_ConvoyRules.Captured(Entity, Group)) return false;
|
||||
if (Members.IsEmpty() || Members.Count() > 8 || Seats.Count() != Members.Count()) return false;
|
||||
if (InterceptOrder && (Members.Count() != m_InterceptMembers.Count() || Seats.Count() != m_InterceptSeats.Count())) return false;
|
||||
array<AIAgent> agents = {}; Group.GetAgents(agents);
|
||||
if (agents.Count() != Members.Count()) return false;
|
||||
for (int i = 0; i < Members.Count(); i++)
|
||||
{
|
||||
if (!Members[i] || !ERR_Actors.CanFight(Members[i]) || !Seats[i] || Seats[i].GetOccupant() != Members[i]) return false;
|
||||
int attributed;
|
||||
foreach (AIAgent agent : agents)
|
||||
{
|
||||
if (!agent || !Members.Contains(agent.GetControlledEntity())) return false;
|
||||
if (agent.GetControlledEntity() == Members[i]) attributed++;
|
||||
}
|
||||
if (attributed != 1) return false;
|
||||
if (InterceptOrder && (Members[i] != m_InterceptMembers[i] || Seats[i] != m_InterceptSeats[i])) return false;
|
||||
}
|
||||
if (InterceptOrder && (Entity != m_InterceptEntity || Group != m_InterceptGroup || Driver != m_InterceptDriver)) return false;
|
||||
return true;
|
||||
}
|
||||
bool InterceptMatches(ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
return order && InterceptOrder == order && InterceptToken == m_InterceptToken && m_InterceptToken != 0;
|
||||
}
|
||||
bool ClaimIntercept(ERR_GameMode mode, ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
if (!order || order.Token <= m_LastInterceptToken || InterceptToken != 0 || InterceptOrder || InterceptWaypoint || !InterceptCrew(mode)) return false;
|
||||
m_InterceptEntity = Entity; m_InterceptGroup = Group; m_InterceptDriver = Driver;
|
||||
m_InterceptMembers.Clear(); m_InterceptSeats.Clear();
|
||||
foreach (IEntity member : Members) m_InterceptMembers.Insert(member);
|
||||
foreach (BaseCompartmentSlot seat : Seats) m_InterceptSeats.Insert(seat);
|
||||
m_InterceptToken = order.Token; m_LastInterceptToken = order.Token; InterceptToken = order.Token; InterceptOrder = order;
|
||||
m_InterceptPosition = order.Position; m_InterceptExpiresAt = order.ExpiresAt;
|
||||
return true;
|
||||
}
|
||||
void ClearIntercept(ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
if (!InterceptMatches(order)) return;
|
||||
// Delete only the exact waypoint this owner created, never an adopted handle.
|
||||
if (m_InterceptWaypoint && m_InterceptGroup) m_InterceptGroup.RemoveWaypoint(m_InterceptWaypoint);
|
||||
if (m_InterceptWaypoint) SCR_EntityHelper.DeleteEntityAndChildren(m_InterceptWaypoint);
|
||||
if (InterceptWaypoint == m_InterceptWaypoint) InterceptWaypoint = null;
|
||||
m_InterceptWaypoint = null; InterceptToken = 0; m_InterceptToken = 0;
|
||||
InterceptActive = false; InterceptOrder = null; InterceptReceipt = null; InterceptRecord = null; InterceptRoute = null;
|
||||
m_InterceptMembers.Clear(); m_InterceptSeats.Clear();
|
||||
}
|
||||
// Pair preparation owns no route state. Recheck after both native allocations.
|
||||
bool CanInstallIntercept(ERR_GameMode mode, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!InterceptMatches(order) || order.Token != m_InterceptToken || InterceptActive || !InterceptCrew(mode)) return false;
|
||||
return !(order.Position != m_InterceptPosition) && order.ExpiresAt == m_InterceptExpiresAt && now < m_InterceptExpiresAt;
|
||||
}
|
||||
// Caller transfers ownership of the prepared handle before AddWaypoint, which
|
||||
// is a native boundary rather than a transactional pair operation.
|
||||
void InstallIntercept(AIWaypoint detour)
|
||||
{
|
||||
m_InterceptRouteIndex = RouteIndex;
|
||||
m_InterceptWaypoint = detour; InterceptWaypoint = detour; InterceptActive = true;
|
||||
Group.AddWaypoint(detour);
|
||||
}
|
||||
bool DeliverIntercept(ERR_GameMode mode, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!InterceptMatches(order) || order.Token != m_InterceptToken || InterceptActive || !InterceptCrew(mode)) return false;
|
||||
if (order.Position != m_InterceptPosition || order.ExpiresAt != m_InterceptExpiresAt || now >= m_InterceptExpiresAt) return false;
|
||||
AIWaypoint detour = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), m_InterceptPosition));
|
||||
if (!detour) return false;
|
||||
detour.SetCompletionRadius(15);
|
||||
ClearOrder(); m_InterceptRouteIndex = RouteIndex;
|
||||
m_InterceptWaypoint = detour; InterceptWaypoint = detour; InterceptActive = true;
|
||||
Group.AddWaypoint(detour);
|
||||
return true;
|
||||
}
|
||||
// True suppresses ordinary travel only after actual radio delivery. No route
|
||||
// index is consumed by arrival at the detour; removal leaves the seated group held.
|
||||
bool TickIntercept(ERR_GameMode mode, float now)
|
||||
{
|
||||
if (!InterceptOrder) return false;
|
||||
if (!InterceptMatches(InterceptOrder)) return InterceptActive;
|
||||
if (Released || Arrived || now >= m_InterceptExpiresAt || !InterceptCrew(mode) || Combat.Held || SupplyHeld || SupplyToken != 0)
|
||||
{ bool delivered = InterceptActive; ClearIntercept(InterceptOrder); if (delivered) LegDeadline = now + 600000; return false; }
|
||||
if (!InterceptActive) return false;
|
||||
if (RouteIndex != m_InterceptRouteIndex || InterceptWaypoint != m_InterceptWaypoint)
|
||||
{ ClearIntercept(InterceptOrder); return false; }
|
||||
if (m_InterceptWaypoint && vector.DistanceXZ(Entity.GetOrigin(), m_InterceptPosition) <= 15)
|
||||
{
|
||||
Group.RemoveWaypoint(m_InterceptWaypoint); SCR_EntityHelper.DeleteEntityAndChildren(m_InterceptWaypoint);
|
||||
m_InterceptWaypoint = null; InterceptWaypoint = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void ClearOrder()
|
||||
{
|
||||
if (Waypoint && Group) Group.RemoveWaypoint(Waypoint);
|
||||
if (Waypoint) SCR_EntityHelper.DeleteEntityAndChildren(Waypoint);
|
||||
Waypoint = null;
|
||||
}
|
||||
void Release(ERR_GameMode mode)
|
||||
{
|
||||
if (Released) return;
|
||||
ClearIntercept(InterceptOrder);
|
||||
Released = true;
|
||||
Combat.Held = false; SupplyToken = 0; SupplyHeld = false;
|
||||
ClearOrder();
|
||||
if (mode && mode.Forces())
|
||||
{
|
||||
ERR_ForceRecord record = mode.Forces().FindGroup(Group);
|
||||
if (record) record.Pinned = false;
|
||||
}
|
||||
if (Group)
|
||||
{
|
||||
ERR_HQ hq;
|
||||
if (mode) hq = ERR_HQ.Cast(mode.Command());
|
||||
bool retained = false;
|
||||
if (Registered && !PlayerCaptureObserved && hq && !ERR_ConvoyRules.Captured(Entity, Group)) retained = hq.ReleaseConvoySurvivors(Group);
|
||||
if (!retained && mode && mode.Command()) mode.Command().UnregisterGroup(Group);
|
||||
Group.SetPermanentLOD(-1);
|
||||
array<AIAgent> agents = {}; Group.GetAgents(agents);
|
||||
foreach (AIAgent agent : agents) if (agent) agent.SetPermanentLOD(-1);
|
||||
}
|
||||
// Vehicle, cargo, casualties and surviving crew remain physical and lootable.
|
||||
}
|
||||
bool CrewValid(ERR_GameMode mode)
|
||||
{
|
||||
if (!Group || !Entity || !ERR_Actors.Alive(Entity) || Group.GetFaction() != mode.EnemyFaction() || !ERR_Actors.CanFight(Driver)) return false;
|
||||
if (ERR_Actors.Player(Driver) || !ERR_ConvoyRules.PartOf(CompartmentAccessComponent.GetVehicleIn(Driver), Entity)) return false;
|
||||
foreach (IEntity member : Members)
|
||||
if (!ERR_Actors.Alive(member) || ERR_Actors.Player(member) || ERR_Actors.Group(member) != Group) return false;
|
||||
return true;
|
||||
}
|
||||
bool Order(ERR_ForceRoute route, float now)
|
||||
{
|
||||
if (Released || Arrived || InterceptActive || Combat.Held || SupplyHeld || !Group || !Entity || !route || RouteIndex >= route.Count()) return false;
|
||||
if (Waypoint) return true;
|
||||
Waypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), route.At(RouteIndex)));
|
||||
if (!Waypoint) return false;
|
||||
Waypoint.SetCompletionRadius(35);
|
||||
Group.AddWaypoint(Waypoint);
|
||||
LegDeadline = now + 600000;
|
||||
return true;
|
||||
}
|
||||
void Tick(ERR_GameMode mode, ERR_ForceRoute route, float now)
|
||||
{
|
||||
if (TickIntercept(mode, now)) return;
|
||||
if (Released || Arrived) return;
|
||||
if (!CrewValid(mode)) { Release(mode); return; }
|
||||
if (Combat.Held || SupplyHeld) return;
|
||||
// Native completion measures the seated group, not the vehicle origin.
|
||||
// Keep the same predicate after native removal of the completed waypoint.
|
||||
if (Waypoint && Waypoint.IsWithinCompletionRadius(Group))
|
||||
{
|
||||
ClearOrder(); RouteIndex++;
|
||||
if (RouteIndex >= route.Count()) { Arrived = true; return; }
|
||||
}
|
||||
if (Waypoint && now >= LegDeadline) { Release(mode); return; }
|
||||
if (!Order(route, now)) Release(mode);
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_ConvoyCargoCallback : ScriptedInventoryOperationCallback
|
||||
{
|
||||
ResourceName Prefab;
|
||||
bool Finished;
|
||||
bool Succeeded;
|
||||
protected void Finish(bool success) { if (!Finished) { Finished = true; Succeeded = success; } }
|
||||
protected override void OnComplete() { Finish(true); }
|
||||
protected override void OnFailed() { Finish(false); }
|
||||
protected override void OnAllComplete() { Finish(true); }
|
||||
protected override void OnAllFailed() { Finish(false); }
|
||||
}
|
||||
|
||||
class ERR_ConvoyTask
|
||||
{
|
||||
ref ERR_ConvoyReceipt Receipt;
|
||||
ref ERR_ForceRoute Route;
|
||||
ref array<ref ERR_ConvoyVehicle> Vehicles = {};
|
||||
ref array<ref ERR_ConvoyCargoCallback> Cargo = {};
|
||||
bool Stopped;
|
||||
bool Ready;
|
||||
bool CargoIssued;
|
||||
float Deadline;
|
||||
float NextCheck;
|
||||
string Reason;
|
||||
void Stop(ERR_GameMode mode, string reason)
|
||||
{
|
||||
if (Stopped) return;
|
||||
Stopped = true; Reason = reason;
|
||||
foreach (ERR_ConvoyVehicle vehicle : Vehicles) vehicle.Release(mode);
|
||||
ERR_HQ.Event("CONVOY STOP", reason + "; surviving entities and cargo preserved");
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_ConvoyService
|
||||
{
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected ERR_ConvoyPool m_Pool;
|
||||
protected ref array<ref ERR_ConvoyTask> m_Tasks = {};
|
||||
protected bool m_Stopped;
|
||||
void ERR_ConvoyService(ERR_GameMode mode, ERR_ConvoyPool pool) { m_Mode = mode; m_Pool = pool; }
|
||||
protected bool Current()
|
||||
{
|
||||
return !m_Stopped && Replication.IsServer() && m_Mode && m_Mode == ERR_GameMode.Get() && m_Mode.Active() && m_Pool;
|
||||
}
|
||||
protected ERR_ConvoyTask NewTask() { return new ERR_ConvoyTask(); }
|
||||
protected ERR_ConvoyVehicle NewVehicle() { return new ERR_ConvoyVehicle(); }
|
||||
ERR_ConvoyTask At(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_Tasks.Count()) return null;
|
||||
return m_Tasks[index];
|
||||
}
|
||||
ERR_ConvoyTask Find(string source)
|
||||
{
|
||||
foreach (ERR_ConvoyTask task : m_Tasks) if (task.Receipt && task.Receipt.SourceId == source) return task;
|
||||
return null;
|
||||
}
|
||||
protected bool FillCrew(ERR_ConvoyVehicle unit)
|
||||
{
|
||||
array<BaseCompartmentSlot> all = {};
|
||||
if (!ERR_ConvoyRules.CollectSeats(unit.Entity, all)) return false;
|
||||
array<BaseCompartmentSlot> drivers = {}, turrets = {}, cargo = {};
|
||||
foreach (BaseCompartmentSlot slot : all)
|
||||
{
|
||||
if (!slot.IsCompartmentAccessible() || slot.IsOccupied()) continue;
|
||||
if (slot.GetType() == ECompartmentType.PILOT) drivers.Insert(slot);
|
||||
else if (slot.GetType() == ECompartmentType.TURRET) turrets.Insert(slot);
|
||||
else if (slot.GetType() == ECompartmentType.CARGO) cargo.Insert(slot);
|
||||
}
|
||||
int requiredTurrets = 1;
|
||||
if (unit.Index == 1 || unit.Index == 2) requiredTurrets = 0;
|
||||
if (unit.Index == 4) requiredTurrets = 2;
|
||||
if (drivers.Count() != 1 || turrets.Count() < requiredTurrets || (unit.Index == 2 && cargo.Count() < 8)) return false;
|
||||
unit.Seats.Insert(drivers[0]);
|
||||
for (int i = 0; i < requiredTurrets; i++) unit.Seats.Insert(turrets[i]);
|
||||
if (unit.Index == 2) for (int i = 0; i < 8; i++) unit.Seats.Insert(cargo[i]);
|
||||
// Exact planned population is known before any character is created.
|
||||
if (unit.Seats.Count() != ERR_ConvoyPool.CrewCount(unit.Index)) return false;
|
||||
AIGroup nativeGroup = unit.Group;
|
||||
foreach (BaseCompartmentSlot seat : unit.Seats)
|
||||
{
|
||||
IEntity actor = seat.SpawnCharacterInCompartment(ERR_ConvoyResources.Soldier(), nativeGroup, ERR_ConvoyResources.GroupPrefab());
|
||||
if (actor) { unit.Members.Insert(actor); m_Mode.Own(actor); }
|
||||
if (!actor || nativeGroup != unit.Group) return false;
|
||||
}
|
||||
unit.Driver = unit.Members[0];
|
||||
return true;
|
||||
}
|
||||
ERR_ConvoyTask Dispatch(ERR_ConvoyReceipt receipt, array<vector> positions, array<vector> angles, ERR_ForceRoute route, float now)
|
||||
{
|
||||
if (!Current() || !ERR_ForceRules.Finite(now) || now < 0 || !ERR_ConvoyRules.Plan(positions, angles, route) || !ERR_ConvoyResources.Available() || !ERR_ConvoyRules.GroundClear(positions, angles)) return null;
|
||||
// The server role selection owns the memberless group affiliation.
|
||||
if (!m_Mode.EnemyFaction() || !m_Pool.Consume(receipt)) return null;
|
||||
ERR_ConvoyTask task = NewTask(); task.Receipt = receipt; task.Route = route; task.Deadline = now + 30000;
|
||||
m_Tasks.Insert(task);
|
||||
for (int index = 0; index < 6; index++)
|
||||
{
|
||||
ERR_ConvoyVehicle unit = NewVehicle(); unit.Index = index; task.Vehicles.Insert(unit);
|
||||
unit.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), positions[index]));
|
||||
if (unit.Group) m_Mode.Own(unit.Group);
|
||||
unit.Entity = Vehicle.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.VehiclePrefab(index), positions[index], angles[index]));
|
||||
if (unit.Entity) m_Mode.Own(unit.Entity);
|
||||
if (!unit.Group || !unit.Entity || unit.Group.GetFaction() != m_Mode.EnemyFaction() || !ERR_ConvoyRules.EmptyCrewTemplate(unit.Group) || !FillCrew(unit))
|
||||
{ task.Stop(m_Mode, "finite convoy startup failed; receipt consumed without retry"); return task; }
|
||||
FactionAffiliationComponent affiliation = FactionAffiliationComponent.Cast(unit.Entity.FindComponent(FactionAffiliationComponent));
|
||||
if (affiliation) affiliation.SetAffiliatedFaction(m_Mode.EnemyFaction());
|
||||
AICarMovementComponent movement = AICarMovementComponent.Cast(unit.Entity.FindComponent(AICarMovementComponent));
|
||||
if (movement) movement.SetCruiseSpeed(35);
|
||||
}
|
||||
ERR_HQ.Event("CONVOY CREATED", "six physical vehicles; finite crew created; native readiness and ledger admission pending");
|
||||
return task;
|
||||
}
|
||||
protected string ReadinessReason(ERR_ConvoyVehicle unit)
|
||||
{
|
||||
if (unit.Registered) return "already admitted";
|
||||
if (!unit.Group) return "missing group";
|
||||
if (!unit.Entity) return "missing vehicle";
|
||||
if (!ERR_Actors.Alive(unit.Entity)) return "vehicle destroyed or lacks damage state";
|
||||
if (unit.Group.GetFaction() != m_Mode.EnemyFaction()) return "group faction mismatch";
|
||||
if (!ERR_Actors.CanFight(unit.Driver)) return "driver absent/dead/unconscious or controller unavailable";
|
||||
if (ERR_Actors.Player(unit.Driver)) return "driver controlled by player";
|
||||
if (!ERR_ConvoyRules.PartOf(CompartmentAccessComponent.GetVehicleIn(unit.Driver), unit.Entity)) return "driver outside intended vehicle";
|
||||
foreach (IEntity member : unit.Members)
|
||||
{
|
||||
if (!ERR_Actors.Alive(member)) return "member absent/dead or damage state unavailable";
|
||||
if (ERR_Actors.Player(member)) return "member controlled by player";
|
||||
if (ERR_Actors.Group(member) != unit.Group) return "member native agent/parent group mismatch";
|
||||
}
|
||||
array<AIAgent> agents = {}; unit.Group.GetAgents(agents);
|
||||
if (agents.Count() != unit.Members.Count()) return "native agent count differs from original crew";
|
||||
foreach (AIAgent agent : agents)
|
||||
{
|
||||
if (!agent) return "null native agent";
|
||||
if (!unit.Members.Contains(agent.GetControlledEntity())) return "native agent controls foreign entity";
|
||||
if (!ERR_Actors.CanFight(agent.GetControlledEntity())) return "native agent actor cannot fight";
|
||||
}
|
||||
SCR_AIGroupUtilityComponent utility = unit.Group.GetGroupUtilityComponent();
|
||||
if (!utility) return "group utility unavailable";
|
||||
if (!utility.m_VehicleMgr) return "group vehicle manager unavailable";
|
||||
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(unit.Entity.FindComponent(SCR_AIVehicleUsageComponent));
|
||||
if (!usage) return "vehicle usage component unavailable";
|
||||
for (int i = 0; i < unit.Seats.Count(); i++)
|
||||
if (i >= unit.Members.Count() || !unit.Seats[i] || unit.Seats[i].GetOccupant() != unit.Members[i]) return "intended seat occupant mismatch";
|
||||
return "readiness guards satisfied; awaiting admission";
|
||||
}
|
||||
protected void InspectReadiness(ERR_ConvoyVehicle unit, float now)
|
||||
{
|
||||
if (!unit || now < unit.NextReadinessNote) return;
|
||||
unit.NextReadinessNote = now + 5000;
|
||||
int agents, activeAgents;
|
||||
bool activeGroup;
|
||||
if (unit.Group)
|
||||
{
|
||||
array<AIAgent> current = {}; unit.Group.GetAgents(current);
|
||||
agents = current.Count(); activeGroup = unit.Group.IsAIActivated();
|
||||
foreach (AIAgent agent : current) if (agent && agent.IsAIActivated()) activeAgents++;
|
||||
}
|
||||
ERR_HQ.Event("CONVOY READINESS", string.Format("vehicle=%1 reason=%2 members=%3 agents=%4 activeAgents=%5 activeGroup=%6 seats=%7 registered=%8", unit.Index, ReadinessReason(unit), unit.Members.Count(), agents, activeAgents, activeGroup, unit.Seats.Count(), unit.Registered));
|
||||
}
|
||||
protected bool Admit(ERR_ConvoyTask task, float now)
|
||||
{
|
||||
foreach (ERR_ConvoyVehicle observed : task.Vehicles) InspectReadiness(observed, now);
|
||||
foreach (ERR_ConvoyVehicle unit : task.Vehicles)
|
||||
{
|
||||
if (unit.Registered) continue;
|
||||
if (!unit.CrewValid(m_Mode)) return false;
|
||||
array<AIAgent> agents = {}; unit.Group.GetAgents(agents);
|
||||
if (agents.Count() != unit.Members.Count()) return false;
|
||||
foreach (AIAgent agent : agents)
|
||||
if (!agent || !unit.Members.Contains(agent.GetControlledEntity()) || !ERR_Actors.CanFight(agent.GetControlledEntity())) return false;
|
||||
SCR_AIGroupUtilityComponent utility = unit.Group.GetGroupUtilityComponent();
|
||||
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(unit.Entity.FindComponent(SCR_AIVehicleUsageComponent));
|
||||
if (!utility || !utility.m_VehicleMgr || !usage || !utility.m_VehicleMgr.TryAddVehicle(usage)) return false;
|
||||
ERR_ForceRecord record = m_Mode.Forces().Register(task.Receipt.SourceId + "-vehicle-" + unit.Index.ToString(), ERR_ForceKind.CONVOY, unit.Entity.GetOrigin(), unit.Members.Count(), null, unit.Group, now);
|
||||
if (!record || !m_Pool.BindRecord(task.Receipt, unit.Index, record)) { task.Stop(m_Mode, "convoy ledger admission rejected"); return false; }
|
||||
record.Pinned = true;
|
||||
unit.Registered = true;
|
||||
m_Mode.RegisterGroup(unit.Group);
|
||||
ERR_HQ hq = ERR_HQ.Cast(m_Mode.Command());
|
||||
if (hq)
|
||||
{
|
||||
hq.ReserveGroup(unit.Group);
|
||||
if (!hq.BindVehicle(unit.Group, unit.Entity))
|
||||
ERR_HQ.Event("MOUNTED AMMO", "original convoy vehicle binding unavailable; no replacement lookup");
|
||||
}
|
||||
// Native helper idempotently registers deletion/damage callbacks and mounted
|
||||
// combat activity; raw vehicle-manager admission alone omits those hooks.
|
||||
utility.AddUsableVehicle(usage);
|
||||
unit.Group.SetPermanentLOD(0); unit.Group.ActivateAI();
|
||||
foreach (AIAgent active : agents) { active.SetPermanentLOD(0); active.ActivateAI(); }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected void IssueCargo(ERR_ConvoyTask task)
|
||||
{
|
||||
if (task.CargoIssued || task.Stopped || task.Vehicles.Count() != 6) return;
|
||||
task.CargoIssued = true;
|
||||
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(task.Vehicles[1].Entity.FindComponent(InventoryStorageManagerComponent));
|
||||
if (!inventory) { ERR_HQ.Event("CONVOY CARGO", "ammo truck inventory unavailable; no retry or free replacement"); return; }
|
||||
for (int i = 0; i < 56; i++)
|
||||
{
|
||||
ResourceName prefab = ERR_ConvoyResources.Magazine();
|
||||
if (i < 8) prefab = ERR_ConvoyResources.Rifle();
|
||||
ERR_ConvoyCargoCallback operation = new ERR_ConvoyCargoCallback(); operation.Prefab = prefab; task.Cargo.Insert(operation);
|
||||
if (!inventory.TrySpawnPrefabToStorage(prefab, null, -1, EStoragePurpose.PURPOSE_DEPOSIT, operation)) operation.Finished = true;
|
||||
}
|
||||
ERR_HQ.Event("CONVOY CARGO", "finite target submitted once: eight AK74 and 48 magazines; native capacity/completion may reduce actual stock");
|
||||
}
|
||||
void Tick(float now)
|
||||
{
|
||||
if (!Current() || !ERR_ForceRules.Finite(now)) return;
|
||||
foreach (ERR_ConvoyTask task : m_Tasks)
|
||||
{
|
||||
if (task.Stopped || now < task.NextCheck) continue;
|
||||
task.NextCheck = now + 1000;
|
||||
bool captured;
|
||||
foreach (ERR_ConvoyVehicle unit : task.Vehicles) if (ERR_ConvoyRules.Captured(unit.Entity, unit.Group)) captured = true;
|
||||
if (captured) { task.Stop(m_Mode, "player captured convoy vehicle or joined crew"); continue; }
|
||||
if (!task.Ready)
|
||||
{
|
||||
if (now >= task.Deadline) { task.Stop(m_Mode, "native crew readiness expired; no replacement"); continue; }
|
||||
if (!Admit(task, now)) continue;
|
||||
task.Ready = true; IssueCargo(task);
|
||||
}
|
||||
bool pending;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
vehicle.Tick(m_Mode, task.Route, now);
|
||||
if (!vehicle.Released && !vehicle.Arrived) pending = true;
|
||||
}
|
||||
if (!pending) task.Stop(m_Mode, "finite authored road route ended or all vehicle orders released");
|
||||
}
|
||||
}
|
||||
protected bool InterceptEligible(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
if (!Current() || !task || !order || !m_Tasks.Contains(task) || task.Stopped || !task.Ready || !task.Receipt || !task.Receipt.Consumed) return false;
|
||||
if (!m_Mode.Forces()) return false;
|
||||
ERR_HQ hq = ERR_HQ.Cast(m_Mode.Command());
|
||||
if (!hq || hq.LogisticsToken(order.Group) != order.Token) return false;
|
||||
if (order.Token < 400001 || order.Token > 400256 || order.SourceId != task.Receipt.SourceId) return false;
|
||||
if (order.VehicleIndex != 0 && order.VehicleIndex != 3 && order.VehicleIndex != 5) return false;
|
||||
if (order.VehicleIndex >= task.Vehicles.Count() || order.VehicleIndex >= task.Receipt.Records.Count()) return false;
|
||||
ERR_ConvoyVehicle unit = task.Vehicles[order.VehicleIndex];
|
||||
if (!unit || !unit.Registered || unit.Released || unit.Arrived || unit.Index != order.VehicleIndex || unit.Group != order.Group) return false;
|
||||
if (!unit.InterceptCrew(m_Mode) || !task.Route || unit.RouteIndex < 0 || unit.RouteIndex >= task.Route.Count()) return false;
|
||||
ERR_ForceRecord record = m_Mode.Forces().FindGroup(unit.Group);
|
||||
if (!record || !record.Physical || record.Eliminated || record.Kind != ERR_ForceKind.CONVOY || record.Id != task.Receipt.Records[unit.Index]) return false;
|
||||
if (record.Group != unit.Group || record.SourceId != task.Receipt.SourceId + "-vehicle-" + unit.Index.ToString()) return false;
|
||||
if (unit.InterceptOrder && (unit.InterceptReceipt != task.Receipt || unit.InterceptRecord != record || unit.InterceptRoute != task.Route)) return false;
|
||||
foreach (ERR_ConvoyVehicle other : task.Vehicles)
|
||||
if (!other || other.SupplyToken != 0 || other.SupplyHeld || other.Combat.Held) return false;
|
||||
return true;
|
||||
}
|
||||
bool InterceptCurrent(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
return InterceptEligible(task, order) && task.Vehicles[order.VehicleIndex].InterceptToken == order.Token
|
||||
&& task.Vehicles[order.VehicleIndex].InterceptMatches(order);
|
||||
}
|
||||
bool ReserveIntercept(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!InterceptEligible(task, order) || !ERR_ForceRules.Finite(now) || now < 0) return false;
|
||||
if (!ERR_ForceRules.Finite(order.ObservedAt) || !ERR_ForceRules.Finite(order.IssuedAt) || !ERR_ForceRules.Finite(order.ExpiresAt)) return false;
|
||||
if (order.ObservedAt < 0 || order.IssuedAt < order.ObservedAt || now < order.IssuedAt || now >= order.ExpiresAt) return false;
|
||||
if (order.ExpiresAt > order.ObservedAt + 120000 || !ERR_ForceRules.FiniteVector(order.Position)) return false;
|
||||
if (order.BeliefId <= 0 || order.BeliefRevision <= 0 || order.ReportId <= 0) return false;
|
||||
foreach (ERR_ConvoyTask existing : m_Tasks)
|
||||
foreach (ERR_ConvoyVehicle claimed : existing.Vehicles) if (claimed && claimed.InterceptToken != 0) return false;
|
||||
ERR_ConvoyVehicle unit = task.Vehicles[order.VehicleIndex];
|
||||
if (!unit.ClaimIntercept(m_Mode, order)) return false;
|
||||
unit.InterceptReceipt = task.Receipt; unit.InterceptRecord = m_Mode.Forces().FindGroup(unit.Group); unit.InterceptRoute = task.Route;
|
||||
return true;
|
||||
}
|
||||
protected bool InterceptPairEligible(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder first, ERR_ConvoyInterceptOrder second, float now)
|
||||
{
|
||||
if (!first || !second || first == second || first.Group == second.Group || first.VehicleIndex == second.VehicleIndex || first.Token == second.Token) return false;
|
||||
if (!InterceptEligible(task, first) || !InterceptEligible(task, second) || !ERR_ForceRules.Finite(now) || now < 0) return false;
|
||||
if (task.Vehicles[first.VehicleIndex].Entity == task.Vehicles[second.VehicleIndex].Entity) return false;
|
||||
if (first.BeliefId <= 0 || first.BeliefRevision <= 0 || first.ReportId <= 0) return false;
|
||||
if (first.BeliefId != second.BeliefId || first.BeliefRevision != second.BeliefRevision || first.ReportId != second.ReportId) return false;
|
||||
if (first.ObservedAt != second.ObservedAt || first.IssuedAt != second.IssuedAt || first.ExpiresAt != second.ExpiresAt) return false;
|
||||
if (!ERR_ForceRules.Finite(first.ObservedAt) || !ERR_ForceRules.Finite(first.IssuedAt) || !ERR_ForceRules.Finite(first.ExpiresAt)) return false;
|
||||
if (first.ObservedAt < 0 || first.IssuedAt < first.ObservedAt || now < first.IssuedAt || now >= first.ExpiresAt || first.ExpiresAt > first.ObservedAt + 120000) return false;
|
||||
if (!ERR_ForceRules.FiniteVector(first.Position) || !ERR_ForceRules.FiniteVector(second.Position)) return false;
|
||||
return vector.DistanceXZ(first.Position, second.Position) >= 40;
|
||||
}
|
||||
bool ReserveInterceptPair(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder first, ERR_ConvoyInterceptOrder second, float now)
|
||||
{
|
||||
if (!InterceptPairEligible(task, first, second, now)) return false;
|
||||
foreach (ERR_ConvoyTask existing : m_Tasks)
|
||||
foreach (ERR_ConvoyVehicle claimed : existing.Vehicles) if (claimed && claimed.InterceptToken != 0) return false;
|
||||
ERR_ConvoyVehicle lead = task.Vehicles[first.VehicleIndex];
|
||||
ERR_ConvoyVehicle partner = task.Vehicles[second.VehicleIndex];
|
||||
if (!lead.ClaimIntercept(m_Mode, first)) return false;
|
||||
lead.InterceptReceipt = task.Receipt; lead.InterceptRecord = m_Mode.Forces().FindGroup(lead.Group); lead.InterceptRoute = task.Route;
|
||||
if (!InterceptPairEligible(task, first, second, now) || !partner.ClaimIntercept(m_Mode, second))
|
||||
{ lead.ClearIntercept(first); return false; }
|
||||
partner.InterceptReceipt = task.Receipt; partner.InterceptRecord = m_Mode.Forces().FindGroup(partner.Group); partner.InterceptRoute = task.Route;
|
||||
return true;
|
||||
}
|
||||
bool ApplyInterceptPair(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder first, ERR_ConvoyInterceptOrder second, float now)
|
||||
{
|
||||
if (!InterceptPairEligible(task, first, second, now) || !InterceptCurrent(task, first) || !InterceptCurrent(task, second)) return false;
|
||||
ERR_ConvoyVehicle lead = task.Vehicles[first.VehicleIndex];
|
||||
ERR_ConvoyVehicle partner = task.Vehicles[second.VehicleIndex];
|
||||
if (!lead.CanInstallIntercept(m_Mode, first, now) || !partner.CanInstallIntercept(m_Mode, second, now)) return false;
|
||||
AIWaypoint firstWaypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), first.Position));
|
||||
if (!firstWaypoint) return false;
|
||||
AIWaypoint secondWaypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), second.Position));
|
||||
if (!secondWaypoint) { SCR_EntityHelper.DeleteEntityAndChildren(firstWaypoint); return false; }
|
||||
firstWaypoint.SetCompletionRadius(15); secondWaypoint.SetCompletionRadius(15);
|
||||
if (!InterceptPairEligible(task, first, second, now) || !InterceptCurrent(task, first) || !InterceptCurrent(task, second)
|
||||
|| task.Vehicles[first.VehicleIndex] != lead || task.Vehicles[second.VehicleIndex] != partner
|
||||
|| !lead.CanInstallIntercept(m_Mode, first, now) || !partner.CanInstallIntercept(m_Mode, second, now))
|
||||
{ SCR_EntityHelper.DeleteEntityAndChildren(firstWaypoint); SCR_EntityHelper.DeleteEntityAndChildren(secondWaypoint); return false; }
|
||||
// Route removal is also a native boundary; both prepared handles remain
|
||||
// caller-owned until both exact claims have survived it.
|
||||
lead.ClearOrder();
|
||||
if (InterceptPairEligible(task, first, second, now) && InterceptCurrent(task, first) && InterceptCurrent(task, second)) partner.ClearOrder();
|
||||
if (!InterceptPairEligible(task, first, second, now) || !InterceptCurrent(task, first) || !InterceptCurrent(task, second)
|
||||
|| task.Vehicles[first.VehicleIndex] != lead || task.Vehicles[second.VehicleIndex] != partner
|
||||
|| !lead.CanInstallIntercept(m_Mode, first, now) || !partner.CanInstallIntercept(m_Mode, second, now))
|
||||
{
|
||||
SCR_EntityHelper.DeleteEntityAndChildren(firstWaypoint); SCR_EntityHelper.DeleteEntityAndChildren(secondWaypoint);
|
||||
ReleaseIntercept(task, first, now); ReleaseIntercept(task, second, now); return false;
|
||||
}
|
||||
lead.InstallIntercept(firstWaypoint);
|
||||
// AddWaypoint can invoke native listeners. Do not install the partner after
|
||||
// cancellation/ownership loss; release only these exact owned claims.
|
||||
if (!InterceptPairEligible(task, first, second, now) || !InterceptCurrent(task, first) || !lead.InterceptActive
|
||||
|| !InterceptCurrent(task, second) || task.Vehicles[first.VehicleIndex] != lead || task.Vehicles[second.VehicleIndex] != partner
|
||||
|| !partner.CanInstallIntercept(m_Mode, second, now))
|
||||
{
|
||||
SCR_EntityHelper.DeleteEntityAndChildren(secondWaypoint);
|
||||
ReleaseIntercept(task, first, now); ReleaseIntercept(task, second, now); return false;
|
||||
}
|
||||
partner.InstallIntercept(secondWaypoint);
|
||||
if (InterceptPairEligible(task, first, second, now) && InterceptCurrent(task, first) && InterceptCurrent(task, second)
|
||||
&& task.Vehicles[first.VehicleIndex] == lead && task.Vehicles[second.VehicleIndex] == partner
|
||||
&& lead.InterceptActive && partner.InterceptActive) return true;
|
||||
ReleaseIntercept(task, first, now); ReleaseIntercept(task, second, now); return false;
|
||||
}
|
||||
bool ApplyIntercept(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!InterceptCurrent(task, order) || !ERR_ForceRules.Finite(now) || now < order.IssuedAt || now >= order.ExpiresAt) return false;
|
||||
return task.Vehicles[order.VehicleIndex].DeliverIntercept(m_Mode, order, now);
|
||||
}
|
||||
void ReleaseIntercept(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!task || !order || !m_Tasks.Contains(task) || !ERR_ForceRules.Finite(now)) return;
|
||||
foreach (ERR_ConvoyVehicle unit : task.Vehicles)
|
||||
if (unit && unit.InterceptMatches(order))
|
||||
{ bool delivered = unit.InterceptActive; unit.ClearIntercept(order); if (delivered) unit.LegDeadline = now + 600000; }
|
||||
}
|
||||
bool ApplyCombatOrder(ERR_ConvoyTask task, ERR_ConvoyCombatOrder order, float now)
|
||||
{
|
||||
if (!Current() || !task || !order || !m_Tasks.Contains(task) || task.Stopped || !task.Ready || !task.Receipt || !task.Receipt.Consumed) return false;
|
||||
foreach (ERR_ConvoyVehicle intercept : task.Vehicles) if (intercept && intercept.InterceptToken != 0) return false;
|
||||
if (order.VehicleIndex < 0 || order.VehicleIndex >= task.Vehicles.Count() || order.SourceId != task.Receipt.SourceId) return false;
|
||||
ERR_ConvoyVehicle unit = task.Vehicles[order.VehicleIndex];
|
||||
if (!unit || !unit.Registered || unit.Released || unit.Arrived || unit.Index != order.VehicleIndex || unit.Group != order.Group || !unit.CrewValid(m_Mode)) return false;
|
||||
if (ERR_ConvoyRules.Captured(unit.Entity, unit.Group) || unit.Index >= task.Receipt.Records.Count()) return false;
|
||||
if (!ERR_ConvoyCombatRules.Accept(unit.Combat, order, now) || !task.Route || unit.RouteIndex >= task.Route.Count()) return false;
|
||||
ERR_ForceRecord record = m_Mode.Forces().FindGroup(unit.Group);
|
||||
if (!record || !record.Physical || record.Eliminated || record.Kind != ERR_ForceKind.CONVOY || record.Id != task.Receipt.Records[unit.Index]) return false;
|
||||
array<AIAgent> agents = {}; unit.Group.GetAgents(agents);
|
||||
if (agents.Count() != unit.Members.Count()) return false;
|
||||
foreach (AIAgent agent : agents)
|
||||
if (!agent || !unit.Members.Contains(agent.GetControlledEntity())) return false;
|
||||
// No caller can replay even a command whose native waypoint recreation fails.
|
||||
unit.Combat.LastCommandId = order.CommandId;
|
||||
if (order.Hold)
|
||||
{
|
||||
if (!unit.Combat.Held) unit.ClearOrder();
|
||||
unit.Combat.Held = true; unit.Combat.ReportId = order.ReportId; unit.Combat.ObservedAt = order.ObservedAt;
|
||||
}
|
||||
else
|
||||
{
|
||||
unit.Combat.Held = false;
|
||||
if (!unit.SupplyHeld && !unit.Order(task.Route, now)) { unit.Release(m_Mode); return false; }
|
||||
}
|
||||
ERR_HQ.Event("CONVOY COVER", string.Format("command M%1 report M%2 vehicle=%3 held=%4 routeIndex=%5", order.CommandId, order.ReportId, unit.Index, unit.Combat.Held, unit.RouteIndex));
|
||||
return true;
|
||||
}
|
||||
int Count() { return m_Tasks.Count(); }
|
||||
string Status()
|
||||
{
|
||||
int ready, stopped, vehicles, completed, failed, pending;
|
||||
foreach (ERR_ConvoyTask task : m_Tasks)
|
||||
{
|
||||
if (task.Ready) ready++;
|
||||
if (task.Stopped) stopped++;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles) if (vehicle && vehicle.Entity) vehicles++;
|
||||
foreach (ERR_ConvoyCargoCallback operation : task.Cargo)
|
||||
{
|
||||
if (!operation.Finished) pending++;
|
||||
else if (operation.Succeeded) completed++;
|
||||
else failed++;
|
||||
}
|
||||
}
|
||||
string reason;
|
||||
if (m_Tasks.Count() == 1 && m_Tasks[0].Stopped) reason = m_Tasks[0].Reason;
|
||||
if (reason.Length() > 160) reason = reason.Substring(0, 160);
|
||||
return string.Format("Convoys: %1 tasks, %2 reached readiness, %3 stopped, %4 retained physical vehicles; cargo native insertion successes=%5 failed=%6 pending=%7; current inventory may differ after use/looting", m_Tasks.Count(), ready, stopped, vehicles, completed, failed, pending) + "; reason=" + reason;
|
||||
}
|
||||
void Stop()
|
||||
{
|
||||
if (m_Stopped) return;
|
||||
m_Stopped = true;
|
||||
foreach (ERR_ConvoyTask task : m_Tasks) task.Stop(m_Mode, "HQ ended convoy task service");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-pair-service-0054/tests/test_convoy_intercept_pair.py", line 9, in <module>
|
||||
assert 'bool ReserveInterceptPair(' in source and 'bool ApplyInterceptPair(' in source, 'paired service API absent (baseline feature gap)'
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
AssertionError: paired service API absent (baseline feature gap)
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"path": "addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c",
|
||||
"working_sha256": "e503e3a2eeaff638dee7fac6830cfbac6b8d87402e1191ff65aaa9961d9e0b86",
|
||||
"index_sha256": "e1d5dff5584f1bf12c146df510c860c3e0e046c989ff758def88ecf6b49c6392"
|
||||
}
|
||||
+634
@@ -0,0 +1,634 @@
|
||||
// Independent physical adaptation of the owner's supply-convoy extension.
|
||||
// HQ must dispatch an authored route using a finite canonical pool receipt.
|
||||
class ERR_ConvoyResources
|
||||
{
|
||||
static ResourceName VehiclePrefab(int index)
|
||||
{
|
||||
if (index == 1) return "{3336BE330C4F355B}Prefabs/Vehicles/Wheeled/Ural4320/Ural4320_ammo.et";
|
||||
if (index == 2) return "{16C1F16C9B053801}Prefabs/Vehicles/Wheeled/Ural4320/Ural4320_transport.et";
|
||||
if (index == 4) return "{1DC01AAACFFA8B63}Prefabs/Vehicles/Tracked/BMP1/BMP1_Base.et";
|
||||
if (index == 0 || index == 3 || index == 5) return "{0B4DEA8078B78A9B}Prefabs/Vehicles/Wheeled/UAZ469/UAZ469_PKM.et";
|
||||
return "";
|
||||
}
|
||||
static ResourceName GroupPrefab() { return "{7A968AB297475FA7}Prefabs/ERR/ConvoyCrew.et"; }
|
||||
static ResourceName Soldier()
|
||||
{
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
if (mode && mode.FactionSelection() && mode.FactionSelection().Ready() && mode.FactionSelection().Active("enemy") != "ussr") return mode.FactionSelection().Soldier();
|
||||
return "{DCB41B3746FDD1BE}Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_Rifleman.et"; }
|
||||
static ResourceName Rifle() { return "{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et"; }
|
||||
static ResourceName Magazine() { return "{BBB50A815A2F916B}Prefabs/Weapons/Magazines/Magazine_545x39_AK_30rnd_Ball.et"; }
|
||||
static bool Available()
|
||||
{
|
||||
array<ResourceName> required = {GroupPrefab(), Soldier(), Rifle(), Magazine(), ERR_Resources.MoveWaypoint()};
|
||||
for (int i = 0; i < 6; i++) required.Insert(VehiclePrefab(i));
|
||||
foreach (ResourceName prefab : required)
|
||||
{
|
||||
Resource resource = Resource.Load(prefab);
|
||||
if (!resource || !resource.IsValid()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_ConvoyRules
|
||||
{
|
||||
static bool EmptyCrewTemplate(SCR_AIGroup group)
|
||||
{
|
||||
// The vanilla faction base authors six queued soldiers. Only our memberless
|
||||
// template may receive finite seat occupants; never retire another spawn job.
|
||||
return group && !group.GetSpawnImmediately() && group.GetAgentsCount() == 0
|
||||
&& (!group.m_aUnitPrefabSlots || group.m_aUnitPrefabSlots.IsEmpty());
|
||||
}
|
||||
static bool Plan(array<vector> positions, array<vector> angles, ERR_ForceRoute route)
|
||||
{
|
||||
if (!positions || !angles || positions.Count() != 6 || angles.Count() != 6 || !route || route.Looped() || route.Count() < 2 || !route.Seal()) return false;
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
if (!ERR_ForceRules.FiniteVector(positions[i]) || !ERR_ForceRules.FiniteVector(angles[i])) return false;
|
||||
for (int j = 0; j < i; j++) if (vector.DistanceXZ(positions[i], positions[j]) < 18) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static bool GroundClear(array<vector> positions, array<vector> angles)
|
||||
{
|
||||
if (!positions || !angles || positions.Count() != 6 || angles.Count() != 6) return false;
|
||||
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
|
||||
for (int i = 0; i < positions.Count(); i++) if (!placement.Clear(positions[i], angles[i][0])) return false;
|
||||
return true;
|
||||
}
|
||||
static bool PartOf(IEntity entity, IEntity root)
|
||||
{
|
||||
int depth;
|
||||
while (entity && depth++ < 32)
|
||||
{
|
||||
if (entity == root) return true;
|
||||
entity = entity.GetParent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static bool Captured(Vehicle vehicle, SCR_AIGroup group)
|
||||
{
|
||||
if (group && group.GetPlayerCount(true) > 0) return true;
|
||||
if (!vehicle) return false;
|
||||
array<int> ids = {}; GetGame().GetPlayerManager().GetPlayers(ids);
|
||||
foreach (int id : ids)
|
||||
{
|
||||
IEntity player = GetGame().GetPlayerManager().GetPlayerControlledEntity(id);
|
||||
if (player && PartOf(CompartmentAccessComponent.GetVehicleIn(player), vehicle)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static bool CollectSeats(IEntity root, array<BaseCompartmentSlot> seats)
|
||||
{
|
||||
array<IEntity> pending = {root};
|
||||
int cursor;
|
||||
while (cursor < pending.Count() && cursor < 128)
|
||||
{
|
||||
IEntity entity = pending[cursor++];
|
||||
if (!entity) return false;
|
||||
SCR_BaseCompartmentManagerComponent manager = SCR_BaseCompartmentManagerComponent.Cast(entity.FindComponent(SCR_BaseCompartmentManagerComponent));
|
||||
if (manager)
|
||||
{
|
||||
array<BaseCompartmentSlot> local = {}; manager.GetCompartments(local);
|
||||
foreach (BaseCompartmentSlot seat : local) if (seat && !seats.Contains(seat)) seats.Insert(seat);
|
||||
}
|
||||
IEntity child = entity.GetChildren();
|
||||
while (child)
|
||||
{
|
||||
if (pending.Count() >= 128 || pending.Contains(child)) return false;
|
||||
pending.Insert(child); child = child.GetSibling();
|
||||
}
|
||||
}
|
||||
return cursor == pending.Count();
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_ConvoyVehicle
|
||||
{
|
||||
int Index;
|
||||
Vehicle Entity;
|
||||
SCR_AIGroup Group;
|
||||
IEntity Driver;
|
||||
ref array<IEntity> Members = {};
|
||||
ref array<BaseCompartmentSlot> Seats = {};
|
||||
AIWaypoint Waypoint;
|
||||
int RouteIndex;
|
||||
bool Released;
|
||||
bool Registered;
|
||||
// Latched only for an exact tracked hunter at authoritative player entry.
|
||||
bool PlayerCaptureObserved;
|
||||
ref ERR_ConvoyCombatState Combat = new ERR_ConvoyCombatState();
|
||||
int SupplyToken;
|
||||
bool SupplyHeld;
|
||||
int InterceptToken;
|
||||
bool InterceptActive;
|
||||
AIWaypoint InterceptWaypoint;
|
||||
ref ERR_ConvoyInterceptOrder InterceptOrder;
|
||||
ERR_ConvoyReceipt InterceptReceipt;
|
||||
ERR_ForceRecord InterceptRecord;
|
||||
ERR_ForceRoute InterceptRoute;
|
||||
protected AIWaypoint m_InterceptWaypoint;
|
||||
protected Vehicle m_InterceptEntity;
|
||||
protected SCR_AIGroup m_InterceptGroup;
|
||||
protected IEntity m_InterceptDriver;
|
||||
protected ref array<IEntity> m_InterceptMembers = {};
|
||||
protected ref array<BaseCompartmentSlot> m_InterceptSeats = {};
|
||||
protected int m_InterceptToken;
|
||||
protected int m_LastInterceptToken;
|
||||
protected vector m_InterceptPosition;
|
||||
protected float m_InterceptExpiresAt;
|
||||
protected int m_InterceptRouteIndex;
|
||||
|
||||
bool Arrived;
|
||||
float LegDeadline;
|
||||
float NextReadinessNote;
|
||||
|
||||
// Pending downlink owns a claim only; normal authored travel continues.
|
||||
bool InterceptCrew(ERR_GameMode mode)
|
||||
{
|
||||
if (!mode || !CrewValid(mode) || !Entity || !Group || ERR_ConvoyRules.Captured(Entity, Group)) return false;
|
||||
if (Members.IsEmpty() || Members.Count() > 8 || Seats.Count() != Members.Count()) return false;
|
||||
if (InterceptOrder && (Members.Count() != m_InterceptMembers.Count() || Seats.Count() != m_InterceptSeats.Count())) return false;
|
||||
array<AIAgent> agents = {}; Group.GetAgents(agents);
|
||||
if (agents.Count() != Members.Count()) return false;
|
||||
for (int i = 0; i < Members.Count(); i++)
|
||||
{
|
||||
if (!Members[i] || !ERR_Actors.CanFight(Members[i]) || !Seats[i] || Seats[i].GetOccupant() != Members[i]) return false;
|
||||
int attributed;
|
||||
foreach (AIAgent agent : agents)
|
||||
{
|
||||
if (!agent || !Members.Contains(agent.GetControlledEntity())) return false;
|
||||
if (agent.GetControlledEntity() == Members[i]) attributed++;
|
||||
}
|
||||
if (attributed != 1) return false;
|
||||
if (InterceptOrder && (Members[i] != m_InterceptMembers[i] || Seats[i] != m_InterceptSeats[i])) return false;
|
||||
}
|
||||
if (InterceptOrder && (Entity != m_InterceptEntity || Group != m_InterceptGroup || Driver != m_InterceptDriver)) return false;
|
||||
return true;
|
||||
}
|
||||
bool InterceptMatches(ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
return order && InterceptOrder == order && InterceptToken == m_InterceptToken && m_InterceptToken != 0;
|
||||
}
|
||||
bool ClaimIntercept(ERR_GameMode mode, ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
if (!order || order.Token <= m_LastInterceptToken || InterceptToken != 0 || InterceptOrder || InterceptWaypoint || !InterceptCrew(mode)) return false;
|
||||
m_InterceptEntity = Entity; m_InterceptGroup = Group; m_InterceptDriver = Driver;
|
||||
m_InterceptMembers.Clear(); m_InterceptSeats.Clear();
|
||||
foreach (IEntity member : Members) m_InterceptMembers.Insert(member);
|
||||
foreach (BaseCompartmentSlot seat : Seats) m_InterceptSeats.Insert(seat);
|
||||
m_InterceptToken = order.Token; m_LastInterceptToken = order.Token; InterceptToken = order.Token; InterceptOrder = order;
|
||||
m_InterceptPosition = order.Position; m_InterceptExpiresAt = order.ExpiresAt;
|
||||
return true;
|
||||
}
|
||||
void ClearIntercept(ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
if (!InterceptMatches(order)) return;
|
||||
// Delete only the exact waypoint this owner created, never an adopted handle.
|
||||
if (m_InterceptWaypoint && m_InterceptGroup) m_InterceptGroup.RemoveWaypoint(m_InterceptWaypoint);
|
||||
if (m_InterceptWaypoint) SCR_EntityHelper.DeleteEntityAndChildren(m_InterceptWaypoint);
|
||||
if (InterceptWaypoint == m_InterceptWaypoint) InterceptWaypoint = null;
|
||||
m_InterceptWaypoint = null; InterceptToken = 0; m_InterceptToken = 0;
|
||||
InterceptActive = false; InterceptOrder = null; InterceptReceipt = null; InterceptRecord = null; InterceptRoute = null;
|
||||
m_InterceptMembers.Clear(); m_InterceptSeats.Clear();
|
||||
}
|
||||
bool DeliverIntercept(ERR_GameMode mode, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!InterceptMatches(order) || order.Token != m_InterceptToken || InterceptActive || !InterceptCrew(mode)) return false;
|
||||
if (order.Position != m_InterceptPosition || order.ExpiresAt != m_InterceptExpiresAt || now >= m_InterceptExpiresAt) return false;
|
||||
AIWaypoint detour = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), m_InterceptPosition));
|
||||
if (!detour) return false;
|
||||
detour.SetCompletionRadius(15);
|
||||
ClearOrder(); m_InterceptRouteIndex = RouteIndex;
|
||||
m_InterceptWaypoint = detour; InterceptWaypoint = detour; InterceptActive = true;
|
||||
Group.AddWaypoint(detour);
|
||||
return true;
|
||||
}
|
||||
// True suppresses ordinary travel only after actual radio delivery. No route
|
||||
// index is consumed by arrival at the detour; removal leaves the seated group held.
|
||||
bool TickIntercept(ERR_GameMode mode, float now)
|
||||
{
|
||||
if (!InterceptOrder) return false;
|
||||
if (!InterceptMatches(InterceptOrder)) return InterceptActive;
|
||||
if (Released || Arrived || now >= m_InterceptExpiresAt || !InterceptCrew(mode) || Combat.Held || SupplyHeld || SupplyToken != 0)
|
||||
{ bool delivered = InterceptActive; ClearIntercept(InterceptOrder); if (delivered) LegDeadline = now + 600000; return false; }
|
||||
if (!InterceptActive) return false;
|
||||
if (RouteIndex != m_InterceptRouteIndex || InterceptWaypoint != m_InterceptWaypoint)
|
||||
{ ClearIntercept(InterceptOrder); return false; }
|
||||
if (m_InterceptWaypoint && vector.DistanceXZ(Entity.GetOrigin(), m_InterceptPosition) <= 15)
|
||||
{
|
||||
Group.RemoveWaypoint(m_InterceptWaypoint); SCR_EntityHelper.DeleteEntityAndChildren(m_InterceptWaypoint);
|
||||
m_InterceptWaypoint = null; InterceptWaypoint = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void ClearOrder()
|
||||
{
|
||||
if (Waypoint && Group) Group.RemoveWaypoint(Waypoint);
|
||||
if (Waypoint) SCR_EntityHelper.DeleteEntityAndChildren(Waypoint);
|
||||
Waypoint = null;
|
||||
}
|
||||
void Release(ERR_GameMode mode)
|
||||
{
|
||||
if (Released) return;
|
||||
ClearIntercept(InterceptOrder);
|
||||
Released = true;
|
||||
Combat.Held = false; SupplyToken = 0; SupplyHeld = false;
|
||||
ClearOrder();
|
||||
if (mode && mode.Forces())
|
||||
{
|
||||
ERR_ForceRecord record = mode.Forces().FindGroup(Group);
|
||||
if (record) record.Pinned = false;
|
||||
}
|
||||
if (Group)
|
||||
{
|
||||
ERR_HQ hq;
|
||||
if (mode) hq = ERR_HQ.Cast(mode.Command());
|
||||
bool retained = false;
|
||||
if (Registered && !PlayerCaptureObserved && hq && !ERR_ConvoyRules.Captured(Entity, Group)) retained = hq.ReleaseConvoySurvivors(Group);
|
||||
if (!retained && mode && mode.Command()) mode.Command().UnregisterGroup(Group);
|
||||
Group.SetPermanentLOD(-1);
|
||||
array<AIAgent> agents = {}; Group.GetAgents(agents);
|
||||
foreach (AIAgent agent : agents) if (agent) agent.SetPermanentLOD(-1);
|
||||
}
|
||||
// Vehicle, cargo, casualties and surviving crew remain physical and lootable.
|
||||
}
|
||||
bool CrewValid(ERR_GameMode mode)
|
||||
{
|
||||
if (!Group || !Entity || !ERR_Actors.Alive(Entity) || Group.GetFaction() != mode.EnemyFaction() || !ERR_Actors.CanFight(Driver)) return false;
|
||||
if (ERR_Actors.Player(Driver) || !ERR_ConvoyRules.PartOf(CompartmentAccessComponent.GetVehicleIn(Driver), Entity)) return false;
|
||||
foreach (IEntity member : Members)
|
||||
if (!ERR_Actors.Alive(member) || ERR_Actors.Player(member) || ERR_Actors.Group(member) != Group) return false;
|
||||
return true;
|
||||
}
|
||||
bool Order(ERR_ForceRoute route, float now)
|
||||
{
|
||||
if (Released || Arrived || InterceptActive || Combat.Held || SupplyHeld || !Group || !Entity || !route || RouteIndex >= route.Count()) return false;
|
||||
if (Waypoint) return true;
|
||||
Waypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), route.At(RouteIndex)));
|
||||
if (!Waypoint) return false;
|
||||
Waypoint.SetCompletionRadius(35);
|
||||
Group.AddWaypoint(Waypoint);
|
||||
LegDeadline = now + 600000;
|
||||
return true;
|
||||
}
|
||||
void Tick(ERR_GameMode mode, ERR_ForceRoute route, float now)
|
||||
{
|
||||
if (TickIntercept(mode, now)) return;
|
||||
if (Released || Arrived) return;
|
||||
if (!CrewValid(mode)) { Release(mode); return; }
|
||||
if (Combat.Held || SupplyHeld) return;
|
||||
// Native completion measures the seated group, not the vehicle origin.
|
||||
// Keep the same predicate after native removal of the completed waypoint.
|
||||
if (Waypoint && Waypoint.IsWithinCompletionRadius(Group))
|
||||
{
|
||||
ClearOrder(); RouteIndex++;
|
||||
if (RouteIndex >= route.Count()) { Arrived = true; return; }
|
||||
}
|
||||
if (Waypoint && now >= LegDeadline) { Release(mode); return; }
|
||||
if (!Order(route, now)) Release(mode);
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_ConvoyCargoCallback : ScriptedInventoryOperationCallback
|
||||
{
|
||||
ResourceName Prefab;
|
||||
bool Finished;
|
||||
bool Succeeded;
|
||||
protected void Finish(bool success) { if (!Finished) { Finished = true; Succeeded = success; } }
|
||||
protected override void OnComplete() { Finish(true); }
|
||||
protected override void OnFailed() { Finish(false); }
|
||||
protected override void OnAllComplete() { Finish(true); }
|
||||
protected override void OnAllFailed() { Finish(false); }
|
||||
}
|
||||
|
||||
class ERR_ConvoyTask
|
||||
{
|
||||
ref ERR_ConvoyReceipt Receipt;
|
||||
ref ERR_ForceRoute Route;
|
||||
ref array<ref ERR_ConvoyVehicle> Vehicles = {};
|
||||
ref array<ref ERR_ConvoyCargoCallback> Cargo = {};
|
||||
bool Stopped;
|
||||
bool Ready;
|
||||
bool CargoIssued;
|
||||
float Deadline;
|
||||
float NextCheck;
|
||||
string Reason;
|
||||
void Stop(ERR_GameMode mode, string reason)
|
||||
{
|
||||
if (Stopped) return;
|
||||
Stopped = true; Reason = reason;
|
||||
foreach (ERR_ConvoyVehicle vehicle : Vehicles) vehicle.Release(mode);
|
||||
ERR_HQ.Event("CONVOY STOP", reason + "; surviving entities and cargo preserved");
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_ConvoyService
|
||||
{
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected ERR_ConvoyPool m_Pool;
|
||||
protected ref array<ref ERR_ConvoyTask> m_Tasks = {};
|
||||
protected bool m_Stopped;
|
||||
void ERR_ConvoyService(ERR_GameMode mode, ERR_ConvoyPool pool) { m_Mode = mode; m_Pool = pool; }
|
||||
protected bool Current()
|
||||
{
|
||||
return !m_Stopped && Replication.IsServer() && m_Mode && m_Mode == ERR_GameMode.Get() && m_Mode.Active() && m_Pool;
|
||||
}
|
||||
protected ERR_ConvoyTask NewTask() { return new ERR_ConvoyTask(); }
|
||||
protected ERR_ConvoyVehicle NewVehicle() { return new ERR_ConvoyVehicle(); }
|
||||
ERR_ConvoyTask At(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_Tasks.Count()) return null;
|
||||
return m_Tasks[index];
|
||||
}
|
||||
ERR_ConvoyTask Find(string source)
|
||||
{
|
||||
foreach (ERR_ConvoyTask task : m_Tasks) if (task.Receipt && task.Receipt.SourceId == source) return task;
|
||||
return null;
|
||||
}
|
||||
protected bool FillCrew(ERR_ConvoyVehicle unit)
|
||||
{
|
||||
array<BaseCompartmentSlot> all = {};
|
||||
if (!ERR_ConvoyRules.CollectSeats(unit.Entity, all)) return false;
|
||||
array<BaseCompartmentSlot> drivers = {}, turrets = {}, cargo = {};
|
||||
foreach (BaseCompartmentSlot slot : all)
|
||||
{
|
||||
if (!slot.IsCompartmentAccessible() || slot.IsOccupied()) continue;
|
||||
if (slot.GetType() == ECompartmentType.PILOT) drivers.Insert(slot);
|
||||
else if (slot.GetType() == ECompartmentType.TURRET) turrets.Insert(slot);
|
||||
else if (slot.GetType() == ECompartmentType.CARGO) cargo.Insert(slot);
|
||||
}
|
||||
int requiredTurrets = 1;
|
||||
if (unit.Index == 1 || unit.Index == 2) requiredTurrets = 0;
|
||||
if (unit.Index == 4) requiredTurrets = 2;
|
||||
if (drivers.Count() != 1 || turrets.Count() < requiredTurrets || (unit.Index == 2 && cargo.Count() < 8)) return false;
|
||||
unit.Seats.Insert(drivers[0]);
|
||||
for (int i = 0; i < requiredTurrets; i++) unit.Seats.Insert(turrets[i]);
|
||||
if (unit.Index == 2) for (int i = 0; i < 8; i++) unit.Seats.Insert(cargo[i]);
|
||||
// Exact planned population is known before any character is created.
|
||||
if (unit.Seats.Count() != ERR_ConvoyPool.CrewCount(unit.Index)) return false;
|
||||
AIGroup nativeGroup = unit.Group;
|
||||
foreach (BaseCompartmentSlot seat : unit.Seats)
|
||||
{
|
||||
IEntity actor = seat.SpawnCharacterInCompartment(ERR_ConvoyResources.Soldier(), nativeGroup, ERR_ConvoyResources.GroupPrefab());
|
||||
if (actor) { unit.Members.Insert(actor); m_Mode.Own(actor); }
|
||||
if (!actor || nativeGroup != unit.Group) return false;
|
||||
}
|
||||
unit.Driver = unit.Members[0];
|
||||
return true;
|
||||
}
|
||||
ERR_ConvoyTask Dispatch(ERR_ConvoyReceipt receipt, array<vector> positions, array<vector> angles, ERR_ForceRoute route, float now)
|
||||
{
|
||||
if (!Current() || !ERR_ForceRules.Finite(now) || now < 0 || !ERR_ConvoyRules.Plan(positions, angles, route) || !ERR_ConvoyResources.Available() || !ERR_ConvoyRules.GroundClear(positions, angles)) return null;
|
||||
// The server role selection owns the memberless group affiliation.
|
||||
if (!m_Mode.EnemyFaction() || !m_Pool.Consume(receipt)) return null;
|
||||
ERR_ConvoyTask task = NewTask(); task.Receipt = receipt; task.Route = route; task.Deadline = now + 30000;
|
||||
m_Tasks.Insert(task);
|
||||
for (int index = 0; index < 6; index++)
|
||||
{
|
||||
ERR_ConvoyVehicle unit = NewVehicle(); unit.Index = index; task.Vehicles.Insert(unit);
|
||||
unit.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), positions[index]));
|
||||
if (unit.Group) m_Mode.Own(unit.Group);
|
||||
unit.Entity = Vehicle.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.VehiclePrefab(index), positions[index], angles[index]));
|
||||
if (unit.Entity) m_Mode.Own(unit.Entity);
|
||||
if (!unit.Group || !unit.Entity || unit.Group.GetFaction() != m_Mode.EnemyFaction() || !ERR_ConvoyRules.EmptyCrewTemplate(unit.Group) || !FillCrew(unit))
|
||||
{ task.Stop(m_Mode, "finite convoy startup failed; receipt consumed without retry"); return task; }
|
||||
FactionAffiliationComponent affiliation = FactionAffiliationComponent.Cast(unit.Entity.FindComponent(FactionAffiliationComponent));
|
||||
if (affiliation) affiliation.SetAffiliatedFaction(m_Mode.EnemyFaction());
|
||||
AICarMovementComponent movement = AICarMovementComponent.Cast(unit.Entity.FindComponent(AICarMovementComponent));
|
||||
if (movement) movement.SetCruiseSpeed(35);
|
||||
}
|
||||
ERR_HQ.Event("CONVOY CREATED", "six physical vehicles; finite crew created; native readiness and ledger admission pending");
|
||||
return task;
|
||||
}
|
||||
protected string ReadinessReason(ERR_ConvoyVehicle unit)
|
||||
{
|
||||
if (unit.Registered) return "already admitted";
|
||||
if (!unit.Group) return "missing group";
|
||||
if (!unit.Entity) return "missing vehicle";
|
||||
if (!ERR_Actors.Alive(unit.Entity)) return "vehicle destroyed or lacks damage state";
|
||||
if (unit.Group.GetFaction() != m_Mode.EnemyFaction()) return "group faction mismatch";
|
||||
if (!ERR_Actors.CanFight(unit.Driver)) return "driver absent/dead/unconscious or controller unavailable";
|
||||
if (ERR_Actors.Player(unit.Driver)) return "driver controlled by player";
|
||||
if (!ERR_ConvoyRules.PartOf(CompartmentAccessComponent.GetVehicleIn(unit.Driver), unit.Entity)) return "driver outside intended vehicle";
|
||||
foreach (IEntity member : unit.Members)
|
||||
{
|
||||
if (!ERR_Actors.Alive(member)) return "member absent/dead or damage state unavailable";
|
||||
if (ERR_Actors.Player(member)) return "member controlled by player";
|
||||
if (ERR_Actors.Group(member) != unit.Group) return "member native agent/parent group mismatch";
|
||||
}
|
||||
array<AIAgent> agents = {}; unit.Group.GetAgents(agents);
|
||||
if (agents.Count() != unit.Members.Count()) return "native agent count differs from original crew";
|
||||
foreach (AIAgent agent : agents)
|
||||
{
|
||||
if (!agent) return "null native agent";
|
||||
if (!unit.Members.Contains(agent.GetControlledEntity())) return "native agent controls foreign entity";
|
||||
if (!ERR_Actors.CanFight(agent.GetControlledEntity())) return "native agent actor cannot fight";
|
||||
}
|
||||
SCR_AIGroupUtilityComponent utility = unit.Group.GetGroupUtilityComponent();
|
||||
if (!utility) return "group utility unavailable";
|
||||
if (!utility.m_VehicleMgr) return "group vehicle manager unavailable";
|
||||
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(unit.Entity.FindComponent(SCR_AIVehicleUsageComponent));
|
||||
if (!usage) return "vehicle usage component unavailable";
|
||||
for (int i = 0; i < unit.Seats.Count(); i++)
|
||||
if (i >= unit.Members.Count() || !unit.Seats[i] || unit.Seats[i].GetOccupant() != unit.Members[i]) return "intended seat occupant mismatch";
|
||||
return "readiness guards satisfied; awaiting admission";
|
||||
}
|
||||
protected void InspectReadiness(ERR_ConvoyVehicle unit, float now)
|
||||
{
|
||||
if (!unit || now < unit.NextReadinessNote) return;
|
||||
unit.NextReadinessNote = now + 5000;
|
||||
int agents, activeAgents;
|
||||
bool activeGroup;
|
||||
if (unit.Group)
|
||||
{
|
||||
array<AIAgent> current = {}; unit.Group.GetAgents(current);
|
||||
agents = current.Count(); activeGroup = unit.Group.IsAIActivated();
|
||||
foreach (AIAgent agent : current) if (agent && agent.IsAIActivated()) activeAgents++;
|
||||
}
|
||||
ERR_HQ.Event("CONVOY READINESS", string.Format("vehicle=%1 reason=%2 members=%3 agents=%4 activeAgents=%5 activeGroup=%6 seats=%7 registered=%8", unit.Index, ReadinessReason(unit), unit.Members.Count(), agents, activeAgents, activeGroup, unit.Seats.Count(), unit.Registered));
|
||||
}
|
||||
protected bool Admit(ERR_ConvoyTask task, float now)
|
||||
{
|
||||
foreach (ERR_ConvoyVehicle observed : task.Vehicles) InspectReadiness(observed, now);
|
||||
foreach (ERR_ConvoyVehicle unit : task.Vehicles)
|
||||
{
|
||||
if (unit.Registered) continue;
|
||||
if (!unit.CrewValid(m_Mode)) return false;
|
||||
array<AIAgent> agents = {}; unit.Group.GetAgents(agents);
|
||||
if (agents.Count() != unit.Members.Count()) return false;
|
||||
foreach (AIAgent agent : agents)
|
||||
if (!agent || !unit.Members.Contains(agent.GetControlledEntity()) || !ERR_Actors.CanFight(agent.GetControlledEntity())) return false;
|
||||
SCR_AIGroupUtilityComponent utility = unit.Group.GetGroupUtilityComponent();
|
||||
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(unit.Entity.FindComponent(SCR_AIVehicleUsageComponent));
|
||||
if (!utility || !utility.m_VehicleMgr || !usage || !utility.m_VehicleMgr.TryAddVehicle(usage)) return false;
|
||||
ERR_ForceRecord record = m_Mode.Forces().Register(task.Receipt.SourceId + "-vehicle-" + unit.Index.ToString(), ERR_ForceKind.CONVOY, unit.Entity.GetOrigin(), unit.Members.Count(), null, unit.Group, now);
|
||||
if (!record || !m_Pool.BindRecord(task.Receipt, unit.Index, record)) { task.Stop(m_Mode, "convoy ledger admission rejected"); return false; }
|
||||
record.Pinned = true;
|
||||
unit.Registered = true;
|
||||
m_Mode.RegisterGroup(unit.Group);
|
||||
ERR_HQ hq = ERR_HQ.Cast(m_Mode.Command());
|
||||
if (hq)
|
||||
{
|
||||
hq.ReserveGroup(unit.Group);
|
||||
if (!hq.BindVehicle(unit.Group, unit.Entity))
|
||||
ERR_HQ.Event("MOUNTED AMMO", "original convoy vehicle binding unavailable; no replacement lookup");
|
||||
}
|
||||
// Native helper idempotently registers deletion/damage callbacks and mounted
|
||||
// combat activity; raw vehicle-manager admission alone omits those hooks.
|
||||
utility.AddUsableVehicle(usage);
|
||||
unit.Group.SetPermanentLOD(0); unit.Group.ActivateAI();
|
||||
foreach (AIAgent active : agents) { active.SetPermanentLOD(0); active.ActivateAI(); }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected void IssueCargo(ERR_ConvoyTask task)
|
||||
{
|
||||
if (task.CargoIssued || task.Stopped || task.Vehicles.Count() != 6) return;
|
||||
task.CargoIssued = true;
|
||||
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(task.Vehicles[1].Entity.FindComponent(InventoryStorageManagerComponent));
|
||||
if (!inventory) { ERR_HQ.Event("CONVOY CARGO", "ammo truck inventory unavailable; no retry or free replacement"); return; }
|
||||
for (int i = 0; i < 56; i++)
|
||||
{
|
||||
ResourceName prefab = ERR_ConvoyResources.Magazine();
|
||||
if (i < 8) prefab = ERR_ConvoyResources.Rifle();
|
||||
ERR_ConvoyCargoCallback operation = new ERR_ConvoyCargoCallback(); operation.Prefab = prefab; task.Cargo.Insert(operation);
|
||||
if (!inventory.TrySpawnPrefabToStorage(prefab, null, -1, EStoragePurpose.PURPOSE_DEPOSIT, operation)) operation.Finished = true;
|
||||
}
|
||||
ERR_HQ.Event("CONVOY CARGO", "finite target submitted once: eight AK74 and 48 magazines; native capacity/completion may reduce actual stock");
|
||||
}
|
||||
void Tick(float now)
|
||||
{
|
||||
if (!Current() || !ERR_ForceRules.Finite(now)) return;
|
||||
foreach (ERR_ConvoyTask task : m_Tasks)
|
||||
{
|
||||
if (task.Stopped || now < task.NextCheck) continue;
|
||||
task.NextCheck = now + 1000;
|
||||
bool captured;
|
||||
foreach (ERR_ConvoyVehicle unit : task.Vehicles) if (ERR_ConvoyRules.Captured(unit.Entity, unit.Group)) captured = true;
|
||||
if (captured) { task.Stop(m_Mode, "player captured convoy vehicle or joined crew"); continue; }
|
||||
if (!task.Ready)
|
||||
{
|
||||
if (now >= task.Deadline) { task.Stop(m_Mode, "native crew readiness expired; no replacement"); continue; }
|
||||
if (!Admit(task, now)) continue;
|
||||
task.Ready = true; IssueCargo(task);
|
||||
}
|
||||
bool pending;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
vehicle.Tick(m_Mode, task.Route, now);
|
||||
if (!vehicle.Released && !vehicle.Arrived) pending = true;
|
||||
}
|
||||
if (!pending) task.Stop(m_Mode, "finite authored road route ended or all vehicle orders released");
|
||||
}
|
||||
}
|
||||
protected bool InterceptEligible(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
if (!Current() || !task || !order || !m_Tasks.Contains(task) || task.Stopped || !task.Ready || !task.Receipt || !task.Receipt.Consumed) return false;
|
||||
if (!m_Mode.Forces()) return false;
|
||||
ERR_HQ hq = ERR_HQ.Cast(m_Mode.Command());
|
||||
if (!hq || hq.LogisticsToken(order.Group) != order.Token) return false;
|
||||
if (order.Token < 400001 || order.Token > 400256 || order.SourceId != task.Receipt.SourceId) return false;
|
||||
if (order.VehicleIndex != 0 && order.VehicleIndex != 3 && order.VehicleIndex != 5) return false;
|
||||
if (order.VehicleIndex >= task.Vehicles.Count() || order.VehicleIndex >= task.Receipt.Records.Count()) return false;
|
||||
ERR_ConvoyVehicle unit = task.Vehicles[order.VehicleIndex];
|
||||
if (!unit || !unit.Registered || unit.Released || unit.Arrived || unit.Index != order.VehicleIndex || unit.Group != order.Group) return false;
|
||||
if (!unit.InterceptCrew(m_Mode) || !task.Route || unit.RouteIndex < 0 || unit.RouteIndex >= task.Route.Count()) return false;
|
||||
ERR_ForceRecord record = m_Mode.Forces().FindGroup(unit.Group);
|
||||
if (!record || !record.Physical || record.Eliminated || record.Kind != ERR_ForceKind.CONVOY || record.Id != task.Receipt.Records[unit.Index]) return false;
|
||||
if (record.Group != unit.Group || record.SourceId != task.Receipt.SourceId + "-vehicle-" + unit.Index.ToString()) return false;
|
||||
if (unit.InterceptOrder && (unit.InterceptReceipt != task.Receipt || unit.InterceptRecord != record || unit.InterceptRoute != task.Route)) return false;
|
||||
foreach (ERR_ConvoyVehicle other : task.Vehicles)
|
||||
if (!other || other.SupplyToken != 0 || other.SupplyHeld || other.Combat.Held) return false;
|
||||
return true;
|
||||
}
|
||||
bool InterceptCurrent(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
return InterceptEligible(task, order) && task.Vehicles[order.VehicleIndex].InterceptToken == order.Token
|
||||
&& task.Vehicles[order.VehicleIndex].InterceptMatches(order);
|
||||
}
|
||||
bool ReserveIntercept(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!InterceptEligible(task, order) || !ERR_ForceRules.Finite(now) || now < 0) return false;
|
||||
if (!ERR_ForceRules.Finite(order.ObservedAt) || !ERR_ForceRules.Finite(order.IssuedAt) || !ERR_ForceRules.Finite(order.ExpiresAt)) return false;
|
||||
if (order.ObservedAt < 0 || order.IssuedAt < order.ObservedAt || now < order.IssuedAt || now >= order.ExpiresAt) return false;
|
||||
if (order.ExpiresAt > order.ObservedAt + 120000 || !ERR_ForceRules.FiniteVector(order.Position)) return false;
|
||||
if (order.BeliefId <= 0 || order.BeliefRevision <= 0 || order.ReportId <= 0) return false;
|
||||
foreach (ERR_ConvoyTask existing : m_Tasks)
|
||||
foreach (ERR_ConvoyVehicle claimed : existing.Vehicles) if (claimed && claimed.InterceptToken != 0) return false;
|
||||
ERR_ConvoyVehicle unit = task.Vehicles[order.VehicleIndex];
|
||||
if (!unit.ClaimIntercept(m_Mode, order)) return false;
|
||||
unit.InterceptReceipt = task.Receipt; unit.InterceptRecord = m_Mode.Forces().FindGroup(unit.Group); unit.InterceptRoute = task.Route;
|
||||
return true;
|
||||
}
|
||||
bool ApplyIntercept(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!InterceptCurrent(task, order) || !ERR_ForceRules.Finite(now) || now < order.IssuedAt || now >= order.ExpiresAt) return false;
|
||||
return task.Vehicles[order.VehicleIndex].DeliverIntercept(m_Mode, order, now);
|
||||
}
|
||||
void ReleaseIntercept(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!task || !order || !m_Tasks.Contains(task) || !ERR_ForceRules.Finite(now)) return;
|
||||
foreach (ERR_ConvoyVehicle unit : task.Vehicles)
|
||||
if (unit && unit.InterceptMatches(order))
|
||||
{ bool delivered = unit.InterceptActive; unit.ClearIntercept(order); if (delivered) unit.LegDeadline = now + 600000; }
|
||||
}
|
||||
bool ApplyCombatOrder(ERR_ConvoyTask task, ERR_ConvoyCombatOrder order, float now)
|
||||
{
|
||||
if (!Current() || !task || !order || !m_Tasks.Contains(task) || task.Stopped || !task.Ready || !task.Receipt || !task.Receipt.Consumed) return false;
|
||||
foreach (ERR_ConvoyVehicle intercept : task.Vehicles) if (intercept && intercept.InterceptToken != 0) return false;
|
||||
if (order.VehicleIndex < 0 || order.VehicleIndex >= task.Vehicles.Count() || order.SourceId != task.Receipt.SourceId) return false;
|
||||
ERR_ConvoyVehicle unit = task.Vehicles[order.VehicleIndex];
|
||||
if (!unit || !unit.Registered || unit.Released || unit.Arrived || unit.Index != order.VehicleIndex || unit.Group != order.Group || !unit.CrewValid(m_Mode)) return false;
|
||||
if (ERR_ConvoyRules.Captured(unit.Entity, unit.Group) || unit.Index >= task.Receipt.Records.Count()) return false;
|
||||
if (!ERR_ConvoyCombatRules.Accept(unit.Combat, order, now) || !task.Route || unit.RouteIndex >= task.Route.Count()) return false;
|
||||
ERR_ForceRecord record = m_Mode.Forces().FindGroup(unit.Group);
|
||||
if (!record || !record.Physical || record.Eliminated || record.Kind != ERR_ForceKind.CONVOY || record.Id != task.Receipt.Records[unit.Index]) return false;
|
||||
array<AIAgent> agents = {}; unit.Group.GetAgents(agents);
|
||||
if (agents.Count() != unit.Members.Count()) return false;
|
||||
foreach (AIAgent agent : agents)
|
||||
if (!agent || !unit.Members.Contains(agent.GetControlledEntity())) return false;
|
||||
// No caller can replay even a command whose native waypoint recreation fails.
|
||||
unit.Combat.LastCommandId = order.CommandId;
|
||||
if (order.Hold)
|
||||
{
|
||||
if (!unit.Combat.Held) unit.ClearOrder();
|
||||
unit.Combat.Held = true; unit.Combat.ReportId = order.ReportId; unit.Combat.ObservedAt = order.ObservedAt;
|
||||
}
|
||||
else
|
||||
{
|
||||
unit.Combat.Held = false;
|
||||
if (!unit.SupplyHeld && !unit.Order(task.Route, now)) { unit.Release(m_Mode); return false; }
|
||||
}
|
||||
ERR_HQ.Event("CONVOY COVER", string.Format("command M%1 report M%2 vehicle=%3 held=%4 routeIndex=%5", order.CommandId, order.ReportId, unit.Index, unit.Combat.Held, unit.RouteIndex));
|
||||
return true;
|
||||
}
|
||||
int Count() { return m_Tasks.Count(); }
|
||||
string Status()
|
||||
{
|
||||
int ready, stopped, vehicles, completed, failed, pending;
|
||||
foreach (ERR_ConvoyTask task : m_Tasks)
|
||||
{
|
||||
if (task.Ready) ready++;
|
||||
if (task.Stopped) stopped++;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles) if (vehicle && vehicle.Entity) vehicles++;
|
||||
foreach (ERR_ConvoyCargoCallback operation : task.Cargo)
|
||||
{
|
||||
if (!operation.Finished) pending++;
|
||||
else if (operation.Succeeded) completed++;
|
||||
else failed++;
|
||||
}
|
||||
}
|
||||
string reason;
|
||||
if (m_Tasks.Count() == 1 && m_Tasks[0].Stopped) reason = m_Tasks[0].Reason;
|
||||
if (reason.Length() > 160) reason = reason.Substring(0, 160);
|
||||
return string.Format("Convoys: %1 tasks, %2 reached readiness, %3 stopped, %4 retained physical vehicles; cargo native insertion successes=%5 failed=%6 pending=%7; current inventory may differ after use/looting", m_Tasks.Count(), ready, stopped, vehicles, completed, failed, pending) + "; reason=" + reason;
|
||||
}
|
||||
void Stop()
|
||||
{
|
||||
if (m_Stopped) return;
|
||||
m_Stopped = true;
|
||||
foreach (ERR_ConvoyTask task : m_Tasks) task.Stop(m_Mode, "HQ ended convoy task service");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
from pathlib import Path
|
||||
import subprocess,hashlib,json
|
||||
root=Path('/home/jakub/devel/escapistsrereintroduced'); out=Path('/tmp/erri-pair-service-0054'); rel='addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c'
|
||||
s=(root/rel).read_text(); p=out/rel;p.parent.mkdir(parents=True,exist_ok=True)
|
||||
b=out/'baseline'/rel;b.parent.mkdir(parents=True,exist_ok=True);b.write_text(s)
|
||||
idx=subprocess.run(['git','show',':'+rel],cwd=root,capture_output=True,check=True).stdout
|
||||
(out/'baseline.json').write_text(json.dumps({'path':rel,'working_sha256':hashlib.sha256(s.encode()).hexdigest(),'index_sha256':hashlib.sha256(idx).hexdigest()},indent=2)+'\n')
|
||||
needle=' bool DeliverIntercept(ERR_GameMode mode, ERR_ConvoyInterceptOrder order, float now)'
|
||||
insert=''' // Pair preparation owns no route state. Recheck after both native allocations.
|
||||
bool CanInstallIntercept(ERR_GameMode mode, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!InterceptMatches(order) || order.Token != m_InterceptToken || InterceptActive || !InterceptCrew(mode)) return false;
|
||||
return order.Position == m_InterceptPosition && order.ExpiresAt == m_InterceptExpiresAt && now < m_InterceptExpiresAt;
|
||||
}
|
||||
// Caller transfers ownership of the prepared handle before AddWaypoint, which
|
||||
// is a native boundary rather than a transactional pair operation.
|
||||
void InstallIntercept(AIWaypoint detour)
|
||||
{
|
||||
ClearOrder(); m_InterceptRouteIndex = RouteIndex;
|
||||
m_InterceptWaypoint = detour; InterceptWaypoint = detour; InterceptActive = true;
|
||||
Group.AddWaypoint(detour);
|
||||
}
|
||||
'''
|
||||
assert needle in s;s=s.replace(needle,insert+needle,1)
|
||||
needle=' bool ApplyIntercept(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order, float now)'
|
||||
insert=''' protected bool InterceptPairEligible(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder first, ERR_ConvoyInterceptOrder second, float now)
|
||||
{
|
||||
if (!first || !second || first == second || first.Group == second.Group || first.VehicleIndex == second.VehicleIndex || first.Token == second.Token) return false;
|
||||
if (!InterceptEligible(task, first) || !InterceptEligible(task, second) || !ERR_ForceRules.Finite(now) || now < 0) return false;
|
||||
if (task.Vehicles[first.VehicleIndex].Entity == task.Vehicles[second.VehicleIndex].Entity) return false;
|
||||
if (first.BeliefId <= 0 || first.BeliefRevision <= 0 || first.ReportId <= 0) return false;
|
||||
if (first.BeliefId != second.BeliefId || first.BeliefRevision != second.BeliefRevision || first.ReportId != second.ReportId) return false;
|
||||
if (first.ObservedAt != second.ObservedAt || first.IssuedAt != second.IssuedAt || first.ExpiresAt != second.ExpiresAt) return false;
|
||||
if (!ERR_ForceRules.Finite(first.ObservedAt) || !ERR_ForceRules.Finite(first.IssuedAt) || !ERR_ForceRules.Finite(first.ExpiresAt)) return false;
|
||||
if (first.ObservedAt < 0 || first.IssuedAt < first.ObservedAt || now < first.IssuedAt || now >= first.ExpiresAt || first.ExpiresAt > first.ObservedAt + 120000) return false;
|
||||
if (!ERR_ForceRules.FiniteVector(first.Position) || !ERR_ForceRules.FiniteVector(second.Position)) return false;
|
||||
return vector.DistanceXZ(first.Position, second.Position) >= 40;
|
||||
}
|
||||
bool ReserveInterceptPair(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder first, ERR_ConvoyInterceptOrder second, float now)
|
||||
{
|
||||
if (!InterceptPairEligible(task, first, second, now)) return false;
|
||||
foreach (ERR_ConvoyTask existing : m_Tasks)
|
||||
foreach (ERR_ConvoyVehicle claimed : existing.Vehicles) if (claimed && claimed.InterceptToken != 0) return false;
|
||||
ERR_ConvoyVehicle lead = task.Vehicles[first.VehicleIndex];
|
||||
ERR_ConvoyVehicle partner = task.Vehicles[second.VehicleIndex];
|
||||
if (!lead.ClaimIntercept(m_Mode, first)) return false;
|
||||
lead.InterceptReceipt = task.Receipt; lead.InterceptRecord = m_Mode.Forces().FindGroup(lead.Group); lead.InterceptRoute = task.Route;
|
||||
if (!InterceptPairEligible(task, first, second, now) || !partner.ClaimIntercept(m_Mode, second))
|
||||
{ lead.ClearIntercept(first); return false; }
|
||||
partner.InterceptReceipt = task.Receipt; partner.InterceptRecord = m_Mode.Forces().FindGroup(partner.Group); partner.InterceptRoute = task.Route;
|
||||
return true;
|
||||
}
|
||||
bool ApplyInterceptPair(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder first, ERR_ConvoyInterceptOrder second, float now)
|
||||
{
|
||||
if (!InterceptPairEligible(task, first, second, now) || !InterceptCurrent(task, first) || !InterceptCurrent(task, second)) return false;
|
||||
ERR_ConvoyVehicle lead = task.Vehicles[first.VehicleIndex];
|
||||
ERR_ConvoyVehicle partner = task.Vehicles[second.VehicleIndex];
|
||||
if (!lead.CanInstallIntercept(m_Mode, first, now) || !partner.CanInstallIntercept(m_Mode, second, now)) return false;
|
||||
AIWaypoint firstWaypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), first.Position));
|
||||
if (!firstWaypoint) return false;
|
||||
AIWaypoint secondWaypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), second.Position));
|
||||
if (!secondWaypoint) { SCR_EntityHelper.DeleteEntityAndChildren(firstWaypoint); return false; }
|
||||
firstWaypoint.SetCompletionRadius(15); secondWaypoint.SetCompletionRadius(15);
|
||||
if (!InterceptPairEligible(task, first, second, now) || !InterceptCurrent(task, first) || !InterceptCurrent(task, second)
|
||||
|| task.Vehicles[first.VehicleIndex] != lead || task.Vehicles[second.VehicleIndex] != partner
|
||||
|| !lead.CanInstallIntercept(m_Mode, first, now) || !partner.CanInstallIntercept(m_Mode, second, now))
|
||||
{ SCR_EntityHelper.DeleteEntityAndChildren(firstWaypoint); SCR_EntityHelper.DeleteEntityAndChildren(secondWaypoint); return false; }
|
||||
lead.InstallIntercept(firstWaypoint);
|
||||
// AddWaypoint can invoke native listeners. Do not install the partner after
|
||||
// cancellation/ownership loss; release only these exact owned claims.
|
||||
if (!InterceptPairEligible(task, first, second, now) || !InterceptCurrent(task, first) || !lead.InterceptActive
|
||||
|| !InterceptCurrent(task, second) || !partner.CanInstallIntercept(m_Mode, second, now))
|
||||
{
|
||||
SCR_EntityHelper.DeleteEntityAndChildren(secondWaypoint);
|
||||
ReleaseIntercept(task, first, now); ReleaseIntercept(task, second, now); return false;
|
||||
}
|
||||
partner.InstallIntercept(secondWaypoint);
|
||||
if (InterceptPairEligible(task, first, second, now) && InterceptCurrent(task, first) && InterceptCurrent(task, second)
|
||||
&& lead.InterceptActive && partner.InterceptActive) return true;
|
||||
ReleaseIntercept(task, first, now); ReleaseIntercept(task, second, now); return false;
|
||||
}
|
||||
'''
|
||||
assert needle in s;s=s.replace(needle,insert+needle,1);p.write_text(s)
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"scope": "One original convoy pair, distinct original escorts and HQ tokens, same delivered memory/timing, minimum 40m horizontal separation. No actors/items/ammo spawned. Only two native waypoint entities allocated.",
|
||||
"files": [
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c",
|
||||
"tests/test_convoy_intercept_pair.py"
|
||||
],
|
||||
"tests": {
|
||||
"pair": "green.log",
|
||||
"singleton_lifecycle": "lifecycle.log",
|
||||
"geometry_existing": "rules.log",
|
||||
"baseline": "baseline-red.log is explicit absent pair API assertion, not a claimed runtime behavior failure",
|
||||
"mutations": [
|
||||
"mutation-rollback-red.log",
|
||||
"mutation-spacing-red.log"
|
||||
]
|
||||
},
|
||||
"limits": [
|
||||
"Offline engine API doubles; physical driving/native callback timing unqualified.",
|
||||
"Preallocation and precommit failures leave both original routes unchanged; native ClearOrder/AddWaypoint boundary failures cancel exact claims, without atomic rollback or route restoration claim.",
|
||||
"Existing singleton behavior and legacy direct recursive ClearIntercept callback handling unchanged; no concrete native recursive callback demonstrated."
|
||||
],
|
||||
"adapters": "No existing harness changes needed; test_hq_mounted_intercept extracts geometry only. New pair test reuses unchanged lifecycle native doubles, extracts complete actual vehicle and actual service methods."
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
from pathlib import Path
|
||||
p=Path('/tmp/erri-pair-service-0054/addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c');s=p.read_text().replace('return order.Position == m_InterceptPosition &&','return !(order.Position != m_InterceptPosition) &&')
|
||||
s=s.replace(' ClearOrder(); m_InterceptRouteIndex = RouteIndex;\n m_InterceptWaypoint = detour; InterceptWaypoint = detour; InterceptActive = true;\n Group.AddWaypoint(detour);\n }\n bool DeliverIntercept',' m_InterceptRouteIndex = RouteIndex;\n m_InterceptWaypoint = detour; InterceptWaypoint = detour; InterceptActive = true;\n Group.AddWaypoint(detour);\n }\n bool DeliverIntercept',1)
|
||||
needle=' lead.InstallIntercept(firstWaypoint);'
|
||||
s=s.replace(needle,''' // Route removal is also a native boundary; both prepared handles remain
|
||||
// caller-owned until both exact claims have survived it.
|
||||
lead.ClearOrder();
|
||||
if (InterceptPairEligible(task, first, second, now) && InterceptCurrent(task, first) && InterceptCurrent(task, second)) partner.ClearOrder();
|
||||
if (!InterceptPairEligible(task, first, second, now) || !InterceptCurrent(task, first) || !InterceptCurrent(task, second)
|
||||
|| task.Vehicles[first.VehicleIndex] != lead || task.Vehicles[second.VehicleIndex] != partner
|
||||
|| !lead.CanInstallIntercept(m_Mode, first, now) || !partner.CanInstallIntercept(m_Mode, second, now))
|
||||
{
|
||||
SCR_EntityHelper.DeleteEntityAndChildren(firstWaypoint); SCR_EntityHelper.DeleteEntityAndChildren(secondWaypoint);
|
||||
ReleaseIntercept(task, first, now); ReleaseIntercept(task, second, now); return false;
|
||||
}
|
||||
'''+needle,1);p.write_text(s)
|
||||
@@ -0,0 +1,223 @@
|
||||
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c
|
||||
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c
|
||||
@@ -193,6 +193,20 @@
|
||||
InterceptActive = false; InterceptOrder = null; InterceptReceipt = null; InterceptRecord = null; InterceptRoute = null;
|
||||
m_InterceptMembers.Clear(); m_InterceptSeats.Clear();
|
||||
}
|
||||
+ // Pair preparation owns no route state. Recheck after both native allocations.
|
||||
+ bool CanInstallIntercept(ERR_GameMode mode, ERR_ConvoyInterceptOrder order, float now)
|
||||
+ {
|
||||
+ if (!InterceptMatches(order) || order.Token != m_InterceptToken || InterceptActive || !InterceptCrew(mode)) return false;
|
||||
+ return !(order.Position != m_InterceptPosition) && order.ExpiresAt == m_InterceptExpiresAt && now < m_InterceptExpiresAt;
|
||||
+ }
|
||||
+ // Caller transfers ownership of the prepared handle before AddWaypoint, which
|
||||
+ // is a native boundary rather than a transactional pair operation.
|
||||
+ void InstallIntercept(AIWaypoint detour)
|
||||
+ {
|
||||
+ m_InterceptRouteIndex = RouteIndex;
|
||||
+ m_InterceptWaypoint = detour; InterceptWaypoint = detour; InterceptActive = true;
|
||||
+ Group.AddWaypoint(detour);
|
||||
+ }
|
||||
bool DeliverIntercept(ERR_GameMode mode, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!InterceptMatches(order) || order.Token != m_InterceptToken || InterceptActive || !InterceptCrew(mode)) return false;
|
||||
@@ -562,6 +576,75 @@
|
||||
unit.InterceptReceipt = task.Receipt; unit.InterceptRecord = m_Mode.Forces().FindGroup(unit.Group); unit.InterceptRoute = task.Route;
|
||||
return true;
|
||||
}
|
||||
+ protected bool InterceptPairEligible(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder first, ERR_ConvoyInterceptOrder second, float now)
|
||||
+ {
|
||||
+ if (!first || !second || first == second || first.Group == second.Group || first.VehicleIndex == second.VehicleIndex || first.Token == second.Token) return false;
|
||||
+ if (!InterceptEligible(task, first) || !InterceptEligible(task, second) || !ERR_ForceRules.Finite(now) || now < 0) return false;
|
||||
+ if (task.Vehicles[first.VehicleIndex].Entity == task.Vehicles[second.VehicleIndex].Entity) return false;
|
||||
+ if (first.BeliefId <= 0 || first.BeliefRevision <= 0 || first.ReportId <= 0) return false;
|
||||
+ if (first.BeliefId != second.BeliefId || first.BeliefRevision != second.BeliefRevision || first.ReportId != second.ReportId) return false;
|
||||
+ if (first.ObservedAt != second.ObservedAt || first.IssuedAt != second.IssuedAt || first.ExpiresAt != second.ExpiresAt) return false;
|
||||
+ if (!ERR_ForceRules.Finite(first.ObservedAt) || !ERR_ForceRules.Finite(first.IssuedAt) || !ERR_ForceRules.Finite(first.ExpiresAt)) return false;
|
||||
+ if (first.ObservedAt < 0 || first.IssuedAt < first.ObservedAt || now < first.IssuedAt || now >= first.ExpiresAt || first.ExpiresAt > first.ObservedAt + 120000) return false;
|
||||
+ if (!ERR_ForceRules.FiniteVector(first.Position) || !ERR_ForceRules.FiniteVector(second.Position)) return false;
|
||||
+ return vector.DistanceXZ(first.Position, second.Position) >= 40;
|
||||
+ }
|
||||
+ bool ReserveInterceptPair(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder first, ERR_ConvoyInterceptOrder second, float now)
|
||||
+ {
|
||||
+ if (!InterceptPairEligible(task, first, second, now)) return false;
|
||||
+ foreach (ERR_ConvoyTask existing : m_Tasks)
|
||||
+ foreach (ERR_ConvoyVehicle claimed : existing.Vehicles) if (claimed && claimed.InterceptToken != 0) return false;
|
||||
+ ERR_ConvoyVehicle lead = task.Vehicles[first.VehicleIndex];
|
||||
+ ERR_ConvoyVehicle partner = task.Vehicles[second.VehicleIndex];
|
||||
+ if (!lead.ClaimIntercept(m_Mode, first)) return false;
|
||||
+ lead.InterceptReceipt = task.Receipt; lead.InterceptRecord = m_Mode.Forces().FindGroup(lead.Group); lead.InterceptRoute = task.Route;
|
||||
+ if (!InterceptPairEligible(task, first, second, now) || !partner.ClaimIntercept(m_Mode, second))
|
||||
+ { lead.ClearIntercept(first); return false; }
|
||||
+ partner.InterceptReceipt = task.Receipt; partner.InterceptRecord = m_Mode.Forces().FindGroup(partner.Group); partner.InterceptRoute = task.Route;
|
||||
+ return true;
|
||||
+ }
|
||||
+ bool ApplyInterceptPair(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder first, ERR_ConvoyInterceptOrder second, float now)
|
||||
+ {
|
||||
+ if (!InterceptPairEligible(task, first, second, now) || !InterceptCurrent(task, first) || !InterceptCurrent(task, second)) return false;
|
||||
+ ERR_ConvoyVehicle lead = task.Vehicles[first.VehicleIndex];
|
||||
+ ERR_ConvoyVehicle partner = task.Vehicles[second.VehicleIndex];
|
||||
+ if (!lead.CanInstallIntercept(m_Mode, first, now) || !partner.CanInstallIntercept(m_Mode, second, now)) return false;
|
||||
+ AIWaypoint firstWaypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), first.Position));
|
||||
+ if (!firstWaypoint) return false;
|
||||
+ AIWaypoint secondWaypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), second.Position));
|
||||
+ if (!secondWaypoint) { SCR_EntityHelper.DeleteEntityAndChildren(firstWaypoint); return false; }
|
||||
+ firstWaypoint.SetCompletionRadius(15); secondWaypoint.SetCompletionRadius(15);
|
||||
+ if (!InterceptPairEligible(task, first, second, now) || !InterceptCurrent(task, first) || !InterceptCurrent(task, second)
|
||||
+ || task.Vehicles[first.VehicleIndex] != lead || task.Vehicles[second.VehicleIndex] != partner
|
||||
+ || !lead.CanInstallIntercept(m_Mode, first, now) || !partner.CanInstallIntercept(m_Mode, second, now))
|
||||
+ { SCR_EntityHelper.DeleteEntityAndChildren(firstWaypoint); SCR_EntityHelper.DeleteEntityAndChildren(secondWaypoint); return false; }
|
||||
+ // Route removal is also a native boundary; both prepared handles remain
|
||||
+ // caller-owned until both exact claims have survived it.
|
||||
+ lead.ClearOrder();
|
||||
+ if (InterceptPairEligible(task, first, second, now) && InterceptCurrent(task, first) && InterceptCurrent(task, second)) partner.ClearOrder();
|
||||
+ if (!InterceptPairEligible(task, first, second, now) || !InterceptCurrent(task, first) || !InterceptCurrent(task, second)
|
||||
+ || task.Vehicles[first.VehicleIndex] != lead || task.Vehicles[second.VehicleIndex] != partner
|
||||
+ || !lead.CanInstallIntercept(m_Mode, first, now) || !partner.CanInstallIntercept(m_Mode, second, now))
|
||||
+ {
|
||||
+ SCR_EntityHelper.DeleteEntityAndChildren(firstWaypoint); SCR_EntityHelper.DeleteEntityAndChildren(secondWaypoint);
|
||||
+ ReleaseIntercept(task, first, now); ReleaseIntercept(task, second, now); return false;
|
||||
+ }
|
||||
+ lead.InstallIntercept(firstWaypoint);
|
||||
+ // AddWaypoint can invoke native listeners. Do not install the partner after
|
||||
+ // cancellation/ownership loss; release only these exact owned claims.
|
||||
+ if (!InterceptPairEligible(task, first, second, now) || !InterceptCurrent(task, first) || !lead.InterceptActive
|
||||
+ || !InterceptCurrent(task, second) || task.Vehicles[first.VehicleIndex] != lead || task.Vehicles[second.VehicleIndex] != partner
|
||||
+ || !partner.CanInstallIntercept(m_Mode, second, now))
|
||||
+ {
|
||||
+ SCR_EntityHelper.DeleteEntityAndChildren(secondWaypoint);
|
||||
+ ReleaseIntercept(task, first, now); ReleaseIntercept(task, second, now); return false;
|
||||
+ }
|
||||
+ partner.InstallIntercept(secondWaypoint);
|
||||
+ if (InterceptPairEligible(task, first, second, now) && InterceptCurrent(task, first) && InterceptCurrent(task, second)
|
||||
+ && task.Vehicles[first.VehicleIndex] == lead && task.Vehicles[second.VehicleIndex] == partner
|
||||
+ && lead.InterceptActive && partner.InterceptActive) return true;
|
||||
+ ReleaseIntercept(task, first, now); ReleaseIntercept(task, second, now); return false;
|
||||
+ }
|
||||
bool ApplyIntercept(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!InterceptCurrent(task, order) || !ERR_ForceRules.Finite(now) || now < order.IssuedAt || now >= order.ExpiresAt) return false;
|
||||
--- /dev/null
|
||||
+++ b/tests/test_convoy_intercept_pair.py
|
||||
@@ -0,0 +1,121 @@
|
||||
+"""Execute actual pair admission/apply plus the complete existing vehicle policy.
|
||||
+Native doubles inject allocation, AddWaypoint and RemoveWaypoint boundaries.
|
||||
+"""
|
||||
+import contextlib,io,os,re,runpy
|
||||
+from pathlib import Path
|
||||
+import policy_harness as h
|
||||
+path=Path(os.environ.get('ERR_CONVOY_SERVICE',h.ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c'))
|
||||
+source=path.read_text()
|
||||
+assert 'bool ReserveInterceptPair(' in source and 'bool ApplyInterceptPair(' in source, 'paired service API absent (baseline feature gap)'
|
||||
+# Reuse existing full vehicle/native doubles without running or changing its assertions.
|
||||
+captured=[]; original_run=h.run
|
||||
+try:
|
||||
+ h.run=captured.append
|
||||
+ with contextlib.redirect_stdout(io.StringIO()): runpy.run_path(str(h.ROOT/'tests/test_convoy_lifecycle.py'))
|
||||
+finally: h.run=original_run
|
||||
+assert len(captured)==1
|
||||
+cpp=captured[0]
|
||||
+pair='\n'.join(h.body(path,s) for s in ('protected bool InterceptPairEligible(', 'bool ReserveInterceptPair(', 'bool ApplyInterceptPair('))
|
||||
+pair=pair.replace('protected ','').replace('foreach (','for (')
|
||||
+for typ in ['ERR_ConvoyTask','ERR_ConvoyInterceptOrder','ERR_ConvoyVehicle','AIWaypoint']:
|
||||
+ pair=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',pair)
|
||||
+for obj in ['task','first','second','lead','partner','existing','claimed','m_Mode','firstWaypoint','secondWaypoint','Receipt']:
|
||||
+ pair=pair.replace(obj+'.',obj+'->')
|
||||
+pair=re.sub(r'(Vehicles\[[^\]]+\])\.',r'\1->',pair).replace('Forces().','Forces()->')
|
||||
+for ns in ['ERR_ForceRules','ERR_Actors','ERR_Resources','SCR_EntityHelper','vector']:
|
||||
+ pair=pair.replace(ns+'.',ns+'::')
|
||||
+pair=pair.replace('AIWaypoint.Cast(', 'static_cast<AIWaypoint*>(')
|
||||
+cpp=cpp.replace('bool ApplyIntercept(ERR_ConvoyTask*',pair+'\nbool ApplyIntercept(ERR_ConvoyTask*',1)
|
||||
+cpp=cpp[:cpp.index('int main(){')]
|
||||
+cpp=cpp.replace('#include <cassert>','#include <cassert>\n#include <functional>\n#include <map>')
|
||||
+cpp=cpp.replace('void RemoveWaypoint(AIWaypoint*){removes++;}void AddWaypoint(AIWaypoint*){adds++;}', 'std::function<void()> onRemove,onAdd;void RemoveWaypoint(AIWaypoint*){removes++;if(onRemove)onRemove();}void AddWaypoint(AIWaypoint*){adds++;if(onAdd)onAdd();}')
|
||||
+cpp=cpp.replace('ERR_ForceRecord* FindGroup(SCR_AIGroup*){return &record;}', 'std::map<SCR_AIGroup*,ERR_ForceRecord*> records;ERR_ForceRecord* FindGroup(SCR_AIGroup* g){return records.count(g)?records[g]:&record;}')
|
||||
+cpp=cpp.replace('int LogisticsToken(SCR_AIGroup*){return token;}', 'std::map<SCR_AIGroup*,int> tokens;int LogisticsToken(SCR_AIGroup* g){return tokens.count(g)?tokens[g]:token;}')
|
||||
+cpp=cpp.replace('int spawns=0;bool fail=false;', 'int spawns=0,failAt=0;bool fail=false;std::function<void()> onSpawn;')
|
||||
+cpp=cpp.replace('spawns++;return fail?nullptr:new AIWaypoint();','spawns++;if(onSpawn)onSpawn();return (fail||spawns==failAt)?nullptr:new AIWaypoint();')
|
||||
+cpp+=r'''
|
||||
+struct PairCase:InterceptCase{
|
||||
+ Case b; ERR_ConvoyInterceptOrder second;ERR_ConvoyVehicle fillers[4];
|
||||
+ PairCase(){
|
||||
+ b.unit.Index=3;b.mode.ledger.record.Id=10;b.mode.ledger.record.SourceId="original-vehicle-3";
|
||||
+ mode.ledger.records[&group]=&mode.ledger.record;mode.ledger.records[&b.group]=&b.mode.ledger.record;
|
||||
+ mode.command.tokens[&group]=400001;mode.command.tokens[&b.group]=400002;
|
||||
+ second=order;second.Token=400002;second.Group=&b.group;second.VehicleIndex=3;second.Position={740,0,0};
|
||||
+ task.Vehicles={&unit,&fillers[0],&fillers[1],&b.unit,&fillers[2],&fillers[3]};
|
||||
+ unit.Tick(&mode,&route,0);b.unit.Tick(&mode,&route,0);
|
||||
+ }
|
||||
+ bool ReservePair(){return service.ReserveInterceptPair(&task,&order,&second,1000);}
|
||||
+ bool ApplyPair(){return service.ApplyInterceptPair(&task,&order,&second,2000);}
|
||||
+};
|
||||
+int main(){
|
||||
+ {PairCase f;auto a=f.unit.Waypoint;auto b=f.b.unit.Waypoint;float da=f.unit.LegDeadline,db=f.b.unit.LegDeadline;
|
||||
+ assert(f.ReservePair());assert(f.service.InterceptCurrent(&f.task,&f.order)&&f.service.InterceptCurrent(&f.task,&f.second));
|
||||
+ assert(f.unit.Waypoint==a&&f.b.unit.Waypoint==b&&!f.unit.InterceptActive&&!f.b.unit.InterceptActive&&f.unit.LegDeadline==da&&f.b.unit.LegDeadline==db);
|
||||
+ assert(f.ApplyPair());assert(f.unit.InterceptActive&&f.b.unit.InterceptActive&&f.unit.InterceptWaypoint!=f.b.unit.InterceptWaypoint);
|
||||
+ assert(f.unit.InterceptWaypoint->radius==15&&f.b.unit.InterceptWaypoint->radius==15&&f.unit.RouteIndex==0&&f.b.unit.RouteIndex==0);
|
||||
+ assert(!f.ApplyPair());f.vehicle.position=f.order.Position;f.b.vehicle.position=f.second.Position;
|
||||
+ f.unit.Tick(&f.mode,&f.route,3000);f.b.unit.Tick(&f.mode,&f.route,3000);
|
||||
+ assert(f.unit.InterceptActive&&f.b.unit.InterceptActive&&!f.unit.InterceptWaypoint&&!f.b.unit.InterceptWaypoint);
|
||||
+ f.unit.Tick(&f.mode,&f.route,121000);f.b.unit.Tick(&f.mode,&f.route,121000);
|
||||
+ assert(!f.unit.InterceptToken&&!f.b.unit.InterceptToken&&f.unit.Waypoint&&f.b.unit.Waypoint);
|
||||
+ }
|
||||
+ for(int bad=0;bad<19;bad++){PairCase f;auto a=f.unit.Waypoint;auto b=f.b.unit.Waypoint;
|
||||
+ if(bad==0)f.second.Token=f.order.Token;
|
||||
+ if(bad==1)f.second.Group=f.order.Group;
|
||||
+ if(bad==2)f.second.VehicleIndex=0;
|
||||
+ if(bad==3)f.second.Position.x=739;
|
||||
+ if(bad==4)f.second.Position.x=NAN;
|
||||
+ if(bad==5)f.second.BeliefId++;
|
||||
+ if(bad==6)f.second.BeliefRevision++;
|
||||
+ if(bad==7)f.second.ReportId++;
|
||||
+ if(bad==8)f.second.ObservedAt++;
|
||||
+ if(bad==9)f.second.IssuedAt++;
|
||||
+ if(bad==10)f.second.ExpiresAt++;
|
||||
+ if(bad==11)f.second.SourceId="foreign";
|
||||
+ if(bad==12)f.b.driver.player=true;
|
||||
+ if(bad==13)f.b.seat.occupant=nullptr;
|
||||
+ if(bad==14)f.fillers[0].SupplyToken=9;
|
||||
+ if(bad==15)f.fillers[0].Combat->Held=true;
|
||||
+ if(bad==16)f.mode.command.tokens[&f.b.group]=0;
|
||||
+ if(bad==17)f.b.unit.Entity=&f.vehicle;
|
||||
+ if(bad==18){f.order.ExpiresAt=121001;f.second.ExpiresAt=121001;}
|
||||
+ assert(!f.ReservePair()&&!f.unit.InterceptToken&&!f.b.unit.InterceptToken&&f.unit.Waypoint==a&&f.b.unit.Waypoint==b);
|
||||
+ }
|
||||
+ // Claim two fails after claim one: spent token cannot be reused, route untouched.
|
||||
+ {PairCase f;assert(f.b.unit.ClaimIntercept(&f.mode,&f.second));f.b.unit.ClearIntercept(&f.second);
|
||||
+ auto a=f.unit.Waypoint;auto b=f.b.unit.Waypoint;float deadline=f.unit.LegDeadline;
|
||||
+ assert(!f.ReservePair());assert(!f.unit.InterceptToken&&!f.b.unit.InterceptToken&&f.unit.Waypoint==a&&f.b.unit.Waypoint==b&&f.unit.LegDeadline==deadline);
|
||||
+ }
|
||||
+ // Global exclusion includes a different original task; never steal its claim.
|
||||
+ {PairCase f;ERR_ConvoyTask other;ERR_ConvoyVehicle owned;owned.InterceptToken=400099;other.Vehicles={&owned};f.service.m_Tasks.Insert(&other);
|
||||
+ assert(!f.ReservePair()&&owned.InterceptToken==400099&&!f.unit.InterceptToken);}
|
||||
+ for(int allocation=1;allocation<=2;allocation++){PairCase f;assert(f.ReservePair());auto a=f.unit.Waypoint;auto b=f.b.unit.Waypoint;int deletes=SCR_EntityHelper::deletes,adds=f.group.adds+f.b.group.adds,removes=f.group.removes+f.b.group.removes;
|
||||
+ ERR_Actors::failAt=ERR_Actors::spawns+allocation;assert(!f.ApplyPair());ERR_Actors::failAt=0;
|
||||
+ assert(!f.unit.InterceptActive&&!f.b.unit.InterceptActive&&f.unit.Waypoint==a&&f.b.unit.Waypoint==b);
|
||||
+ assert(SCR_EntityHelper::deletes==deletes+allocation-1);
|
||||
+ assert(f.group.adds+f.b.group.adds==adds&&f.group.removes+f.b.group.removes==removes);
|
||||
+ assert(f.unit.Entity==&f.vehicle&&f.b.unit.Entity==&f.b.vehicle&&f.unit.Driver==&f.driver&&f.b.unit.Driver==&f.b.driver);
|
||||
+ }
|
||||
+ // Allocation may revoke original crew. Both prepared handles are deleted before either route changes.
|
||||
+ {PairCase f;assert(f.ReservePair());auto a=f.unit.Waypoint;auto b=f.b.unit.Waypoint;int deletes=SCR_EntityHelper::deletes;
|
||||
+ ERR_Actors::onSpawn=[&](){f.b.driver.alive=false;};assert(!f.ApplyPair());ERR_Actors::onSpawn={};
|
||||
+ assert(f.unit.Waypoint==a&&f.b.unit.Waypoint==b&&SCR_EntityHelper::deletes==deletes+2);}
|
||||
+ // Immutable claimed destinations: changing both orders together is still rejected.
|
||||
+ {PairCase f;assert(f.ReservePair());f.order.Position.x+=100;f.second.Position.x+=100;assert(!f.ApplyPair()&&!f.unit.InterceptActive&&!f.b.unit.InterceptActive);}
|
||||
+ // Native removal and AddWaypoint may synchronously cancel the pair.
|
||||
+ for(int boundary=0;boundary<3;boundary++){PairCase f;assert(f.ReservePair());int a=f.group.adds,b=f.b.group.adds;
|
||||
+ auto cancel=[&](){f.service.ReleaseIntercept(&f.task,&f.order,2000);f.service.ReleaseIntercept(&f.task,&f.second,2000);};
|
||||
+ if(boundary==0)f.group.onRemove=[&](){f.mode.command.tokens[&f.b.group]=0;};
|
||||
+ if(boundary==1)f.group.onAdd=cancel;
|
||||
+ if(boundary==2)f.b.group.onAdd=cancel;
|
||||
+ assert(!f.ApplyPair());assert(!f.unit.InterceptToken&&!f.b.unit.InterceptToken&&!f.unit.InterceptActive&&!f.b.unit.InterceptActive);
|
||||
+ assert(f.b.group.adds==b+(boundary==2));assert(f.group.adds==a+(boundary!=0));
|
||||
+ }
|
||||
+ // Per-member release remains exact and never steals a foreign public waypoint.
|
||||
+ {PairCase f;assert(f.ReservePair());assert(f.ApplyPair());AIWaypoint foreign;f.b.unit.InterceptWaypoint=&foreign;
|
||||
+ f.service.ReleaseIntercept(&f.task,&f.order,3000);assert(!f.unit.InterceptToken&&f.b.unit.InterceptToken==400002);
|
||||
+ f.service.ReleaseIntercept(&f.task,&f.second,3000);assert(f.b.unit.InterceptWaypoint==&foreign&&!f.b.unit.InterceptToken);}
|
||||
+}
|
||||
+'''
|
||||
+h.run(cpp)
|
||||
+print('PASS: actual pair admission, rollback, native allocation/revocation boundaries, per-member ownership, hold and expiry')
|
||||
@@ -0,0 +1 @@
|
||||
PASS: actual pair admission, rollback, native allocation/revocation boundaries, per-member ownership, hold and expiry
|
||||
@@ -0,0 +1 @@
|
||||
PASS: actual native seated-group arrival outside35m, native waypoint removal, idempotent advance, and convoy waypoint idempotence/arrival/deadline, missing/dead/captured/reparented crew refusal, failed native waypoint release, original vehicle/member preservation and no replacement
|
||||
@@ -0,0 +1,12 @@
|
||||
check: /tmp/err-policy-_v2ln7x4/check.cpp:413: int main(): Assertion `!f.unit.InterceptToken&&!f.b.unit.InterceptToken&&f.unit.Waypoint==a&&f.b.unit.Waypoint==b&&f.unit.LegDeadline==deadline' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-pair-service-0054/tests/test_convoy_intercept_pair.py", line 120, in <module>
|
||||
h.run(cpp)
|
||||
~~~~~^^^^^
|
||||
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
|
||||
subprocess.run([str(p/'check')],check=True)
|
||||
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/usr/lib/python3.14/subprocess.py", line 578, in run
|
||||
raise CalledProcessError(retcode, process.args,
|
||||
output=stdout, stderr=stderr)
|
||||
subprocess.CalledProcessError: Command '['/tmp/err-policy-_v2ln7x4/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1,717 @@
|
||||
// Independent physical adaptation of the owner's supply-convoy extension.
|
||||
// HQ must dispatch an authored route using a finite canonical pool receipt.
|
||||
class ERR_ConvoyResources
|
||||
{
|
||||
static ResourceName VehiclePrefab(int index)
|
||||
{
|
||||
if (index == 1) return "{3336BE330C4F355B}Prefabs/Vehicles/Wheeled/Ural4320/Ural4320_ammo.et";
|
||||
if (index == 2) return "{16C1F16C9B053801}Prefabs/Vehicles/Wheeled/Ural4320/Ural4320_transport.et";
|
||||
if (index == 4) return "{1DC01AAACFFA8B63}Prefabs/Vehicles/Tracked/BMP1/BMP1_Base.et";
|
||||
if (index == 0 || index == 3 || index == 5) return "{0B4DEA8078B78A9B}Prefabs/Vehicles/Wheeled/UAZ469/UAZ469_PKM.et";
|
||||
return "";
|
||||
}
|
||||
static ResourceName GroupPrefab() { return "{7A968AB297475FA7}Prefabs/ERR/ConvoyCrew.et"; }
|
||||
static ResourceName Soldier()
|
||||
{
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
if (mode && mode.FactionSelection() && mode.FactionSelection().Ready() && mode.FactionSelection().Active("enemy") != "ussr") return mode.FactionSelection().Soldier();
|
||||
return "{DCB41B3746FDD1BE}Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_Rifleman.et"; }
|
||||
static ResourceName Rifle() { return "{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et"; }
|
||||
static ResourceName Magazine() { return "{BBB50A815A2F916B}Prefabs/Weapons/Magazines/Magazine_545x39_AK_30rnd_Ball.et"; }
|
||||
static bool Available()
|
||||
{
|
||||
array<ResourceName> required = {GroupPrefab(), Soldier(), Rifle(), Magazine(), ERR_Resources.MoveWaypoint()};
|
||||
for (int i = 0; i < 6; i++) required.Insert(VehiclePrefab(i));
|
||||
foreach (ResourceName prefab : required)
|
||||
{
|
||||
Resource resource = Resource.Load(prefab);
|
||||
if (!resource || !resource.IsValid()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_ConvoyRules
|
||||
{
|
||||
static bool EmptyCrewTemplate(SCR_AIGroup group)
|
||||
{
|
||||
// The vanilla faction base authors six queued soldiers. Only our memberless
|
||||
// template may receive finite seat occupants; never retire another spawn job.
|
||||
return group && !group.GetSpawnImmediately() && group.GetAgentsCount() == 0
|
||||
&& (!group.m_aUnitPrefabSlots || group.m_aUnitPrefabSlots.IsEmpty());
|
||||
}
|
||||
static bool Plan(array<vector> positions, array<vector> angles, ERR_ForceRoute route)
|
||||
{
|
||||
if (!positions || !angles || positions.Count() != 6 || angles.Count() != 6 || !route || route.Looped() || route.Count() < 2 || !route.Seal()) return false;
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
if (!ERR_ForceRules.FiniteVector(positions[i]) || !ERR_ForceRules.FiniteVector(angles[i])) return false;
|
||||
for (int j = 0; j < i; j++) if (vector.DistanceXZ(positions[i], positions[j]) < 18) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static bool GroundClear(array<vector> positions, array<vector> angles)
|
||||
{
|
||||
if (!positions || !angles || positions.Count() != 6 || angles.Count() != 6) return false;
|
||||
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
|
||||
for (int i = 0; i < positions.Count(); i++) if (!placement.Clear(positions[i], angles[i][0])) return false;
|
||||
return true;
|
||||
}
|
||||
static bool PartOf(IEntity entity, IEntity root)
|
||||
{
|
||||
int depth;
|
||||
while (entity && depth++ < 32)
|
||||
{
|
||||
if (entity == root) return true;
|
||||
entity = entity.GetParent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static bool Captured(Vehicle vehicle, SCR_AIGroup group)
|
||||
{
|
||||
if (group && group.GetPlayerCount(true) > 0) return true;
|
||||
if (!vehicle) return false;
|
||||
array<int> ids = {}; GetGame().GetPlayerManager().GetPlayers(ids);
|
||||
foreach (int id : ids)
|
||||
{
|
||||
IEntity player = GetGame().GetPlayerManager().GetPlayerControlledEntity(id);
|
||||
if (player && PartOf(CompartmentAccessComponent.GetVehicleIn(player), vehicle)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static bool CollectSeats(IEntity root, array<BaseCompartmentSlot> seats)
|
||||
{
|
||||
array<IEntity> pending = {root};
|
||||
int cursor;
|
||||
while (cursor < pending.Count() && cursor < 128)
|
||||
{
|
||||
IEntity entity = pending[cursor++];
|
||||
if (!entity) return false;
|
||||
SCR_BaseCompartmentManagerComponent manager = SCR_BaseCompartmentManagerComponent.Cast(entity.FindComponent(SCR_BaseCompartmentManagerComponent));
|
||||
if (manager)
|
||||
{
|
||||
array<BaseCompartmentSlot> local = {}; manager.GetCompartments(local);
|
||||
foreach (BaseCompartmentSlot seat : local) if (seat && !seats.Contains(seat)) seats.Insert(seat);
|
||||
}
|
||||
IEntity child = entity.GetChildren();
|
||||
while (child)
|
||||
{
|
||||
if (pending.Count() >= 128 || pending.Contains(child)) return false;
|
||||
pending.Insert(child); child = child.GetSibling();
|
||||
}
|
||||
}
|
||||
return cursor == pending.Count();
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_ConvoyVehicle
|
||||
{
|
||||
int Index;
|
||||
Vehicle Entity;
|
||||
SCR_AIGroup Group;
|
||||
IEntity Driver;
|
||||
ref array<IEntity> Members = {};
|
||||
ref array<BaseCompartmentSlot> Seats = {};
|
||||
AIWaypoint Waypoint;
|
||||
int RouteIndex;
|
||||
bool Released;
|
||||
bool Registered;
|
||||
// Latched only for an exact tracked hunter at authoritative player entry.
|
||||
bool PlayerCaptureObserved;
|
||||
ref ERR_ConvoyCombatState Combat = new ERR_ConvoyCombatState();
|
||||
int SupplyToken;
|
||||
bool SupplyHeld;
|
||||
int InterceptToken;
|
||||
bool InterceptActive;
|
||||
AIWaypoint InterceptWaypoint;
|
||||
ref ERR_ConvoyInterceptOrder InterceptOrder;
|
||||
ERR_ConvoyReceipt InterceptReceipt;
|
||||
ERR_ForceRecord InterceptRecord;
|
||||
ERR_ForceRoute InterceptRoute;
|
||||
protected AIWaypoint m_InterceptWaypoint;
|
||||
protected Vehicle m_InterceptEntity;
|
||||
protected SCR_AIGroup m_InterceptGroup;
|
||||
protected IEntity m_InterceptDriver;
|
||||
protected ref array<IEntity> m_InterceptMembers = {};
|
||||
protected ref array<BaseCompartmentSlot> m_InterceptSeats = {};
|
||||
protected int m_InterceptToken;
|
||||
protected int m_LastInterceptToken;
|
||||
protected vector m_InterceptPosition;
|
||||
protected float m_InterceptExpiresAt;
|
||||
protected int m_InterceptRouteIndex;
|
||||
|
||||
bool Arrived;
|
||||
float LegDeadline;
|
||||
float NextReadinessNote;
|
||||
|
||||
// Pending downlink owns a claim only; normal authored travel continues.
|
||||
bool InterceptCrew(ERR_GameMode mode)
|
||||
{
|
||||
if (!mode || !CrewValid(mode) || !Entity || !Group || ERR_ConvoyRules.Captured(Entity, Group)) return false;
|
||||
if (Members.IsEmpty() || Members.Count() > 8 || Seats.Count() != Members.Count()) return false;
|
||||
if (InterceptOrder && (Members.Count() != m_InterceptMembers.Count() || Seats.Count() != m_InterceptSeats.Count())) return false;
|
||||
array<AIAgent> agents = {}; Group.GetAgents(agents);
|
||||
if (agents.Count() != Members.Count()) return false;
|
||||
for (int i = 0; i < Members.Count(); i++)
|
||||
{
|
||||
if (!Members[i] || !ERR_Actors.CanFight(Members[i]) || !Seats[i] || Seats[i].GetOccupant() != Members[i]) return false;
|
||||
int attributed;
|
||||
foreach (AIAgent agent : agents)
|
||||
{
|
||||
if (!agent || !Members.Contains(agent.GetControlledEntity())) return false;
|
||||
if (agent.GetControlledEntity() == Members[i]) attributed++;
|
||||
}
|
||||
if (attributed != 1) return false;
|
||||
if (InterceptOrder && (Members[i] != m_InterceptMembers[i] || Seats[i] != m_InterceptSeats[i])) return false;
|
||||
}
|
||||
if (InterceptOrder && (Entity != m_InterceptEntity || Group != m_InterceptGroup || Driver != m_InterceptDriver)) return false;
|
||||
return true;
|
||||
}
|
||||
bool InterceptMatches(ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
return order && InterceptOrder == order && InterceptToken == m_InterceptToken && m_InterceptToken != 0;
|
||||
}
|
||||
bool ClaimIntercept(ERR_GameMode mode, ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
if (!order || order.Token <= m_LastInterceptToken || InterceptToken != 0 || InterceptOrder || InterceptWaypoint || !InterceptCrew(mode)) return false;
|
||||
m_InterceptEntity = Entity; m_InterceptGroup = Group; m_InterceptDriver = Driver;
|
||||
m_InterceptMembers.Clear(); m_InterceptSeats.Clear();
|
||||
foreach (IEntity member : Members) m_InterceptMembers.Insert(member);
|
||||
foreach (BaseCompartmentSlot seat : Seats) m_InterceptSeats.Insert(seat);
|
||||
m_InterceptToken = order.Token; m_LastInterceptToken = order.Token; InterceptToken = order.Token; InterceptOrder = order;
|
||||
m_InterceptPosition = order.Position; m_InterceptExpiresAt = order.ExpiresAt;
|
||||
return true;
|
||||
}
|
||||
void ClearIntercept(ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
if (!InterceptMatches(order)) return;
|
||||
// Delete only the exact waypoint this owner created, never an adopted handle.
|
||||
if (m_InterceptWaypoint && m_InterceptGroup) m_InterceptGroup.RemoveWaypoint(m_InterceptWaypoint);
|
||||
if (m_InterceptWaypoint) SCR_EntityHelper.DeleteEntityAndChildren(m_InterceptWaypoint);
|
||||
if (InterceptWaypoint == m_InterceptWaypoint) InterceptWaypoint = null;
|
||||
m_InterceptWaypoint = null; InterceptToken = 0; m_InterceptToken = 0;
|
||||
InterceptActive = false; InterceptOrder = null; InterceptReceipt = null; InterceptRecord = null; InterceptRoute = null;
|
||||
m_InterceptMembers.Clear(); m_InterceptSeats.Clear();
|
||||
}
|
||||
// Pair preparation owns no route state. Recheck after both native allocations.
|
||||
bool CanInstallIntercept(ERR_GameMode mode, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!InterceptMatches(order) || order.Token != m_InterceptToken || InterceptActive || !InterceptCrew(mode)) return false;
|
||||
return !(order.Position != m_InterceptPosition) && order.ExpiresAt == m_InterceptExpiresAt && now < m_InterceptExpiresAt;
|
||||
}
|
||||
// Caller transfers ownership of the prepared handle before AddWaypoint, which
|
||||
// is a native boundary rather than a transactional pair operation.
|
||||
void InstallIntercept(AIWaypoint detour)
|
||||
{
|
||||
m_InterceptRouteIndex = RouteIndex;
|
||||
m_InterceptWaypoint = detour; InterceptWaypoint = detour; InterceptActive = true;
|
||||
Group.AddWaypoint(detour);
|
||||
}
|
||||
bool DeliverIntercept(ERR_GameMode mode, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!InterceptMatches(order) || order.Token != m_InterceptToken || InterceptActive || !InterceptCrew(mode)) return false;
|
||||
if (order.Position != m_InterceptPosition || order.ExpiresAt != m_InterceptExpiresAt || now >= m_InterceptExpiresAt) return false;
|
||||
AIWaypoint detour = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), m_InterceptPosition));
|
||||
if (!detour) return false;
|
||||
detour.SetCompletionRadius(15);
|
||||
ClearOrder(); m_InterceptRouteIndex = RouteIndex;
|
||||
m_InterceptWaypoint = detour; InterceptWaypoint = detour; InterceptActive = true;
|
||||
Group.AddWaypoint(detour);
|
||||
return true;
|
||||
}
|
||||
// True suppresses ordinary travel only after actual radio delivery. No route
|
||||
// index is consumed by arrival at the detour; removal leaves the seated group held.
|
||||
bool TickIntercept(ERR_GameMode mode, float now)
|
||||
{
|
||||
if (!InterceptOrder) return false;
|
||||
if (!InterceptMatches(InterceptOrder)) return InterceptActive;
|
||||
if (Released || Arrived || now >= m_InterceptExpiresAt || !InterceptCrew(mode) || Combat.Held || SupplyHeld || SupplyToken != 0)
|
||||
{ bool delivered = InterceptActive; ClearIntercept(InterceptOrder); if (delivered) LegDeadline = now + 600000; return false; }
|
||||
if (!InterceptActive) return false;
|
||||
if (RouteIndex != m_InterceptRouteIndex || InterceptWaypoint != m_InterceptWaypoint)
|
||||
{ ClearIntercept(InterceptOrder); return false; }
|
||||
if (m_InterceptWaypoint && vector.DistanceXZ(Entity.GetOrigin(), m_InterceptPosition) <= 15)
|
||||
{
|
||||
Group.RemoveWaypoint(m_InterceptWaypoint); SCR_EntityHelper.DeleteEntityAndChildren(m_InterceptWaypoint);
|
||||
m_InterceptWaypoint = null; InterceptWaypoint = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void ClearOrder()
|
||||
{
|
||||
if (Waypoint && Group) Group.RemoveWaypoint(Waypoint);
|
||||
if (Waypoint) SCR_EntityHelper.DeleteEntityAndChildren(Waypoint);
|
||||
Waypoint = null;
|
||||
}
|
||||
void Release(ERR_GameMode mode)
|
||||
{
|
||||
if (Released) return;
|
||||
ClearIntercept(InterceptOrder);
|
||||
Released = true;
|
||||
Combat.Held = false; SupplyToken = 0; SupplyHeld = false;
|
||||
ClearOrder();
|
||||
if (mode && mode.Forces())
|
||||
{
|
||||
ERR_ForceRecord record = mode.Forces().FindGroup(Group);
|
||||
if (record) record.Pinned = false;
|
||||
}
|
||||
if (Group)
|
||||
{
|
||||
ERR_HQ hq;
|
||||
if (mode) hq = ERR_HQ.Cast(mode.Command());
|
||||
bool retained = false;
|
||||
if (Registered && !PlayerCaptureObserved && hq && !ERR_ConvoyRules.Captured(Entity, Group)) retained = hq.ReleaseConvoySurvivors(Group);
|
||||
if (!retained && mode && mode.Command()) mode.Command().UnregisterGroup(Group);
|
||||
Group.SetPermanentLOD(-1);
|
||||
array<AIAgent> agents = {}; Group.GetAgents(agents);
|
||||
foreach (AIAgent agent : agents) if (agent) agent.SetPermanentLOD(-1);
|
||||
}
|
||||
// Vehicle, cargo, casualties and surviving crew remain physical and lootable.
|
||||
}
|
||||
bool CrewValid(ERR_GameMode mode)
|
||||
{
|
||||
if (!Group || !Entity || !ERR_Actors.Alive(Entity) || Group.GetFaction() != mode.EnemyFaction() || !ERR_Actors.CanFight(Driver)) return false;
|
||||
if (ERR_Actors.Player(Driver) || !ERR_ConvoyRules.PartOf(CompartmentAccessComponent.GetVehicleIn(Driver), Entity)) return false;
|
||||
foreach (IEntity member : Members)
|
||||
if (!ERR_Actors.Alive(member) || ERR_Actors.Player(member) || ERR_Actors.Group(member) != Group) return false;
|
||||
return true;
|
||||
}
|
||||
bool Order(ERR_ForceRoute route, float now)
|
||||
{
|
||||
if (Released || Arrived || InterceptActive || Combat.Held || SupplyHeld || !Group || !Entity || !route || RouteIndex >= route.Count()) return false;
|
||||
if (Waypoint) return true;
|
||||
Waypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), route.At(RouteIndex)));
|
||||
if (!Waypoint) return false;
|
||||
Waypoint.SetCompletionRadius(35);
|
||||
Group.AddWaypoint(Waypoint);
|
||||
LegDeadline = now + 600000;
|
||||
return true;
|
||||
}
|
||||
void Tick(ERR_GameMode mode, ERR_ForceRoute route, float now)
|
||||
{
|
||||
if (TickIntercept(mode, now)) return;
|
||||
if (Released || Arrived) return;
|
||||
if (!CrewValid(mode)) { Release(mode); return; }
|
||||
if (Combat.Held || SupplyHeld) return;
|
||||
// Native completion measures the seated group, not the vehicle origin.
|
||||
// Keep the same predicate after native removal of the completed waypoint.
|
||||
if (Waypoint && Waypoint.IsWithinCompletionRadius(Group))
|
||||
{
|
||||
ClearOrder(); RouteIndex++;
|
||||
if (RouteIndex >= route.Count()) { Arrived = true; return; }
|
||||
}
|
||||
if (Waypoint && now >= LegDeadline) { Release(mode); return; }
|
||||
if (!Order(route, now)) Release(mode);
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_ConvoyCargoCallback : ScriptedInventoryOperationCallback
|
||||
{
|
||||
ResourceName Prefab;
|
||||
bool Finished;
|
||||
bool Succeeded;
|
||||
protected void Finish(bool success) { if (!Finished) { Finished = true; Succeeded = success; } }
|
||||
protected override void OnComplete() { Finish(true); }
|
||||
protected override void OnFailed() { Finish(false); }
|
||||
protected override void OnAllComplete() { Finish(true); }
|
||||
protected override void OnAllFailed() { Finish(false); }
|
||||
}
|
||||
|
||||
class ERR_ConvoyTask
|
||||
{
|
||||
ref ERR_ConvoyReceipt Receipt;
|
||||
ref ERR_ForceRoute Route;
|
||||
ref array<ref ERR_ConvoyVehicle> Vehicles = {};
|
||||
ref array<ref ERR_ConvoyCargoCallback> Cargo = {};
|
||||
bool Stopped;
|
||||
bool Ready;
|
||||
bool CargoIssued;
|
||||
float Deadline;
|
||||
float NextCheck;
|
||||
string Reason;
|
||||
void Stop(ERR_GameMode mode, string reason)
|
||||
{
|
||||
if (Stopped) return;
|
||||
Stopped = true; Reason = reason;
|
||||
foreach (ERR_ConvoyVehicle vehicle : Vehicles) vehicle.Release(mode);
|
||||
ERR_HQ.Event("CONVOY STOP", reason + "; surviving entities and cargo preserved");
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_ConvoyService
|
||||
{
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected ERR_ConvoyPool m_Pool;
|
||||
protected ref array<ref ERR_ConvoyTask> m_Tasks = {};
|
||||
protected bool m_Stopped;
|
||||
void ERR_ConvoyService(ERR_GameMode mode, ERR_ConvoyPool pool) { m_Mode = mode; m_Pool = pool; }
|
||||
protected bool Current()
|
||||
{
|
||||
return !m_Stopped && Replication.IsServer() && m_Mode && m_Mode == ERR_GameMode.Get() && m_Mode.Active() && m_Pool;
|
||||
}
|
||||
protected ERR_ConvoyTask NewTask() { return new ERR_ConvoyTask(); }
|
||||
protected ERR_ConvoyVehicle NewVehicle() { return new ERR_ConvoyVehicle(); }
|
||||
ERR_ConvoyTask At(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_Tasks.Count()) return null;
|
||||
return m_Tasks[index];
|
||||
}
|
||||
ERR_ConvoyTask Find(string source)
|
||||
{
|
||||
foreach (ERR_ConvoyTask task : m_Tasks) if (task.Receipt && task.Receipt.SourceId == source) return task;
|
||||
return null;
|
||||
}
|
||||
protected bool FillCrew(ERR_ConvoyVehicle unit)
|
||||
{
|
||||
array<BaseCompartmentSlot> all = {};
|
||||
if (!ERR_ConvoyRules.CollectSeats(unit.Entity, all)) return false;
|
||||
array<BaseCompartmentSlot> drivers = {}, turrets = {}, cargo = {};
|
||||
foreach (BaseCompartmentSlot slot : all)
|
||||
{
|
||||
if (!slot.IsCompartmentAccessible() || slot.IsOccupied()) continue;
|
||||
if (slot.GetType() == ECompartmentType.PILOT) drivers.Insert(slot);
|
||||
else if (slot.GetType() == ECompartmentType.TURRET) turrets.Insert(slot);
|
||||
else if (slot.GetType() == ECompartmentType.CARGO) cargo.Insert(slot);
|
||||
}
|
||||
int requiredTurrets = 1;
|
||||
if (unit.Index == 1 || unit.Index == 2) requiredTurrets = 0;
|
||||
if (unit.Index == 4) requiredTurrets = 2;
|
||||
if (drivers.Count() != 1 || turrets.Count() < requiredTurrets || (unit.Index == 2 && cargo.Count() < 8)) return false;
|
||||
unit.Seats.Insert(drivers[0]);
|
||||
for (int i = 0; i < requiredTurrets; i++) unit.Seats.Insert(turrets[i]);
|
||||
if (unit.Index == 2) for (int i = 0; i < 8; i++) unit.Seats.Insert(cargo[i]);
|
||||
// Exact planned population is known before any character is created.
|
||||
if (unit.Seats.Count() != ERR_ConvoyPool.CrewCount(unit.Index)) return false;
|
||||
AIGroup nativeGroup = unit.Group;
|
||||
foreach (BaseCompartmentSlot seat : unit.Seats)
|
||||
{
|
||||
IEntity actor = seat.SpawnCharacterInCompartment(ERR_ConvoyResources.Soldier(), nativeGroup, ERR_ConvoyResources.GroupPrefab());
|
||||
if (actor) { unit.Members.Insert(actor); m_Mode.Own(actor); }
|
||||
if (!actor || nativeGroup != unit.Group) return false;
|
||||
}
|
||||
unit.Driver = unit.Members[0];
|
||||
return true;
|
||||
}
|
||||
ERR_ConvoyTask Dispatch(ERR_ConvoyReceipt receipt, array<vector> positions, array<vector> angles, ERR_ForceRoute route, float now)
|
||||
{
|
||||
if (!Current() || !ERR_ForceRules.Finite(now) || now < 0 || !ERR_ConvoyRules.Plan(positions, angles, route) || !ERR_ConvoyResources.Available() || !ERR_ConvoyRules.GroundClear(positions, angles)) return null;
|
||||
// The server role selection owns the memberless group affiliation.
|
||||
if (!m_Mode.EnemyFaction() || !m_Pool.Consume(receipt)) return null;
|
||||
ERR_ConvoyTask task = NewTask(); task.Receipt = receipt; task.Route = route; task.Deadline = now + 30000;
|
||||
m_Tasks.Insert(task);
|
||||
for (int index = 0; index < 6; index++)
|
||||
{
|
||||
ERR_ConvoyVehicle unit = NewVehicle(); unit.Index = index; task.Vehicles.Insert(unit);
|
||||
unit.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), positions[index]));
|
||||
if (unit.Group) m_Mode.Own(unit.Group);
|
||||
unit.Entity = Vehicle.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.VehiclePrefab(index), positions[index], angles[index]));
|
||||
if (unit.Entity) m_Mode.Own(unit.Entity);
|
||||
if (!unit.Group || !unit.Entity || unit.Group.GetFaction() != m_Mode.EnemyFaction() || !ERR_ConvoyRules.EmptyCrewTemplate(unit.Group) || !FillCrew(unit))
|
||||
{ task.Stop(m_Mode, "finite convoy startup failed; receipt consumed without retry"); return task; }
|
||||
FactionAffiliationComponent affiliation = FactionAffiliationComponent.Cast(unit.Entity.FindComponent(FactionAffiliationComponent));
|
||||
if (affiliation) affiliation.SetAffiliatedFaction(m_Mode.EnemyFaction());
|
||||
AICarMovementComponent movement = AICarMovementComponent.Cast(unit.Entity.FindComponent(AICarMovementComponent));
|
||||
if (movement) movement.SetCruiseSpeed(35);
|
||||
}
|
||||
ERR_HQ.Event("CONVOY CREATED", "six physical vehicles; finite crew created; native readiness and ledger admission pending");
|
||||
return task;
|
||||
}
|
||||
protected string ReadinessReason(ERR_ConvoyVehicle unit)
|
||||
{
|
||||
if (unit.Registered) return "already admitted";
|
||||
if (!unit.Group) return "missing group";
|
||||
if (!unit.Entity) return "missing vehicle";
|
||||
if (!ERR_Actors.Alive(unit.Entity)) return "vehicle destroyed or lacks damage state";
|
||||
if (unit.Group.GetFaction() != m_Mode.EnemyFaction()) return "group faction mismatch";
|
||||
if (!ERR_Actors.CanFight(unit.Driver)) return "driver absent/dead/unconscious or controller unavailable";
|
||||
if (ERR_Actors.Player(unit.Driver)) return "driver controlled by player";
|
||||
if (!ERR_ConvoyRules.PartOf(CompartmentAccessComponent.GetVehicleIn(unit.Driver), unit.Entity)) return "driver outside intended vehicle";
|
||||
foreach (IEntity member : unit.Members)
|
||||
{
|
||||
if (!ERR_Actors.Alive(member)) return "member absent/dead or damage state unavailable";
|
||||
if (ERR_Actors.Player(member)) return "member controlled by player";
|
||||
if (ERR_Actors.Group(member) != unit.Group) return "member native agent/parent group mismatch";
|
||||
}
|
||||
array<AIAgent> agents = {}; unit.Group.GetAgents(agents);
|
||||
if (agents.Count() != unit.Members.Count()) return "native agent count differs from original crew";
|
||||
foreach (AIAgent agent : agents)
|
||||
{
|
||||
if (!agent) return "null native agent";
|
||||
if (!unit.Members.Contains(agent.GetControlledEntity())) return "native agent controls foreign entity";
|
||||
if (!ERR_Actors.CanFight(agent.GetControlledEntity())) return "native agent actor cannot fight";
|
||||
}
|
||||
SCR_AIGroupUtilityComponent utility = unit.Group.GetGroupUtilityComponent();
|
||||
if (!utility) return "group utility unavailable";
|
||||
if (!utility.m_VehicleMgr) return "group vehicle manager unavailable";
|
||||
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(unit.Entity.FindComponent(SCR_AIVehicleUsageComponent));
|
||||
if (!usage) return "vehicle usage component unavailable";
|
||||
for (int i = 0; i < unit.Seats.Count(); i++)
|
||||
if (i >= unit.Members.Count() || !unit.Seats[i] || unit.Seats[i].GetOccupant() != unit.Members[i]) return "intended seat occupant mismatch";
|
||||
return "readiness guards satisfied; awaiting admission";
|
||||
}
|
||||
protected void InspectReadiness(ERR_ConvoyVehicle unit, float now)
|
||||
{
|
||||
if (!unit || now < unit.NextReadinessNote) return;
|
||||
unit.NextReadinessNote = now + 5000;
|
||||
int agents, activeAgents;
|
||||
bool activeGroup;
|
||||
if (unit.Group)
|
||||
{
|
||||
array<AIAgent> current = {}; unit.Group.GetAgents(current);
|
||||
agents = current.Count(); activeGroup = unit.Group.IsAIActivated();
|
||||
foreach (AIAgent agent : current) if (agent && agent.IsAIActivated()) activeAgents++;
|
||||
}
|
||||
ERR_HQ.Event("CONVOY READINESS", string.Format("vehicle=%1 reason=%2 members=%3 agents=%4 activeAgents=%5 activeGroup=%6 seats=%7 registered=%8", unit.Index, ReadinessReason(unit), unit.Members.Count(), agents, activeAgents, activeGroup, unit.Seats.Count(), unit.Registered));
|
||||
}
|
||||
protected bool Admit(ERR_ConvoyTask task, float now)
|
||||
{
|
||||
foreach (ERR_ConvoyVehicle observed : task.Vehicles) InspectReadiness(observed, now);
|
||||
foreach (ERR_ConvoyVehicle unit : task.Vehicles)
|
||||
{
|
||||
if (unit.Registered) continue;
|
||||
if (!unit.CrewValid(m_Mode)) return false;
|
||||
array<AIAgent> agents = {}; unit.Group.GetAgents(agents);
|
||||
if (agents.Count() != unit.Members.Count()) return false;
|
||||
foreach (AIAgent agent : agents)
|
||||
if (!agent || !unit.Members.Contains(agent.GetControlledEntity()) || !ERR_Actors.CanFight(agent.GetControlledEntity())) return false;
|
||||
SCR_AIGroupUtilityComponent utility = unit.Group.GetGroupUtilityComponent();
|
||||
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(unit.Entity.FindComponent(SCR_AIVehicleUsageComponent));
|
||||
if (!utility || !utility.m_VehicleMgr || !usage || !utility.m_VehicleMgr.TryAddVehicle(usage)) return false;
|
||||
ERR_ForceRecord record = m_Mode.Forces().Register(task.Receipt.SourceId + "-vehicle-" + unit.Index.ToString(), ERR_ForceKind.CONVOY, unit.Entity.GetOrigin(), unit.Members.Count(), null, unit.Group, now);
|
||||
if (!record || !m_Pool.BindRecord(task.Receipt, unit.Index, record)) { task.Stop(m_Mode, "convoy ledger admission rejected"); return false; }
|
||||
record.Pinned = true;
|
||||
unit.Registered = true;
|
||||
m_Mode.RegisterGroup(unit.Group);
|
||||
ERR_HQ hq = ERR_HQ.Cast(m_Mode.Command());
|
||||
if (hq)
|
||||
{
|
||||
hq.ReserveGroup(unit.Group);
|
||||
if (!hq.BindVehicle(unit.Group, unit.Entity))
|
||||
ERR_HQ.Event("MOUNTED AMMO", "original convoy vehicle binding unavailable; no replacement lookup");
|
||||
}
|
||||
// Native helper idempotently registers deletion/damage callbacks and mounted
|
||||
// combat activity; raw vehicle-manager admission alone omits those hooks.
|
||||
utility.AddUsableVehicle(usage);
|
||||
unit.Group.SetPermanentLOD(0); unit.Group.ActivateAI();
|
||||
foreach (AIAgent active : agents) { active.SetPermanentLOD(0); active.ActivateAI(); }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected void IssueCargo(ERR_ConvoyTask task)
|
||||
{
|
||||
if (task.CargoIssued || task.Stopped || task.Vehicles.Count() != 6) return;
|
||||
task.CargoIssued = true;
|
||||
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(task.Vehicles[1].Entity.FindComponent(InventoryStorageManagerComponent));
|
||||
if (!inventory) { ERR_HQ.Event("CONVOY CARGO", "ammo truck inventory unavailable; no retry or free replacement"); return; }
|
||||
for (int i = 0; i < 56; i++)
|
||||
{
|
||||
ResourceName prefab = ERR_ConvoyResources.Magazine();
|
||||
if (i < 8) prefab = ERR_ConvoyResources.Rifle();
|
||||
ERR_ConvoyCargoCallback operation = new ERR_ConvoyCargoCallback(); operation.Prefab = prefab; task.Cargo.Insert(operation);
|
||||
if (!inventory.TrySpawnPrefabToStorage(prefab, null, -1, EStoragePurpose.PURPOSE_DEPOSIT, operation)) operation.Finished = true;
|
||||
}
|
||||
ERR_HQ.Event("CONVOY CARGO", "finite target submitted once: eight AK74 and 48 magazines; native capacity/completion may reduce actual stock");
|
||||
}
|
||||
void Tick(float now)
|
||||
{
|
||||
if (!Current() || !ERR_ForceRules.Finite(now)) return;
|
||||
foreach (ERR_ConvoyTask task : m_Tasks)
|
||||
{
|
||||
if (task.Stopped || now < task.NextCheck) continue;
|
||||
task.NextCheck = now + 1000;
|
||||
bool captured;
|
||||
foreach (ERR_ConvoyVehicle unit : task.Vehicles) if (ERR_ConvoyRules.Captured(unit.Entity, unit.Group)) captured = true;
|
||||
if (captured) { task.Stop(m_Mode, "player captured convoy vehicle or joined crew"); continue; }
|
||||
if (!task.Ready)
|
||||
{
|
||||
if (now >= task.Deadline) { task.Stop(m_Mode, "native crew readiness expired; no replacement"); continue; }
|
||||
if (!Admit(task, now)) continue;
|
||||
task.Ready = true; IssueCargo(task);
|
||||
}
|
||||
bool pending;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
vehicle.Tick(m_Mode, task.Route, now);
|
||||
if (!vehicle.Released && !vehicle.Arrived) pending = true;
|
||||
}
|
||||
if (!pending) task.Stop(m_Mode, "finite authored road route ended or all vehicle orders released");
|
||||
}
|
||||
}
|
||||
protected bool InterceptEligible(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
if (!Current() || !task || !order || !m_Tasks.Contains(task) || task.Stopped || !task.Ready || !task.Receipt || !task.Receipt.Consumed) return false;
|
||||
if (!m_Mode.Forces()) return false;
|
||||
ERR_HQ hq = ERR_HQ.Cast(m_Mode.Command());
|
||||
if (!hq || hq.LogisticsToken(order.Group) != order.Token) return false;
|
||||
if (order.Token < 400001 || order.Token > 400256 || order.SourceId != task.Receipt.SourceId) return false;
|
||||
if (order.VehicleIndex != 0 && order.VehicleIndex != 3 && order.VehicleIndex != 5) return false;
|
||||
if (order.VehicleIndex >= task.Vehicles.Count() || order.VehicleIndex >= task.Receipt.Records.Count()) return false;
|
||||
ERR_ConvoyVehicle unit = task.Vehicles[order.VehicleIndex];
|
||||
if (!unit || !unit.Registered || unit.Released || unit.Arrived || unit.Index != order.VehicleIndex || unit.Group != order.Group) return false;
|
||||
if (!unit.InterceptCrew(m_Mode) || !task.Route || unit.RouteIndex < 0 || unit.RouteIndex >= task.Route.Count()) return false;
|
||||
ERR_ForceRecord record = m_Mode.Forces().FindGroup(unit.Group);
|
||||
if (!record || !record.Physical || record.Eliminated || record.Kind != ERR_ForceKind.CONVOY || record.Id != task.Receipt.Records[unit.Index]) return false;
|
||||
if (record.Group != unit.Group || record.SourceId != task.Receipt.SourceId + "-vehicle-" + unit.Index.ToString()) return false;
|
||||
if (unit.InterceptOrder && (unit.InterceptReceipt != task.Receipt || unit.InterceptRecord != record || unit.InterceptRoute != task.Route)) return false;
|
||||
foreach (ERR_ConvoyVehicle other : task.Vehicles)
|
||||
if (!other || other.SupplyToken != 0 || other.SupplyHeld || other.Combat.Held) return false;
|
||||
return true;
|
||||
}
|
||||
bool InterceptCurrent(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
return InterceptEligible(task, order) && task.Vehicles[order.VehicleIndex].InterceptToken == order.Token
|
||||
&& task.Vehicles[order.VehicleIndex].InterceptMatches(order);
|
||||
}
|
||||
bool ReserveIntercept(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!InterceptEligible(task, order) || !ERR_ForceRules.Finite(now) || now < 0) return false;
|
||||
if (!ERR_ForceRules.Finite(order.ObservedAt) || !ERR_ForceRules.Finite(order.IssuedAt) || !ERR_ForceRules.Finite(order.ExpiresAt)) return false;
|
||||
if (order.ObservedAt < 0 || order.IssuedAt < order.ObservedAt || now < order.IssuedAt || now >= order.ExpiresAt) return false;
|
||||
if (order.ExpiresAt > order.ObservedAt + 120000 || !ERR_ForceRules.FiniteVector(order.Position)) return false;
|
||||
if (order.BeliefId <= 0 || order.BeliefRevision <= 0 || order.ReportId <= 0) return false;
|
||||
foreach (ERR_ConvoyTask existing : m_Tasks)
|
||||
foreach (ERR_ConvoyVehicle claimed : existing.Vehicles) if (claimed && claimed.InterceptToken != 0) return false;
|
||||
ERR_ConvoyVehicle unit = task.Vehicles[order.VehicleIndex];
|
||||
if (!unit.ClaimIntercept(m_Mode, order)) return false;
|
||||
unit.InterceptReceipt = task.Receipt; unit.InterceptRecord = m_Mode.Forces().FindGroup(unit.Group); unit.InterceptRoute = task.Route;
|
||||
return true;
|
||||
}
|
||||
protected bool InterceptPairEligible(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder first, ERR_ConvoyInterceptOrder second, float now)
|
||||
{
|
||||
if (!first || !second || first == second || first.Group == second.Group || first.VehicleIndex == second.VehicleIndex || first.Token == second.Token) return false;
|
||||
if (!InterceptEligible(task, first) || !InterceptEligible(task, second) || !ERR_ForceRules.Finite(now) || now < 0) return false;
|
||||
if (task.Vehicles[first.VehicleIndex].Entity == task.Vehicles[second.VehicleIndex].Entity) return false;
|
||||
if (first.BeliefId <= 0 || first.BeliefRevision <= 0 || first.ReportId <= 0) return false;
|
||||
if (first.BeliefId != second.BeliefId || first.BeliefRevision != second.BeliefRevision || first.ReportId != second.ReportId) return false;
|
||||
if (first.ObservedAt != second.ObservedAt || first.IssuedAt != second.IssuedAt || first.ExpiresAt != second.ExpiresAt) return false;
|
||||
if (!ERR_ForceRules.Finite(first.ObservedAt) || !ERR_ForceRules.Finite(first.IssuedAt) || !ERR_ForceRules.Finite(first.ExpiresAt)) return false;
|
||||
if (first.ObservedAt < 0 || first.IssuedAt < first.ObservedAt || now < first.IssuedAt || now >= first.ExpiresAt || first.ExpiresAt > first.ObservedAt + 120000) return false;
|
||||
if (!ERR_ForceRules.FiniteVector(first.Position) || !ERR_ForceRules.FiniteVector(second.Position)) return false;
|
||||
return vector.DistanceXZ(first.Position, second.Position) >= 40;
|
||||
}
|
||||
bool ReserveInterceptPair(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder first, ERR_ConvoyInterceptOrder second, float now)
|
||||
{
|
||||
if (!InterceptPairEligible(task, first, second, now)) return false;
|
||||
foreach (ERR_ConvoyTask existing : m_Tasks)
|
||||
foreach (ERR_ConvoyVehicle claimed : existing.Vehicles) if (claimed && claimed.InterceptToken != 0) return false;
|
||||
ERR_ConvoyVehicle lead = task.Vehicles[first.VehicleIndex];
|
||||
ERR_ConvoyVehicle partner = task.Vehicles[second.VehicleIndex];
|
||||
if (!lead.ClaimIntercept(m_Mode, first)) return false;
|
||||
lead.InterceptReceipt = task.Receipt; lead.InterceptRecord = m_Mode.Forces().FindGroup(lead.Group); lead.InterceptRoute = task.Route;
|
||||
if (!InterceptPairEligible(task, first, second, now) || !partner.ClaimIntercept(m_Mode, second))
|
||||
{ return false; }
|
||||
partner.InterceptReceipt = task.Receipt; partner.InterceptRecord = m_Mode.Forces().FindGroup(partner.Group); partner.InterceptRoute = task.Route;
|
||||
return true;
|
||||
}
|
||||
bool ApplyInterceptPair(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder first, ERR_ConvoyInterceptOrder second, float now)
|
||||
{
|
||||
if (!InterceptPairEligible(task, first, second, now) || !InterceptCurrent(task, first) || !InterceptCurrent(task, second)) return false;
|
||||
ERR_ConvoyVehicle lead = task.Vehicles[first.VehicleIndex];
|
||||
ERR_ConvoyVehicle partner = task.Vehicles[second.VehicleIndex];
|
||||
if (!lead.CanInstallIntercept(m_Mode, first, now) || !partner.CanInstallIntercept(m_Mode, second, now)) return false;
|
||||
AIWaypoint firstWaypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), first.Position));
|
||||
if (!firstWaypoint) return false;
|
||||
AIWaypoint secondWaypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), second.Position));
|
||||
if (!secondWaypoint) { SCR_EntityHelper.DeleteEntityAndChildren(firstWaypoint); return false; }
|
||||
firstWaypoint.SetCompletionRadius(15); secondWaypoint.SetCompletionRadius(15);
|
||||
if (!InterceptPairEligible(task, first, second, now) || !InterceptCurrent(task, first) || !InterceptCurrent(task, second)
|
||||
|| task.Vehicles[first.VehicleIndex] != lead || task.Vehicles[second.VehicleIndex] != partner
|
||||
|| !lead.CanInstallIntercept(m_Mode, first, now) || !partner.CanInstallIntercept(m_Mode, second, now))
|
||||
{ SCR_EntityHelper.DeleteEntityAndChildren(firstWaypoint); SCR_EntityHelper.DeleteEntityAndChildren(secondWaypoint); return false; }
|
||||
// Route removal is also a native boundary; both prepared handles remain
|
||||
// caller-owned until both exact claims have survived it.
|
||||
lead.ClearOrder();
|
||||
if (InterceptPairEligible(task, first, second, now) && InterceptCurrent(task, first) && InterceptCurrent(task, second)) partner.ClearOrder();
|
||||
if (!InterceptPairEligible(task, first, second, now) || !InterceptCurrent(task, first) || !InterceptCurrent(task, second)
|
||||
|| task.Vehicles[first.VehicleIndex] != lead || task.Vehicles[second.VehicleIndex] != partner
|
||||
|| !lead.CanInstallIntercept(m_Mode, first, now) || !partner.CanInstallIntercept(m_Mode, second, now))
|
||||
{
|
||||
SCR_EntityHelper.DeleteEntityAndChildren(firstWaypoint); SCR_EntityHelper.DeleteEntityAndChildren(secondWaypoint);
|
||||
ReleaseIntercept(task, first, now); ReleaseIntercept(task, second, now); return false;
|
||||
}
|
||||
lead.InstallIntercept(firstWaypoint);
|
||||
// AddWaypoint can invoke native listeners. Do not install the partner after
|
||||
// cancellation/ownership loss; release only these exact owned claims.
|
||||
if (!InterceptPairEligible(task, first, second, now) || !InterceptCurrent(task, first) || !lead.InterceptActive
|
||||
|| !InterceptCurrent(task, second) || task.Vehicles[first.VehicleIndex] != lead || task.Vehicles[second.VehicleIndex] != partner
|
||||
|| !partner.CanInstallIntercept(m_Mode, second, now))
|
||||
{
|
||||
SCR_EntityHelper.DeleteEntityAndChildren(secondWaypoint);
|
||||
ReleaseIntercept(task, first, now); ReleaseIntercept(task, second, now); return false;
|
||||
}
|
||||
partner.InstallIntercept(secondWaypoint);
|
||||
if (InterceptPairEligible(task, first, second, now) && InterceptCurrent(task, first) && InterceptCurrent(task, second)
|
||||
&& task.Vehicles[first.VehicleIndex] == lead && task.Vehicles[second.VehicleIndex] == partner
|
||||
&& lead.InterceptActive && partner.InterceptActive) return true;
|
||||
ReleaseIntercept(task, first, now); ReleaseIntercept(task, second, now); return false;
|
||||
}
|
||||
bool ApplyIntercept(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!InterceptCurrent(task, order) || !ERR_ForceRules.Finite(now) || now < order.IssuedAt || now >= order.ExpiresAt) return false;
|
||||
return task.Vehicles[order.VehicleIndex].DeliverIntercept(m_Mode, order, now);
|
||||
}
|
||||
void ReleaseIntercept(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!task || !order || !m_Tasks.Contains(task) || !ERR_ForceRules.Finite(now)) return;
|
||||
foreach (ERR_ConvoyVehicle unit : task.Vehicles)
|
||||
if (unit && unit.InterceptMatches(order))
|
||||
{ bool delivered = unit.InterceptActive; unit.ClearIntercept(order); if (delivered) unit.LegDeadline = now + 600000; }
|
||||
}
|
||||
bool ApplyCombatOrder(ERR_ConvoyTask task, ERR_ConvoyCombatOrder order, float now)
|
||||
{
|
||||
if (!Current() || !task || !order || !m_Tasks.Contains(task) || task.Stopped || !task.Ready || !task.Receipt || !task.Receipt.Consumed) return false;
|
||||
foreach (ERR_ConvoyVehicle intercept : task.Vehicles) if (intercept && intercept.InterceptToken != 0) return false;
|
||||
if (order.VehicleIndex < 0 || order.VehicleIndex >= task.Vehicles.Count() || order.SourceId != task.Receipt.SourceId) return false;
|
||||
ERR_ConvoyVehicle unit = task.Vehicles[order.VehicleIndex];
|
||||
if (!unit || !unit.Registered || unit.Released || unit.Arrived || unit.Index != order.VehicleIndex || unit.Group != order.Group || !unit.CrewValid(m_Mode)) return false;
|
||||
if (ERR_ConvoyRules.Captured(unit.Entity, unit.Group) || unit.Index >= task.Receipt.Records.Count()) return false;
|
||||
if (!ERR_ConvoyCombatRules.Accept(unit.Combat, order, now) || !task.Route || unit.RouteIndex >= task.Route.Count()) return false;
|
||||
ERR_ForceRecord record = m_Mode.Forces().FindGroup(unit.Group);
|
||||
if (!record || !record.Physical || record.Eliminated || record.Kind != ERR_ForceKind.CONVOY || record.Id != task.Receipt.Records[unit.Index]) return false;
|
||||
array<AIAgent> agents = {}; unit.Group.GetAgents(agents);
|
||||
if (agents.Count() != unit.Members.Count()) return false;
|
||||
foreach (AIAgent agent : agents)
|
||||
if (!agent || !unit.Members.Contains(agent.GetControlledEntity())) return false;
|
||||
// No caller can replay even a command whose native waypoint recreation fails.
|
||||
unit.Combat.LastCommandId = order.CommandId;
|
||||
if (order.Hold)
|
||||
{
|
||||
if (!unit.Combat.Held) unit.ClearOrder();
|
||||
unit.Combat.Held = true; unit.Combat.ReportId = order.ReportId; unit.Combat.ObservedAt = order.ObservedAt;
|
||||
}
|
||||
else
|
||||
{
|
||||
unit.Combat.Held = false;
|
||||
if (!unit.SupplyHeld && !unit.Order(task.Route, now)) { unit.Release(m_Mode); return false; }
|
||||
}
|
||||
ERR_HQ.Event("CONVOY COVER", string.Format("command M%1 report M%2 vehicle=%3 held=%4 routeIndex=%5", order.CommandId, order.ReportId, unit.Index, unit.Combat.Held, unit.RouteIndex));
|
||||
return true;
|
||||
}
|
||||
int Count() { return m_Tasks.Count(); }
|
||||
string Status()
|
||||
{
|
||||
int ready, stopped, vehicles, completed, failed, pending;
|
||||
foreach (ERR_ConvoyTask task : m_Tasks)
|
||||
{
|
||||
if (task.Ready) ready++;
|
||||
if (task.Stopped) stopped++;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles) if (vehicle && vehicle.Entity) vehicles++;
|
||||
foreach (ERR_ConvoyCargoCallback operation : task.Cargo)
|
||||
{
|
||||
if (!operation.Finished) pending++;
|
||||
else if (operation.Succeeded) completed++;
|
||||
else failed++;
|
||||
}
|
||||
}
|
||||
string reason;
|
||||
if (m_Tasks.Count() == 1 && m_Tasks[0].Stopped) reason = m_Tasks[0].Reason;
|
||||
if (reason.Length() > 160) reason = reason.Substring(0, 160);
|
||||
return string.Format("Convoys: %1 tasks, %2 reached readiness, %3 stopped, %4 retained physical vehicles; cargo native insertion successes=%5 failed=%6 pending=%7; current inventory may differ after use/looting", m_Tasks.Count(), ready, stopped, vehicles, completed, failed, pending) + "; reason=" + reason;
|
||||
}
|
||||
void Stop()
|
||||
{
|
||||
if (m_Stopped) return;
|
||||
m_Stopped = true;
|
||||
foreach (ERR_ConvoyTask task : m_Tasks) task.Stop(m_Mode, "HQ ended convoy task service");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
check: /tmp/err-policy-gxipern3/check.cpp:408: int main(): Assertion `!f.ReservePair()&&!f.unit.InterceptToken&&!f.b.unit.InterceptToken&&f.unit.Waypoint==a&&f.b.unit.Waypoint==b' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-pair-service-0054/tests/test_convoy_intercept_pair.py", line 120, in <module>
|
||||
h.run(cpp)
|
||||
~~~~~^^^^^
|
||||
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
|
||||
subprocess.run([str(p/'check')],check=True)
|
||||
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/usr/lib/python3.14/subprocess.py", line 578, in run
|
||||
raise CalledProcessError(retcode, process.args,
|
||||
output=stdout, stderr=stderr)
|
||||
subprocess.CalledProcessError: Command '['/tmp/err-policy-gxipern3/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1,717 @@
|
||||
// Independent physical adaptation of the owner's supply-convoy extension.
|
||||
// HQ must dispatch an authored route using a finite canonical pool receipt.
|
||||
class ERR_ConvoyResources
|
||||
{
|
||||
static ResourceName VehiclePrefab(int index)
|
||||
{
|
||||
if (index == 1) return "{3336BE330C4F355B}Prefabs/Vehicles/Wheeled/Ural4320/Ural4320_ammo.et";
|
||||
if (index == 2) return "{16C1F16C9B053801}Prefabs/Vehicles/Wheeled/Ural4320/Ural4320_transport.et";
|
||||
if (index == 4) return "{1DC01AAACFFA8B63}Prefabs/Vehicles/Tracked/BMP1/BMP1_Base.et";
|
||||
if (index == 0 || index == 3 || index == 5) return "{0B4DEA8078B78A9B}Prefabs/Vehicles/Wheeled/UAZ469/UAZ469_PKM.et";
|
||||
return "";
|
||||
}
|
||||
static ResourceName GroupPrefab() { return "{7A968AB297475FA7}Prefabs/ERR/ConvoyCrew.et"; }
|
||||
static ResourceName Soldier()
|
||||
{
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
if (mode && mode.FactionSelection() && mode.FactionSelection().Ready() && mode.FactionSelection().Active("enemy") != "ussr") return mode.FactionSelection().Soldier();
|
||||
return "{DCB41B3746FDD1BE}Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_Rifleman.et"; }
|
||||
static ResourceName Rifle() { return "{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et"; }
|
||||
static ResourceName Magazine() { return "{BBB50A815A2F916B}Prefabs/Weapons/Magazines/Magazine_545x39_AK_30rnd_Ball.et"; }
|
||||
static bool Available()
|
||||
{
|
||||
array<ResourceName> required = {GroupPrefab(), Soldier(), Rifle(), Magazine(), ERR_Resources.MoveWaypoint()};
|
||||
for (int i = 0; i < 6; i++) required.Insert(VehiclePrefab(i));
|
||||
foreach (ResourceName prefab : required)
|
||||
{
|
||||
Resource resource = Resource.Load(prefab);
|
||||
if (!resource || !resource.IsValid()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_ConvoyRules
|
||||
{
|
||||
static bool EmptyCrewTemplate(SCR_AIGroup group)
|
||||
{
|
||||
// The vanilla faction base authors six queued soldiers. Only our memberless
|
||||
// template may receive finite seat occupants; never retire another spawn job.
|
||||
return group && !group.GetSpawnImmediately() && group.GetAgentsCount() == 0
|
||||
&& (!group.m_aUnitPrefabSlots || group.m_aUnitPrefabSlots.IsEmpty());
|
||||
}
|
||||
static bool Plan(array<vector> positions, array<vector> angles, ERR_ForceRoute route)
|
||||
{
|
||||
if (!positions || !angles || positions.Count() != 6 || angles.Count() != 6 || !route || route.Looped() || route.Count() < 2 || !route.Seal()) return false;
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
if (!ERR_ForceRules.FiniteVector(positions[i]) || !ERR_ForceRules.FiniteVector(angles[i])) return false;
|
||||
for (int j = 0; j < i; j++) if (vector.DistanceXZ(positions[i], positions[j]) < 18) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static bool GroundClear(array<vector> positions, array<vector> angles)
|
||||
{
|
||||
if (!positions || !angles || positions.Count() != 6 || angles.Count() != 6) return false;
|
||||
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
|
||||
for (int i = 0; i < positions.Count(); i++) if (!placement.Clear(positions[i], angles[i][0])) return false;
|
||||
return true;
|
||||
}
|
||||
static bool PartOf(IEntity entity, IEntity root)
|
||||
{
|
||||
int depth;
|
||||
while (entity && depth++ < 32)
|
||||
{
|
||||
if (entity == root) return true;
|
||||
entity = entity.GetParent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static bool Captured(Vehicle vehicle, SCR_AIGroup group)
|
||||
{
|
||||
if (group && group.GetPlayerCount(true) > 0) return true;
|
||||
if (!vehicle) return false;
|
||||
array<int> ids = {}; GetGame().GetPlayerManager().GetPlayers(ids);
|
||||
foreach (int id : ids)
|
||||
{
|
||||
IEntity player = GetGame().GetPlayerManager().GetPlayerControlledEntity(id);
|
||||
if (player && PartOf(CompartmentAccessComponent.GetVehicleIn(player), vehicle)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static bool CollectSeats(IEntity root, array<BaseCompartmentSlot> seats)
|
||||
{
|
||||
array<IEntity> pending = {root};
|
||||
int cursor;
|
||||
while (cursor < pending.Count() && cursor < 128)
|
||||
{
|
||||
IEntity entity = pending[cursor++];
|
||||
if (!entity) return false;
|
||||
SCR_BaseCompartmentManagerComponent manager = SCR_BaseCompartmentManagerComponent.Cast(entity.FindComponent(SCR_BaseCompartmentManagerComponent));
|
||||
if (manager)
|
||||
{
|
||||
array<BaseCompartmentSlot> local = {}; manager.GetCompartments(local);
|
||||
foreach (BaseCompartmentSlot seat : local) if (seat && !seats.Contains(seat)) seats.Insert(seat);
|
||||
}
|
||||
IEntity child = entity.GetChildren();
|
||||
while (child)
|
||||
{
|
||||
if (pending.Count() >= 128 || pending.Contains(child)) return false;
|
||||
pending.Insert(child); child = child.GetSibling();
|
||||
}
|
||||
}
|
||||
return cursor == pending.Count();
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_ConvoyVehicle
|
||||
{
|
||||
int Index;
|
||||
Vehicle Entity;
|
||||
SCR_AIGroup Group;
|
||||
IEntity Driver;
|
||||
ref array<IEntity> Members = {};
|
||||
ref array<BaseCompartmentSlot> Seats = {};
|
||||
AIWaypoint Waypoint;
|
||||
int RouteIndex;
|
||||
bool Released;
|
||||
bool Registered;
|
||||
// Latched only for an exact tracked hunter at authoritative player entry.
|
||||
bool PlayerCaptureObserved;
|
||||
ref ERR_ConvoyCombatState Combat = new ERR_ConvoyCombatState();
|
||||
int SupplyToken;
|
||||
bool SupplyHeld;
|
||||
int InterceptToken;
|
||||
bool InterceptActive;
|
||||
AIWaypoint InterceptWaypoint;
|
||||
ref ERR_ConvoyInterceptOrder InterceptOrder;
|
||||
ERR_ConvoyReceipt InterceptReceipt;
|
||||
ERR_ForceRecord InterceptRecord;
|
||||
ERR_ForceRoute InterceptRoute;
|
||||
protected AIWaypoint m_InterceptWaypoint;
|
||||
protected Vehicle m_InterceptEntity;
|
||||
protected SCR_AIGroup m_InterceptGroup;
|
||||
protected IEntity m_InterceptDriver;
|
||||
protected ref array<IEntity> m_InterceptMembers = {};
|
||||
protected ref array<BaseCompartmentSlot> m_InterceptSeats = {};
|
||||
protected int m_InterceptToken;
|
||||
protected int m_LastInterceptToken;
|
||||
protected vector m_InterceptPosition;
|
||||
protected float m_InterceptExpiresAt;
|
||||
protected int m_InterceptRouteIndex;
|
||||
|
||||
bool Arrived;
|
||||
float LegDeadline;
|
||||
float NextReadinessNote;
|
||||
|
||||
// Pending downlink owns a claim only; normal authored travel continues.
|
||||
bool InterceptCrew(ERR_GameMode mode)
|
||||
{
|
||||
if (!mode || !CrewValid(mode) || !Entity || !Group || ERR_ConvoyRules.Captured(Entity, Group)) return false;
|
||||
if (Members.IsEmpty() || Members.Count() > 8 || Seats.Count() != Members.Count()) return false;
|
||||
if (InterceptOrder && (Members.Count() != m_InterceptMembers.Count() || Seats.Count() != m_InterceptSeats.Count())) return false;
|
||||
array<AIAgent> agents = {}; Group.GetAgents(agents);
|
||||
if (agents.Count() != Members.Count()) return false;
|
||||
for (int i = 0; i < Members.Count(); i++)
|
||||
{
|
||||
if (!Members[i] || !ERR_Actors.CanFight(Members[i]) || !Seats[i] || Seats[i].GetOccupant() != Members[i]) return false;
|
||||
int attributed;
|
||||
foreach (AIAgent agent : agents)
|
||||
{
|
||||
if (!agent || !Members.Contains(agent.GetControlledEntity())) return false;
|
||||
if (agent.GetControlledEntity() == Members[i]) attributed++;
|
||||
}
|
||||
if (attributed != 1) return false;
|
||||
if (InterceptOrder && (Members[i] != m_InterceptMembers[i] || Seats[i] != m_InterceptSeats[i])) return false;
|
||||
}
|
||||
if (InterceptOrder && (Entity != m_InterceptEntity || Group != m_InterceptGroup || Driver != m_InterceptDriver)) return false;
|
||||
return true;
|
||||
}
|
||||
bool InterceptMatches(ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
return order && InterceptOrder == order && InterceptToken == m_InterceptToken && m_InterceptToken != 0;
|
||||
}
|
||||
bool ClaimIntercept(ERR_GameMode mode, ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
if (!order || order.Token <= m_LastInterceptToken || InterceptToken != 0 || InterceptOrder || InterceptWaypoint || !InterceptCrew(mode)) return false;
|
||||
m_InterceptEntity = Entity; m_InterceptGroup = Group; m_InterceptDriver = Driver;
|
||||
m_InterceptMembers.Clear(); m_InterceptSeats.Clear();
|
||||
foreach (IEntity member : Members) m_InterceptMembers.Insert(member);
|
||||
foreach (BaseCompartmentSlot seat : Seats) m_InterceptSeats.Insert(seat);
|
||||
m_InterceptToken = order.Token; m_LastInterceptToken = order.Token; InterceptToken = order.Token; InterceptOrder = order;
|
||||
m_InterceptPosition = order.Position; m_InterceptExpiresAt = order.ExpiresAt;
|
||||
return true;
|
||||
}
|
||||
void ClearIntercept(ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
if (!InterceptMatches(order)) return;
|
||||
// Delete only the exact waypoint this owner created, never an adopted handle.
|
||||
if (m_InterceptWaypoint && m_InterceptGroup) m_InterceptGroup.RemoveWaypoint(m_InterceptWaypoint);
|
||||
if (m_InterceptWaypoint) SCR_EntityHelper.DeleteEntityAndChildren(m_InterceptWaypoint);
|
||||
if (InterceptWaypoint == m_InterceptWaypoint) InterceptWaypoint = null;
|
||||
m_InterceptWaypoint = null; InterceptToken = 0; m_InterceptToken = 0;
|
||||
InterceptActive = false; InterceptOrder = null; InterceptReceipt = null; InterceptRecord = null; InterceptRoute = null;
|
||||
m_InterceptMembers.Clear(); m_InterceptSeats.Clear();
|
||||
}
|
||||
// Pair preparation owns no route state. Recheck after both native allocations.
|
||||
bool CanInstallIntercept(ERR_GameMode mode, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!InterceptMatches(order) || order.Token != m_InterceptToken || InterceptActive || !InterceptCrew(mode)) return false;
|
||||
return !(order.Position != m_InterceptPosition) && order.ExpiresAt == m_InterceptExpiresAt && now < m_InterceptExpiresAt;
|
||||
}
|
||||
// Caller transfers ownership of the prepared handle before AddWaypoint, which
|
||||
// is a native boundary rather than a transactional pair operation.
|
||||
void InstallIntercept(AIWaypoint detour)
|
||||
{
|
||||
m_InterceptRouteIndex = RouteIndex;
|
||||
m_InterceptWaypoint = detour; InterceptWaypoint = detour; InterceptActive = true;
|
||||
Group.AddWaypoint(detour);
|
||||
}
|
||||
bool DeliverIntercept(ERR_GameMode mode, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!InterceptMatches(order) || order.Token != m_InterceptToken || InterceptActive || !InterceptCrew(mode)) return false;
|
||||
if (order.Position != m_InterceptPosition || order.ExpiresAt != m_InterceptExpiresAt || now >= m_InterceptExpiresAt) return false;
|
||||
AIWaypoint detour = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), m_InterceptPosition));
|
||||
if (!detour) return false;
|
||||
detour.SetCompletionRadius(15);
|
||||
ClearOrder(); m_InterceptRouteIndex = RouteIndex;
|
||||
m_InterceptWaypoint = detour; InterceptWaypoint = detour; InterceptActive = true;
|
||||
Group.AddWaypoint(detour);
|
||||
return true;
|
||||
}
|
||||
// True suppresses ordinary travel only after actual radio delivery. No route
|
||||
// index is consumed by arrival at the detour; removal leaves the seated group held.
|
||||
bool TickIntercept(ERR_GameMode mode, float now)
|
||||
{
|
||||
if (!InterceptOrder) return false;
|
||||
if (!InterceptMatches(InterceptOrder)) return InterceptActive;
|
||||
if (Released || Arrived || now >= m_InterceptExpiresAt || !InterceptCrew(mode) || Combat.Held || SupplyHeld || SupplyToken != 0)
|
||||
{ bool delivered = InterceptActive; ClearIntercept(InterceptOrder); if (delivered) LegDeadline = now + 600000; return false; }
|
||||
if (!InterceptActive) return false;
|
||||
if (RouteIndex != m_InterceptRouteIndex || InterceptWaypoint != m_InterceptWaypoint)
|
||||
{ ClearIntercept(InterceptOrder); return false; }
|
||||
if (m_InterceptWaypoint && vector.DistanceXZ(Entity.GetOrigin(), m_InterceptPosition) <= 15)
|
||||
{
|
||||
Group.RemoveWaypoint(m_InterceptWaypoint); SCR_EntityHelper.DeleteEntityAndChildren(m_InterceptWaypoint);
|
||||
m_InterceptWaypoint = null; InterceptWaypoint = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void ClearOrder()
|
||||
{
|
||||
if (Waypoint && Group) Group.RemoveWaypoint(Waypoint);
|
||||
if (Waypoint) SCR_EntityHelper.DeleteEntityAndChildren(Waypoint);
|
||||
Waypoint = null;
|
||||
}
|
||||
void Release(ERR_GameMode mode)
|
||||
{
|
||||
if (Released) return;
|
||||
ClearIntercept(InterceptOrder);
|
||||
Released = true;
|
||||
Combat.Held = false; SupplyToken = 0; SupplyHeld = false;
|
||||
ClearOrder();
|
||||
if (mode && mode.Forces())
|
||||
{
|
||||
ERR_ForceRecord record = mode.Forces().FindGroup(Group);
|
||||
if (record) record.Pinned = false;
|
||||
}
|
||||
if (Group)
|
||||
{
|
||||
ERR_HQ hq;
|
||||
if (mode) hq = ERR_HQ.Cast(mode.Command());
|
||||
bool retained = false;
|
||||
if (Registered && !PlayerCaptureObserved && hq && !ERR_ConvoyRules.Captured(Entity, Group)) retained = hq.ReleaseConvoySurvivors(Group);
|
||||
if (!retained && mode && mode.Command()) mode.Command().UnregisterGroup(Group);
|
||||
Group.SetPermanentLOD(-1);
|
||||
array<AIAgent> agents = {}; Group.GetAgents(agents);
|
||||
foreach (AIAgent agent : agents) if (agent) agent.SetPermanentLOD(-1);
|
||||
}
|
||||
// Vehicle, cargo, casualties and surviving crew remain physical and lootable.
|
||||
}
|
||||
bool CrewValid(ERR_GameMode mode)
|
||||
{
|
||||
if (!Group || !Entity || !ERR_Actors.Alive(Entity) || Group.GetFaction() != mode.EnemyFaction() || !ERR_Actors.CanFight(Driver)) return false;
|
||||
if (ERR_Actors.Player(Driver) || !ERR_ConvoyRules.PartOf(CompartmentAccessComponent.GetVehicleIn(Driver), Entity)) return false;
|
||||
foreach (IEntity member : Members)
|
||||
if (!ERR_Actors.Alive(member) || ERR_Actors.Player(member) || ERR_Actors.Group(member) != Group) return false;
|
||||
return true;
|
||||
}
|
||||
bool Order(ERR_ForceRoute route, float now)
|
||||
{
|
||||
if (Released || Arrived || InterceptActive || Combat.Held || SupplyHeld || !Group || !Entity || !route || RouteIndex >= route.Count()) return false;
|
||||
if (Waypoint) return true;
|
||||
Waypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), route.At(RouteIndex)));
|
||||
if (!Waypoint) return false;
|
||||
Waypoint.SetCompletionRadius(35);
|
||||
Group.AddWaypoint(Waypoint);
|
||||
LegDeadline = now + 600000;
|
||||
return true;
|
||||
}
|
||||
void Tick(ERR_GameMode mode, ERR_ForceRoute route, float now)
|
||||
{
|
||||
if (TickIntercept(mode, now)) return;
|
||||
if (Released || Arrived) return;
|
||||
if (!CrewValid(mode)) { Release(mode); return; }
|
||||
if (Combat.Held || SupplyHeld) return;
|
||||
// Native completion measures the seated group, not the vehicle origin.
|
||||
// Keep the same predicate after native removal of the completed waypoint.
|
||||
if (Waypoint && Waypoint.IsWithinCompletionRadius(Group))
|
||||
{
|
||||
ClearOrder(); RouteIndex++;
|
||||
if (RouteIndex >= route.Count()) { Arrived = true; return; }
|
||||
}
|
||||
if (Waypoint && now >= LegDeadline) { Release(mode); return; }
|
||||
if (!Order(route, now)) Release(mode);
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_ConvoyCargoCallback : ScriptedInventoryOperationCallback
|
||||
{
|
||||
ResourceName Prefab;
|
||||
bool Finished;
|
||||
bool Succeeded;
|
||||
protected void Finish(bool success) { if (!Finished) { Finished = true; Succeeded = success; } }
|
||||
protected override void OnComplete() { Finish(true); }
|
||||
protected override void OnFailed() { Finish(false); }
|
||||
protected override void OnAllComplete() { Finish(true); }
|
||||
protected override void OnAllFailed() { Finish(false); }
|
||||
}
|
||||
|
||||
class ERR_ConvoyTask
|
||||
{
|
||||
ref ERR_ConvoyReceipt Receipt;
|
||||
ref ERR_ForceRoute Route;
|
||||
ref array<ref ERR_ConvoyVehicle> Vehicles = {};
|
||||
ref array<ref ERR_ConvoyCargoCallback> Cargo = {};
|
||||
bool Stopped;
|
||||
bool Ready;
|
||||
bool CargoIssued;
|
||||
float Deadline;
|
||||
float NextCheck;
|
||||
string Reason;
|
||||
void Stop(ERR_GameMode mode, string reason)
|
||||
{
|
||||
if (Stopped) return;
|
||||
Stopped = true; Reason = reason;
|
||||
foreach (ERR_ConvoyVehicle vehicle : Vehicles) vehicle.Release(mode);
|
||||
ERR_HQ.Event("CONVOY STOP", reason + "; surviving entities and cargo preserved");
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_ConvoyService
|
||||
{
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected ERR_ConvoyPool m_Pool;
|
||||
protected ref array<ref ERR_ConvoyTask> m_Tasks = {};
|
||||
protected bool m_Stopped;
|
||||
void ERR_ConvoyService(ERR_GameMode mode, ERR_ConvoyPool pool) { m_Mode = mode; m_Pool = pool; }
|
||||
protected bool Current()
|
||||
{
|
||||
return !m_Stopped && Replication.IsServer() && m_Mode && m_Mode == ERR_GameMode.Get() && m_Mode.Active() && m_Pool;
|
||||
}
|
||||
protected ERR_ConvoyTask NewTask() { return new ERR_ConvoyTask(); }
|
||||
protected ERR_ConvoyVehicle NewVehicle() { return new ERR_ConvoyVehicle(); }
|
||||
ERR_ConvoyTask At(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_Tasks.Count()) return null;
|
||||
return m_Tasks[index];
|
||||
}
|
||||
ERR_ConvoyTask Find(string source)
|
||||
{
|
||||
foreach (ERR_ConvoyTask task : m_Tasks) if (task.Receipt && task.Receipt.SourceId == source) return task;
|
||||
return null;
|
||||
}
|
||||
protected bool FillCrew(ERR_ConvoyVehicle unit)
|
||||
{
|
||||
array<BaseCompartmentSlot> all = {};
|
||||
if (!ERR_ConvoyRules.CollectSeats(unit.Entity, all)) return false;
|
||||
array<BaseCompartmentSlot> drivers = {}, turrets = {}, cargo = {};
|
||||
foreach (BaseCompartmentSlot slot : all)
|
||||
{
|
||||
if (!slot.IsCompartmentAccessible() || slot.IsOccupied()) continue;
|
||||
if (slot.GetType() == ECompartmentType.PILOT) drivers.Insert(slot);
|
||||
else if (slot.GetType() == ECompartmentType.TURRET) turrets.Insert(slot);
|
||||
else if (slot.GetType() == ECompartmentType.CARGO) cargo.Insert(slot);
|
||||
}
|
||||
int requiredTurrets = 1;
|
||||
if (unit.Index == 1 || unit.Index == 2) requiredTurrets = 0;
|
||||
if (unit.Index == 4) requiredTurrets = 2;
|
||||
if (drivers.Count() != 1 || turrets.Count() < requiredTurrets || (unit.Index == 2 && cargo.Count() < 8)) return false;
|
||||
unit.Seats.Insert(drivers[0]);
|
||||
for (int i = 0; i < requiredTurrets; i++) unit.Seats.Insert(turrets[i]);
|
||||
if (unit.Index == 2) for (int i = 0; i < 8; i++) unit.Seats.Insert(cargo[i]);
|
||||
// Exact planned population is known before any character is created.
|
||||
if (unit.Seats.Count() != ERR_ConvoyPool.CrewCount(unit.Index)) return false;
|
||||
AIGroup nativeGroup = unit.Group;
|
||||
foreach (BaseCompartmentSlot seat : unit.Seats)
|
||||
{
|
||||
IEntity actor = seat.SpawnCharacterInCompartment(ERR_ConvoyResources.Soldier(), nativeGroup, ERR_ConvoyResources.GroupPrefab());
|
||||
if (actor) { unit.Members.Insert(actor); m_Mode.Own(actor); }
|
||||
if (!actor || nativeGroup != unit.Group) return false;
|
||||
}
|
||||
unit.Driver = unit.Members[0];
|
||||
return true;
|
||||
}
|
||||
ERR_ConvoyTask Dispatch(ERR_ConvoyReceipt receipt, array<vector> positions, array<vector> angles, ERR_ForceRoute route, float now)
|
||||
{
|
||||
if (!Current() || !ERR_ForceRules.Finite(now) || now < 0 || !ERR_ConvoyRules.Plan(positions, angles, route) || !ERR_ConvoyResources.Available() || !ERR_ConvoyRules.GroundClear(positions, angles)) return null;
|
||||
// The server role selection owns the memberless group affiliation.
|
||||
if (!m_Mode.EnemyFaction() || !m_Pool.Consume(receipt)) return null;
|
||||
ERR_ConvoyTask task = NewTask(); task.Receipt = receipt; task.Route = route; task.Deadline = now + 30000;
|
||||
m_Tasks.Insert(task);
|
||||
for (int index = 0; index < 6; index++)
|
||||
{
|
||||
ERR_ConvoyVehicle unit = NewVehicle(); unit.Index = index; task.Vehicles.Insert(unit);
|
||||
unit.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), positions[index]));
|
||||
if (unit.Group) m_Mode.Own(unit.Group);
|
||||
unit.Entity = Vehicle.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.VehiclePrefab(index), positions[index], angles[index]));
|
||||
if (unit.Entity) m_Mode.Own(unit.Entity);
|
||||
if (!unit.Group || !unit.Entity || unit.Group.GetFaction() != m_Mode.EnemyFaction() || !ERR_ConvoyRules.EmptyCrewTemplate(unit.Group) || !FillCrew(unit))
|
||||
{ task.Stop(m_Mode, "finite convoy startup failed; receipt consumed without retry"); return task; }
|
||||
FactionAffiliationComponent affiliation = FactionAffiliationComponent.Cast(unit.Entity.FindComponent(FactionAffiliationComponent));
|
||||
if (affiliation) affiliation.SetAffiliatedFaction(m_Mode.EnemyFaction());
|
||||
AICarMovementComponent movement = AICarMovementComponent.Cast(unit.Entity.FindComponent(AICarMovementComponent));
|
||||
if (movement) movement.SetCruiseSpeed(35);
|
||||
}
|
||||
ERR_HQ.Event("CONVOY CREATED", "six physical vehicles; finite crew created; native readiness and ledger admission pending");
|
||||
return task;
|
||||
}
|
||||
protected string ReadinessReason(ERR_ConvoyVehicle unit)
|
||||
{
|
||||
if (unit.Registered) return "already admitted";
|
||||
if (!unit.Group) return "missing group";
|
||||
if (!unit.Entity) return "missing vehicle";
|
||||
if (!ERR_Actors.Alive(unit.Entity)) return "vehicle destroyed or lacks damage state";
|
||||
if (unit.Group.GetFaction() != m_Mode.EnemyFaction()) return "group faction mismatch";
|
||||
if (!ERR_Actors.CanFight(unit.Driver)) return "driver absent/dead/unconscious or controller unavailable";
|
||||
if (ERR_Actors.Player(unit.Driver)) return "driver controlled by player";
|
||||
if (!ERR_ConvoyRules.PartOf(CompartmentAccessComponent.GetVehicleIn(unit.Driver), unit.Entity)) return "driver outside intended vehicle";
|
||||
foreach (IEntity member : unit.Members)
|
||||
{
|
||||
if (!ERR_Actors.Alive(member)) return "member absent/dead or damage state unavailable";
|
||||
if (ERR_Actors.Player(member)) return "member controlled by player";
|
||||
if (ERR_Actors.Group(member) != unit.Group) return "member native agent/parent group mismatch";
|
||||
}
|
||||
array<AIAgent> agents = {}; unit.Group.GetAgents(agents);
|
||||
if (agents.Count() != unit.Members.Count()) return "native agent count differs from original crew";
|
||||
foreach (AIAgent agent : agents)
|
||||
{
|
||||
if (!agent) return "null native agent";
|
||||
if (!unit.Members.Contains(agent.GetControlledEntity())) return "native agent controls foreign entity";
|
||||
if (!ERR_Actors.CanFight(agent.GetControlledEntity())) return "native agent actor cannot fight";
|
||||
}
|
||||
SCR_AIGroupUtilityComponent utility = unit.Group.GetGroupUtilityComponent();
|
||||
if (!utility) return "group utility unavailable";
|
||||
if (!utility.m_VehicleMgr) return "group vehicle manager unavailable";
|
||||
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(unit.Entity.FindComponent(SCR_AIVehicleUsageComponent));
|
||||
if (!usage) return "vehicle usage component unavailable";
|
||||
for (int i = 0; i < unit.Seats.Count(); i++)
|
||||
if (i >= unit.Members.Count() || !unit.Seats[i] || unit.Seats[i].GetOccupant() != unit.Members[i]) return "intended seat occupant mismatch";
|
||||
return "readiness guards satisfied; awaiting admission";
|
||||
}
|
||||
protected void InspectReadiness(ERR_ConvoyVehicle unit, float now)
|
||||
{
|
||||
if (!unit || now < unit.NextReadinessNote) return;
|
||||
unit.NextReadinessNote = now + 5000;
|
||||
int agents, activeAgents;
|
||||
bool activeGroup;
|
||||
if (unit.Group)
|
||||
{
|
||||
array<AIAgent> current = {}; unit.Group.GetAgents(current);
|
||||
agents = current.Count(); activeGroup = unit.Group.IsAIActivated();
|
||||
foreach (AIAgent agent : current) if (agent && agent.IsAIActivated()) activeAgents++;
|
||||
}
|
||||
ERR_HQ.Event("CONVOY READINESS", string.Format("vehicle=%1 reason=%2 members=%3 agents=%4 activeAgents=%5 activeGroup=%6 seats=%7 registered=%8", unit.Index, ReadinessReason(unit), unit.Members.Count(), agents, activeAgents, activeGroup, unit.Seats.Count(), unit.Registered));
|
||||
}
|
||||
protected bool Admit(ERR_ConvoyTask task, float now)
|
||||
{
|
||||
foreach (ERR_ConvoyVehicle observed : task.Vehicles) InspectReadiness(observed, now);
|
||||
foreach (ERR_ConvoyVehicle unit : task.Vehicles)
|
||||
{
|
||||
if (unit.Registered) continue;
|
||||
if (!unit.CrewValid(m_Mode)) return false;
|
||||
array<AIAgent> agents = {}; unit.Group.GetAgents(agents);
|
||||
if (agents.Count() != unit.Members.Count()) return false;
|
||||
foreach (AIAgent agent : agents)
|
||||
if (!agent || !unit.Members.Contains(agent.GetControlledEntity()) || !ERR_Actors.CanFight(agent.GetControlledEntity())) return false;
|
||||
SCR_AIGroupUtilityComponent utility = unit.Group.GetGroupUtilityComponent();
|
||||
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(unit.Entity.FindComponent(SCR_AIVehicleUsageComponent));
|
||||
if (!utility || !utility.m_VehicleMgr || !usage || !utility.m_VehicleMgr.TryAddVehicle(usage)) return false;
|
||||
ERR_ForceRecord record = m_Mode.Forces().Register(task.Receipt.SourceId + "-vehicle-" + unit.Index.ToString(), ERR_ForceKind.CONVOY, unit.Entity.GetOrigin(), unit.Members.Count(), null, unit.Group, now);
|
||||
if (!record || !m_Pool.BindRecord(task.Receipt, unit.Index, record)) { task.Stop(m_Mode, "convoy ledger admission rejected"); return false; }
|
||||
record.Pinned = true;
|
||||
unit.Registered = true;
|
||||
m_Mode.RegisterGroup(unit.Group);
|
||||
ERR_HQ hq = ERR_HQ.Cast(m_Mode.Command());
|
||||
if (hq)
|
||||
{
|
||||
hq.ReserveGroup(unit.Group);
|
||||
if (!hq.BindVehicle(unit.Group, unit.Entity))
|
||||
ERR_HQ.Event("MOUNTED AMMO", "original convoy vehicle binding unavailable; no replacement lookup");
|
||||
}
|
||||
// Native helper idempotently registers deletion/damage callbacks and mounted
|
||||
// combat activity; raw vehicle-manager admission alone omits those hooks.
|
||||
utility.AddUsableVehicle(usage);
|
||||
unit.Group.SetPermanentLOD(0); unit.Group.ActivateAI();
|
||||
foreach (AIAgent active : agents) { active.SetPermanentLOD(0); active.ActivateAI(); }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected void IssueCargo(ERR_ConvoyTask task)
|
||||
{
|
||||
if (task.CargoIssued || task.Stopped || task.Vehicles.Count() != 6) return;
|
||||
task.CargoIssued = true;
|
||||
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(task.Vehicles[1].Entity.FindComponent(InventoryStorageManagerComponent));
|
||||
if (!inventory) { ERR_HQ.Event("CONVOY CARGO", "ammo truck inventory unavailable; no retry or free replacement"); return; }
|
||||
for (int i = 0; i < 56; i++)
|
||||
{
|
||||
ResourceName prefab = ERR_ConvoyResources.Magazine();
|
||||
if (i < 8) prefab = ERR_ConvoyResources.Rifle();
|
||||
ERR_ConvoyCargoCallback operation = new ERR_ConvoyCargoCallback(); operation.Prefab = prefab; task.Cargo.Insert(operation);
|
||||
if (!inventory.TrySpawnPrefabToStorage(prefab, null, -1, EStoragePurpose.PURPOSE_DEPOSIT, operation)) operation.Finished = true;
|
||||
}
|
||||
ERR_HQ.Event("CONVOY CARGO", "finite target submitted once: eight AK74 and 48 magazines; native capacity/completion may reduce actual stock");
|
||||
}
|
||||
void Tick(float now)
|
||||
{
|
||||
if (!Current() || !ERR_ForceRules.Finite(now)) return;
|
||||
foreach (ERR_ConvoyTask task : m_Tasks)
|
||||
{
|
||||
if (task.Stopped || now < task.NextCheck) continue;
|
||||
task.NextCheck = now + 1000;
|
||||
bool captured;
|
||||
foreach (ERR_ConvoyVehicle unit : task.Vehicles) if (ERR_ConvoyRules.Captured(unit.Entity, unit.Group)) captured = true;
|
||||
if (captured) { task.Stop(m_Mode, "player captured convoy vehicle or joined crew"); continue; }
|
||||
if (!task.Ready)
|
||||
{
|
||||
if (now >= task.Deadline) { task.Stop(m_Mode, "native crew readiness expired; no replacement"); continue; }
|
||||
if (!Admit(task, now)) continue;
|
||||
task.Ready = true; IssueCargo(task);
|
||||
}
|
||||
bool pending;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
|
||||
{
|
||||
vehicle.Tick(m_Mode, task.Route, now);
|
||||
if (!vehicle.Released && !vehicle.Arrived) pending = true;
|
||||
}
|
||||
if (!pending) task.Stop(m_Mode, "finite authored road route ended or all vehicle orders released");
|
||||
}
|
||||
}
|
||||
protected bool InterceptEligible(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
if (!Current() || !task || !order || !m_Tasks.Contains(task) || task.Stopped || !task.Ready || !task.Receipt || !task.Receipt.Consumed) return false;
|
||||
if (!m_Mode.Forces()) return false;
|
||||
ERR_HQ hq = ERR_HQ.Cast(m_Mode.Command());
|
||||
if (!hq || hq.LogisticsToken(order.Group) != order.Token) return false;
|
||||
if (order.Token < 400001 || order.Token > 400256 || order.SourceId != task.Receipt.SourceId) return false;
|
||||
if (order.VehicleIndex != 0 && order.VehicleIndex != 3 && order.VehicleIndex != 5) return false;
|
||||
if (order.VehicleIndex >= task.Vehicles.Count() || order.VehicleIndex >= task.Receipt.Records.Count()) return false;
|
||||
ERR_ConvoyVehicle unit = task.Vehicles[order.VehicleIndex];
|
||||
if (!unit || !unit.Registered || unit.Released || unit.Arrived || unit.Index != order.VehicleIndex || unit.Group != order.Group) return false;
|
||||
if (!unit.InterceptCrew(m_Mode) || !task.Route || unit.RouteIndex < 0 || unit.RouteIndex >= task.Route.Count()) return false;
|
||||
ERR_ForceRecord record = m_Mode.Forces().FindGroup(unit.Group);
|
||||
if (!record || !record.Physical || record.Eliminated || record.Kind != ERR_ForceKind.CONVOY || record.Id != task.Receipt.Records[unit.Index]) return false;
|
||||
if (record.Group != unit.Group || record.SourceId != task.Receipt.SourceId + "-vehicle-" + unit.Index.ToString()) return false;
|
||||
if (unit.InterceptOrder && (unit.InterceptReceipt != task.Receipt || unit.InterceptRecord != record || unit.InterceptRoute != task.Route)) return false;
|
||||
foreach (ERR_ConvoyVehicle other : task.Vehicles)
|
||||
if (!other || other.SupplyToken != 0 || other.SupplyHeld || other.Combat.Held) return false;
|
||||
return true;
|
||||
}
|
||||
bool InterceptCurrent(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order)
|
||||
{
|
||||
return InterceptEligible(task, order) && task.Vehicles[order.VehicleIndex].InterceptToken == order.Token
|
||||
&& task.Vehicles[order.VehicleIndex].InterceptMatches(order);
|
||||
}
|
||||
bool ReserveIntercept(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!InterceptEligible(task, order) || !ERR_ForceRules.Finite(now) || now < 0) return false;
|
||||
if (!ERR_ForceRules.Finite(order.ObservedAt) || !ERR_ForceRules.Finite(order.IssuedAt) || !ERR_ForceRules.Finite(order.ExpiresAt)) return false;
|
||||
if (order.ObservedAt < 0 || order.IssuedAt < order.ObservedAt || now < order.IssuedAt || now >= order.ExpiresAt) return false;
|
||||
if (order.ExpiresAt > order.ObservedAt + 120000 || !ERR_ForceRules.FiniteVector(order.Position)) return false;
|
||||
if (order.BeliefId <= 0 || order.BeliefRevision <= 0 || order.ReportId <= 0) return false;
|
||||
foreach (ERR_ConvoyTask existing : m_Tasks)
|
||||
foreach (ERR_ConvoyVehicle claimed : existing.Vehicles) if (claimed && claimed.InterceptToken != 0) return false;
|
||||
ERR_ConvoyVehicle unit = task.Vehicles[order.VehicleIndex];
|
||||
if (!unit.ClaimIntercept(m_Mode, order)) return false;
|
||||
unit.InterceptReceipt = task.Receipt; unit.InterceptRecord = m_Mode.Forces().FindGroup(unit.Group); unit.InterceptRoute = task.Route;
|
||||
return true;
|
||||
}
|
||||
protected bool InterceptPairEligible(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder first, ERR_ConvoyInterceptOrder second, float now)
|
||||
{
|
||||
if (!first || !second || first == second || first.Group == second.Group || first.VehicleIndex == second.VehicleIndex || first.Token == second.Token) return false;
|
||||
if (!InterceptEligible(task, first) || !InterceptEligible(task, second) || !ERR_ForceRules.Finite(now) || now < 0) return false;
|
||||
if (task.Vehicles[first.VehicleIndex].Entity == task.Vehicles[second.VehicleIndex].Entity) return false;
|
||||
if (first.BeliefId <= 0 || first.BeliefRevision <= 0 || first.ReportId <= 0) return false;
|
||||
if (first.BeliefId != second.BeliefId || first.BeliefRevision != second.BeliefRevision || first.ReportId != second.ReportId) return false;
|
||||
if (first.ObservedAt != second.ObservedAt || first.IssuedAt != second.IssuedAt || first.ExpiresAt != second.ExpiresAt) return false;
|
||||
if (!ERR_ForceRules.Finite(first.ObservedAt) || !ERR_ForceRules.Finite(first.IssuedAt) || !ERR_ForceRules.Finite(first.ExpiresAt)) return false;
|
||||
if (first.ObservedAt < 0 || first.IssuedAt < first.ObservedAt || now < first.IssuedAt || now >= first.ExpiresAt || first.ExpiresAt > first.ObservedAt + 120000) return false;
|
||||
if (!ERR_ForceRules.FiniteVector(first.Position) || !ERR_ForceRules.FiniteVector(second.Position)) return false;
|
||||
return vector.DistanceXZ(first.Position, second.Position) >= 0;
|
||||
}
|
||||
bool ReserveInterceptPair(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder first, ERR_ConvoyInterceptOrder second, float now)
|
||||
{
|
||||
if (!InterceptPairEligible(task, first, second, now)) return false;
|
||||
foreach (ERR_ConvoyTask existing : m_Tasks)
|
||||
foreach (ERR_ConvoyVehicle claimed : existing.Vehicles) if (claimed && claimed.InterceptToken != 0) return false;
|
||||
ERR_ConvoyVehicle lead = task.Vehicles[first.VehicleIndex];
|
||||
ERR_ConvoyVehicle partner = task.Vehicles[second.VehicleIndex];
|
||||
if (!lead.ClaimIntercept(m_Mode, first)) return false;
|
||||
lead.InterceptReceipt = task.Receipt; lead.InterceptRecord = m_Mode.Forces().FindGroup(lead.Group); lead.InterceptRoute = task.Route;
|
||||
if (!InterceptPairEligible(task, first, second, now) || !partner.ClaimIntercept(m_Mode, second))
|
||||
{ lead.ClearIntercept(first); return false; }
|
||||
partner.InterceptReceipt = task.Receipt; partner.InterceptRecord = m_Mode.Forces().FindGroup(partner.Group); partner.InterceptRoute = task.Route;
|
||||
return true;
|
||||
}
|
||||
bool ApplyInterceptPair(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder first, ERR_ConvoyInterceptOrder second, float now)
|
||||
{
|
||||
if (!InterceptPairEligible(task, first, second, now) || !InterceptCurrent(task, first) || !InterceptCurrent(task, second)) return false;
|
||||
ERR_ConvoyVehicle lead = task.Vehicles[first.VehicleIndex];
|
||||
ERR_ConvoyVehicle partner = task.Vehicles[second.VehicleIndex];
|
||||
if (!lead.CanInstallIntercept(m_Mode, first, now) || !partner.CanInstallIntercept(m_Mode, second, now)) return false;
|
||||
AIWaypoint firstWaypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), first.Position));
|
||||
if (!firstWaypoint) return false;
|
||||
AIWaypoint secondWaypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), second.Position));
|
||||
if (!secondWaypoint) { SCR_EntityHelper.DeleteEntityAndChildren(firstWaypoint); return false; }
|
||||
firstWaypoint.SetCompletionRadius(15); secondWaypoint.SetCompletionRadius(15);
|
||||
if (!InterceptPairEligible(task, first, second, now) || !InterceptCurrent(task, first) || !InterceptCurrent(task, second)
|
||||
|| task.Vehicles[first.VehicleIndex] != lead || task.Vehicles[second.VehicleIndex] != partner
|
||||
|| !lead.CanInstallIntercept(m_Mode, first, now) || !partner.CanInstallIntercept(m_Mode, second, now))
|
||||
{ SCR_EntityHelper.DeleteEntityAndChildren(firstWaypoint); SCR_EntityHelper.DeleteEntityAndChildren(secondWaypoint); return false; }
|
||||
// Route removal is also a native boundary; both prepared handles remain
|
||||
// caller-owned until both exact claims have survived it.
|
||||
lead.ClearOrder();
|
||||
if (InterceptPairEligible(task, first, second, now) && InterceptCurrent(task, first) && InterceptCurrent(task, second)) partner.ClearOrder();
|
||||
if (!InterceptPairEligible(task, first, second, now) || !InterceptCurrent(task, first) || !InterceptCurrent(task, second)
|
||||
|| task.Vehicles[first.VehicleIndex] != lead || task.Vehicles[second.VehicleIndex] != partner
|
||||
|| !lead.CanInstallIntercept(m_Mode, first, now) || !partner.CanInstallIntercept(m_Mode, second, now))
|
||||
{
|
||||
SCR_EntityHelper.DeleteEntityAndChildren(firstWaypoint); SCR_EntityHelper.DeleteEntityAndChildren(secondWaypoint);
|
||||
ReleaseIntercept(task, first, now); ReleaseIntercept(task, second, now); return false;
|
||||
}
|
||||
lead.InstallIntercept(firstWaypoint);
|
||||
// AddWaypoint can invoke native listeners. Do not install the partner after
|
||||
// cancellation/ownership loss; release only these exact owned claims.
|
||||
if (!InterceptPairEligible(task, first, second, now) || !InterceptCurrent(task, first) || !lead.InterceptActive
|
||||
|| !InterceptCurrent(task, second) || task.Vehicles[first.VehicleIndex] != lead || task.Vehicles[second.VehicleIndex] != partner
|
||||
|| !partner.CanInstallIntercept(m_Mode, second, now))
|
||||
{
|
||||
SCR_EntityHelper.DeleteEntityAndChildren(secondWaypoint);
|
||||
ReleaseIntercept(task, first, now); ReleaseIntercept(task, second, now); return false;
|
||||
}
|
||||
partner.InstallIntercept(secondWaypoint);
|
||||
if (InterceptPairEligible(task, first, second, now) && InterceptCurrent(task, first) && InterceptCurrent(task, second)
|
||||
&& task.Vehicles[first.VehicleIndex] == lead && task.Vehicles[second.VehicleIndex] == partner
|
||||
&& lead.InterceptActive && partner.InterceptActive) return true;
|
||||
ReleaseIntercept(task, first, now); ReleaseIntercept(task, second, now); return false;
|
||||
}
|
||||
bool ApplyIntercept(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!InterceptCurrent(task, order) || !ERR_ForceRules.Finite(now) || now < order.IssuedAt || now >= order.ExpiresAt) return false;
|
||||
return task.Vehicles[order.VehicleIndex].DeliverIntercept(m_Mode, order, now);
|
||||
}
|
||||
void ReleaseIntercept(ERR_ConvoyTask task, ERR_ConvoyInterceptOrder order, float now)
|
||||
{
|
||||
if (!task || !order || !m_Tasks.Contains(task) || !ERR_ForceRules.Finite(now)) return;
|
||||
foreach (ERR_ConvoyVehicle unit : task.Vehicles)
|
||||
if (unit && unit.InterceptMatches(order))
|
||||
{ bool delivered = unit.InterceptActive; unit.ClearIntercept(order); if (delivered) unit.LegDeadline = now + 600000; }
|
||||
}
|
||||
bool ApplyCombatOrder(ERR_ConvoyTask task, ERR_ConvoyCombatOrder order, float now)
|
||||
{
|
||||
if (!Current() || !task || !order || !m_Tasks.Contains(task) || task.Stopped || !task.Ready || !task.Receipt || !task.Receipt.Consumed) return false;
|
||||
foreach (ERR_ConvoyVehicle intercept : task.Vehicles) if (intercept && intercept.InterceptToken != 0) return false;
|
||||
if (order.VehicleIndex < 0 || order.VehicleIndex >= task.Vehicles.Count() || order.SourceId != task.Receipt.SourceId) return false;
|
||||
ERR_ConvoyVehicle unit = task.Vehicles[order.VehicleIndex];
|
||||
if (!unit || !unit.Registered || unit.Released || unit.Arrived || unit.Index != order.VehicleIndex || unit.Group != order.Group || !unit.CrewValid(m_Mode)) return false;
|
||||
if (ERR_ConvoyRules.Captured(unit.Entity, unit.Group) || unit.Index >= task.Receipt.Records.Count()) return false;
|
||||
if (!ERR_ConvoyCombatRules.Accept(unit.Combat, order, now) || !task.Route || unit.RouteIndex >= task.Route.Count()) return false;
|
||||
ERR_ForceRecord record = m_Mode.Forces().FindGroup(unit.Group);
|
||||
if (!record || !record.Physical || record.Eliminated || record.Kind != ERR_ForceKind.CONVOY || record.Id != task.Receipt.Records[unit.Index]) return false;
|
||||
array<AIAgent> agents = {}; unit.Group.GetAgents(agents);
|
||||
if (agents.Count() != unit.Members.Count()) return false;
|
||||
foreach (AIAgent agent : agents)
|
||||
if (!agent || !unit.Members.Contains(agent.GetControlledEntity())) return false;
|
||||
// No caller can replay even a command whose native waypoint recreation fails.
|
||||
unit.Combat.LastCommandId = order.CommandId;
|
||||
if (order.Hold)
|
||||
{
|
||||
if (!unit.Combat.Held) unit.ClearOrder();
|
||||
unit.Combat.Held = true; unit.Combat.ReportId = order.ReportId; unit.Combat.ObservedAt = order.ObservedAt;
|
||||
}
|
||||
else
|
||||
{
|
||||
unit.Combat.Held = false;
|
||||
if (!unit.SupplyHeld && !unit.Order(task.Route, now)) { unit.Release(m_Mode); return false; }
|
||||
}
|
||||
ERR_HQ.Event("CONVOY COVER", string.Format("command M%1 report M%2 vehicle=%3 held=%4 routeIndex=%5", order.CommandId, order.ReportId, unit.Index, unit.Combat.Held, unit.RouteIndex));
|
||||
return true;
|
||||
}
|
||||
int Count() { return m_Tasks.Count(); }
|
||||
string Status()
|
||||
{
|
||||
int ready, stopped, vehicles, completed, failed, pending;
|
||||
foreach (ERR_ConvoyTask task : m_Tasks)
|
||||
{
|
||||
if (task.Ready) ready++;
|
||||
if (task.Stopped) stopped++;
|
||||
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles) if (vehicle && vehicle.Entity) vehicles++;
|
||||
foreach (ERR_ConvoyCargoCallback operation : task.Cargo)
|
||||
{
|
||||
if (!operation.Finished) pending++;
|
||||
else if (operation.Succeeded) completed++;
|
||||
else failed++;
|
||||
}
|
||||
}
|
||||
string reason;
|
||||
if (m_Tasks.Count() == 1 && m_Tasks[0].Stopped) reason = m_Tasks[0].Reason;
|
||||
if (reason.Length() > 160) reason = reason.Substring(0, 160);
|
||||
return string.Format("Convoys: %1 tasks, %2 reached readiness, %3 stopped, %4 retained physical vehicles; cargo native insertion successes=%5 failed=%6 pending=%7; current inventory may differ after use/looting", m_Tasks.Count(), ready, stopped, vehicles, completed, failed, pending) + "; reason=" + reason;
|
||||
}
|
||||
void Stop()
|
||||
{
|
||||
if (m_Stopped) return;
|
||||
m_Stopped = true;
|
||||
foreach (ERR_ConvoyTask task : m_Tasks) task.Stop(m_Mode, "HQ ended convoy task service");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
PASS: actual mounted intercept eligibility/order chronology, fixed destinations, native road selection bounds, width, segment heading, clearance and settlement delegation; no native driving claim
|
||||
@@ -0,0 +1,22 @@
|
||||
from pathlib import Path
|
||||
import os,subprocess,json,hashlib,difflib
|
||||
root=Path('/home/jakub/devel/escapistsrereintroduced');out=Path('/tmp/erri-pair-service-0054');rel='addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c';test='tests/test_convoy_intercept_pair.py'
|
||||
env=os.environ.copy();env.update(PYTHONPATH=str(root/'tests'),PYTHONDONTWRITEBYTECODE='1')
|
||||
def check(name,source,expected):
|
||||
env['ERR_CONVOY_SERVICE']=str(source)
|
||||
p=subprocess.run(['python3',str(out/test)],cwd=root,env=env,capture_output=True,text=True)
|
||||
(out/name).write_text(p.stdout+p.stderr);assert (p.returncode==0)==expected,(name,p.returncode)
|
||||
return p.returncode
|
||||
check('baseline-red.log',out/'baseline'/rel,False)
|
||||
s=(out/rel).read_text()
|
||||
mut=out/'mutation-rollback.c';mut.write_text(s.replace('{ lead.ClearIntercept(first); return false; }','{ return false; }',1));check('mutation-rollback-red.log',mut,False)
|
||||
mut=out/'mutation-spacing.c';mut.write_text(s.replace('return vector.DistanceXZ(first.Position, second.Position) >= 40;','return vector.DistanceXZ(first.Position, second.Position) >= 0;',1));check('mutation-spacing-red.log',mut,False)
|
||||
check('green.log',out/rel,True)
|
||||
patch=''.join(difflib.unified_diff((out/'baseline'/rel).read_text().splitlines(True),s.splitlines(True),fromfile='a/'+rel,tofile='b/'+rel))
|
||||
patch+=''.join(difflib.unified_diff([], (out/test).read_text().splitlines(True),fromfile='/dev/null',tofile='b/'+test))
|
||||
(out/'fix.patch').write_text(patch)
|
||||
p=subprocess.run(['git','apply','--check',str(out/'fix.patch')],cwd=root,capture_output=True,text=True);(out/'apply-check.log').write_text(p.stdout+p.stderr);assert p.returncode==0
|
||||
manifest={f:hashlib.sha256((out/f).read_bytes()).hexdigest() for f in [rel,test,'fix.patch']}
|
||||
(out/'source-manifest.json').write_text(json.dumps(manifest,indent=2)+'\n')
|
||||
(out/'finding.json').write_text(json.dumps({'scope':'One original convoy pair, distinct original escorts and HQ tokens, same delivered memory/timing, minimum 40m horizontal separation. No actors/items/ammo spawned. Only two native waypoint entities allocated.','files':[rel,test],'tests':{'pair':'green.log','singleton_lifecycle':'lifecycle.log','geometry_existing':'rules.log','baseline':'baseline-red.log is explicit absent pair API assertion, not a claimed runtime behavior failure','mutations':['mutation-rollback-red.log','mutation-spacing-red.log']},'limits':['Offline engine API doubles; physical driving/native callback timing unqualified.','Preallocation and precommit failures leave both original routes unchanged; native ClearOrder/AddWaypoint boundary failures cancel exact claims, without atomic rollback or route restoration claim.','Existing singleton behavior and legacy direct recursive ClearIntercept callback handling unchanged; no concrete native recursive callback demonstrated.'],'adapters':'No existing harness changes needed; test_hq_mounted_intercept extracts geometry only. New pair test reuses unchanged lifecycle native doubles, extracts complete actual vehicle and actual service methods.'},indent=2)+'\n')
|
||||
print(json.dumps(manifest,indent=2))
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c": "b59016d83cb9d40136b98b4e0067ea490bc553574fc53603593efd5522dbed20",
|
||||
"tests/test_convoy_intercept_pair.py": "52b55727b7c2441d43530efa701045b9439b9175c71fbd2ae7577f851d2c98ec",
|
||||
"fix.patch": "dd034f9837703ba3cbf157490999152ef1d00fae7878c5fe8073d808e3abe1c4"
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
"""Execute actual pair admission/apply plus the complete existing vehicle policy.
|
||||
Native doubles inject allocation, AddWaypoint and RemoveWaypoint boundaries.
|
||||
"""
|
||||
import contextlib,io,os,re,runpy
|
||||
from pathlib import Path
|
||||
import policy_harness as h
|
||||
path=Path(os.environ.get('ERR_CONVOY_SERVICE',h.ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c'))
|
||||
source=path.read_text()
|
||||
assert 'bool ReserveInterceptPair(' in source and 'bool ApplyInterceptPair(' in source, 'paired service API absent (baseline feature gap)'
|
||||
# Reuse existing full vehicle/native doubles without running or changing its assertions.
|
||||
captured=[]; original_run=h.run
|
||||
try:
|
||||
h.run=captured.append
|
||||
with contextlib.redirect_stdout(io.StringIO()): runpy.run_path(str(h.ROOT/'tests/test_convoy_lifecycle.py'))
|
||||
finally: h.run=original_run
|
||||
assert len(captured)==1
|
||||
cpp=captured[0]
|
||||
pair='\n'.join(h.body(path,s) for s in ('protected bool InterceptPairEligible(', 'bool ReserveInterceptPair(', 'bool ApplyInterceptPair('))
|
||||
pair=pair.replace('protected ','').replace('foreach (','for (')
|
||||
for typ in ['ERR_ConvoyTask','ERR_ConvoyInterceptOrder','ERR_ConvoyVehicle','AIWaypoint']:
|
||||
pair=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',pair)
|
||||
for obj in ['task','first','second','lead','partner','existing','claimed','m_Mode','firstWaypoint','secondWaypoint','Receipt']:
|
||||
pair=pair.replace(obj+'.',obj+'->')
|
||||
pair=re.sub(r'(Vehicles\[[^\]]+\])\.',r'\1->',pair).replace('Forces().','Forces()->')
|
||||
for ns in ['ERR_ForceRules','ERR_Actors','ERR_Resources','SCR_EntityHelper','vector']:
|
||||
pair=pair.replace(ns+'.',ns+'::')
|
||||
pair=pair.replace('AIWaypoint.Cast(', 'static_cast<AIWaypoint*>(')
|
||||
cpp=cpp.replace('bool ApplyIntercept(ERR_ConvoyTask*',pair+'\nbool ApplyIntercept(ERR_ConvoyTask*',1)
|
||||
cpp=cpp[:cpp.index('int main(){')]
|
||||
cpp=cpp.replace('#include <cassert>','#include <cassert>\n#include <functional>\n#include <map>')
|
||||
cpp=cpp.replace('void RemoveWaypoint(AIWaypoint*){removes++;}void AddWaypoint(AIWaypoint*){adds++;}', 'std::function<void()> onRemove,onAdd;void RemoveWaypoint(AIWaypoint*){removes++;if(onRemove)onRemove();}void AddWaypoint(AIWaypoint*){adds++;if(onAdd)onAdd();}')
|
||||
cpp=cpp.replace('ERR_ForceRecord* FindGroup(SCR_AIGroup*){return &record;}', 'std::map<SCR_AIGroup*,ERR_ForceRecord*> records;ERR_ForceRecord* FindGroup(SCR_AIGroup* g){return records.count(g)?records[g]:&record;}')
|
||||
cpp=cpp.replace('int LogisticsToken(SCR_AIGroup*){return token;}', 'std::map<SCR_AIGroup*,int> tokens;int LogisticsToken(SCR_AIGroup* g){return tokens.count(g)?tokens[g]:token;}')
|
||||
cpp=cpp.replace('int spawns=0;bool fail=false;', 'int spawns=0,failAt=0;bool fail=false;std::function<void()> onSpawn;')
|
||||
cpp=cpp.replace('spawns++;return fail?nullptr:new AIWaypoint();','spawns++;if(onSpawn)onSpawn();return (fail||spawns==failAt)?nullptr:new AIWaypoint();')
|
||||
cpp+=r'''
|
||||
struct PairCase:InterceptCase{
|
||||
Case b; ERR_ConvoyInterceptOrder second;ERR_ConvoyVehicle fillers[4];
|
||||
PairCase(){
|
||||
b.unit.Index=3;b.mode.ledger.record.Id=10;b.mode.ledger.record.SourceId="original-vehicle-3";
|
||||
mode.ledger.records[&group]=&mode.ledger.record;mode.ledger.records[&b.group]=&b.mode.ledger.record;
|
||||
mode.command.tokens[&group]=400001;mode.command.tokens[&b.group]=400002;
|
||||
second=order;second.Token=400002;second.Group=&b.group;second.VehicleIndex=3;second.Position={740,0,0};
|
||||
task.Vehicles={&unit,&fillers[0],&fillers[1],&b.unit,&fillers[2],&fillers[3]};
|
||||
unit.Tick(&mode,&route,0);b.unit.Tick(&mode,&route,0);
|
||||
}
|
||||
bool ReservePair(){return service.ReserveInterceptPair(&task,&order,&second,1000);}
|
||||
bool ApplyPair(){return service.ApplyInterceptPair(&task,&order,&second,2000);}
|
||||
};
|
||||
int main(){
|
||||
{PairCase f;auto a=f.unit.Waypoint;auto b=f.b.unit.Waypoint;float da=f.unit.LegDeadline,db=f.b.unit.LegDeadline;
|
||||
assert(f.ReservePair());assert(f.service.InterceptCurrent(&f.task,&f.order)&&f.service.InterceptCurrent(&f.task,&f.second));
|
||||
assert(f.unit.Waypoint==a&&f.b.unit.Waypoint==b&&!f.unit.InterceptActive&&!f.b.unit.InterceptActive&&f.unit.LegDeadline==da&&f.b.unit.LegDeadline==db);
|
||||
assert(f.ApplyPair());assert(f.unit.InterceptActive&&f.b.unit.InterceptActive&&f.unit.InterceptWaypoint!=f.b.unit.InterceptWaypoint);
|
||||
assert(f.unit.InterceptWaypoint->radius==15&&f.b.unit.InterceptWaypoint->radius==15&&f.unit.RouteIndex==0&&f.b.unit.RouteIndex==0);
|
||||
assert(!f.ApplyPair());f.vehicle.position=f.order.Position;f.b.vehicle.position=f.second.Position;
|
||||
f.unit.Tick(&f.mode,&f.route,3000);f.b.unit.Tick(&f.mode,&f.route,3000);
|
||||
assert(f.unit.InterceptActive&&f.b.unit.InterceptActive&&!f.unit.InterceptWaypoint&&!f.b.unit.InterceptWaypoint);
|
||||
f.unit.Tick(&f.mode,&f.route,121000);f.b.unit.Tick(&f.mode,&f.route,121000);
|
||||
assert(!f.unit.InterceptToken&&!f.b.unit.InterceptToken&&f.unit.Waypoint&&f.b.unit.Waypoint);
|
||||
}
|
||||
for(int bad=0;bad<19;bad++){PairCase f;auto a=f.unit.Waypoint;auto b=f.b.unit.Waypoint;
|
||||
if(bad==0)f.second.Token=f.order.Token;
|
||||
if(bad==1)f.second.Group=f.order.Group;
|
||||
if(bad==2)f.second.VehicleIndex=0;
|
||||
if(bad==3)f.second.Position.x=739;
|
||||
if(bad==4)f.second.Position.x=NAN;
|
||||
if(bad==5)f.second.BeliefId++;
|
||||
if(bad==6)f.second.BeliefRevision++;
|
||||
if(bad==7)f.second.ReportId++;
|
||||
if(bad==8)f.second.ObservedAt++;
|
||||
if(bad==9)f.second.IssuedAt++;
|
||||
if(bad==10)f.second.ExpiresAt++;
|
||||
if(bad==11)f.second.SourceId="foreign";
|
||||
if(bad==12)f.b.driver.player=true;
|
||||
if(bad==13)f.b.seat.occupant=nullptr;
|
||||
if(bad==14)f.fillers[0].SupplyToken=9;
|
||||
if(bad==15)f.fillers[0].Combat->Held=true;
|
||||
if(bad==16)f.mode.command.tokens[&f.b.group]=0;
|
||||
if(bad==17)f.b.unit.Entity=&f.vehicle;
|
||||
if(bad==18){f.order.ExpiresAt=121001;f.second.ExpiresAt=121001;}
|
||||
assert(!f.ReservePair()&&!f.unit.InterceptToken&&!f.b.unit.InterceptToken&&f.unit.Waypoint==a&&f.b.unit.Waypoint==b);
|
||||
}
|
||||
// Claim two fails after claim one: spent token cannot be reused, route untouched.
|
||||
{PairCase f;assert(f.b.unit.ClaimIntercept(&f.mode,&f.second));f.b.unit.ClearIntercept(&f.second);
|
||||
auto a=f.unit.Waypoint;auto b=f.b.unit.Waypoint;float deadline=f.unit.LegDeadline;
|
||||
assert(!f.ReservePair());assert(!f.unit.InterceptToken&&!f.b.unit.InterceptToken&&f.unit.Waypoint==a&&f.b.unit.Waypoint==b&&f.unit.LegDeadline==deadline);
|
||||
}
|
||||
// Global exclusion includes a different original task; never steal its claim.
|
||||
{PairCase f;ERR_ConvoyTask other;ERR_ConvoyVehicle owned;owned.InterceptToken=400099;other.Vehicles={&owned};f.service.m_Tasks.Insert(&other);
|
||||
assert(!f.ReservePair()&&owned.InterceptToken==400099&&!f.unit.InterceptToken);}
|
||||
for(int allocation=1;allocation<=2;allocation++){PairCase f;assert(f.ReservePair());auto a=f.unit.Waypoint;auto b=f.b.unit.Waypoint;int deletes=SCR_EntityHelper::deletes,adds=f.group.adds+f.b.group.adds,removes=f.group.removes+f.b.group.removes;
|
||||
ERR_Actors::failAt=ERR_Actors::spawns+allocation;assert(!f.ApplyPair());ERR_Actors::failAt=0;
|
||||
assert(!f.unit.InterceptActive&&!f.b.unit.InterceptActive&&f.unit.Waypoint==a&&f.b.unit.Waypoint==b);
|
||||
assert(SCR_EntityHelper::deletes==deletes+allocation-1);
|
||||
assert(f.group.adds+f.b.group.adds==adds&&f.group.removes+f.b.group.removes==removes);
|
||||
assert(f.unit.Entity==&f.vehicle&&f.b.unit.Entity==&f.b.vehicle&&f.unit.Driver==&f.driver&&f.b.unit.Driver==&f.b.driver);
|
||||
}
|
||||
// Allocation may revoke original crew. Both prepared handles are deleted before either route changes.
|
||||
{PairCase f;assert(f.ReservePair());auto a=f.unit.Waypoint;auto b=f.b.unit.Waypoint;int deletes=SCR_EntityHelper::deletes;
|
||||
ERR_Actors::onSpawn=[&](){f.b.driver.alive=false;};assert(!f.ApplyPair());ERR_Actors::onSpawn={};
|
||||
assert(f.unit.Waypoint==a&&f.b.unit.Waypoint==b&&SCR_EntityHelper::deletes==deletes+2);}
|
||||
// Immutable claimed destinations: changing both orders together is still rejected.
|
||||
{PairCase f;assert(f.ReservePair());f.order.Position.x+=100;f.second.Position.x+=100;assert(!f.ApplyPair()&&!f.unit.InterceptActive&&!f.b.unit.InterceptActive);}
|
||||
// Native removal and AddWaypoint may synchronously cancel the pair.
|
||||
for(int boundary=0;boundary<3;boundary++){PairCase f;assert(f.ReservePair());int a=f.group.adds,b=f.b.group.adds;
|
||||
auto cancel=[&](){f.service.ReleaseIntercept(&f.task,&f.order,2000);f.service.ReleaseIntercept(&f.task,&f.second,2000);};
|
||||
if(boundary==0)f.group.onRemove=[&](){f.mode.command.tokens[&f.b.group]=0;};
|
||||
if(boundary==1)f.group.onAdd=cancel;
|
||||
if(boundary==2)f.b.group.onAdd=cancel;
|
||||
assert(!f.ApplyPair());assert(!f.unit.InterceptToken&&!f.b.unit.InterceptToken&&!f.unit.InterceptActive&&!f.b.unit.InterceptActive);
|
||||
assert(f.b.group.adds==b+(boundary==2));assert(f.group.adds==a+(boundary!=0));
|
||||
}
|
||||
// Per-member release remains exact and never steals a foreign public waypoint.
|
||||
{PairCase f;assert(f.ReservePair());assert(f.ApplyPair());AIWaypoint foreign;f.b.unit.InterceptWaypoint=&foreign;
|
||||
f.service.ReleaseIntercept(&f.task,&f.order,3000);assert(!f.unit.InterceptToken&&f.b.unit.InterceptToken==400002);
|
||||
f.service.ReleaseIntercept(&f.task,&f.second,3000);assert(f.b.unit.InterceptWaypoint==&foreign&&!f.b.unit.InterceptToken);}
|
||||
}
|
||||
'''
|
||||
h.run(cpp)
|
||||
print('PASS: actual pair admission, rollback, native allocation/revocation boundaries, per-member ownership, hold and expiry')
|
||||
@@ -0,0 +1,422 @@
|
||||
{
|
||||
"addons/core/AI/BehaviorTrees/ERR/MortarRapidFire.bt": "16b263bcb2fb3ae361367a600753a829425620d2e6b350a891c51fba47437ab6",
|
||||
"addons/core/AI/BehaviorTrees/ERR/MortarRapidFire.bt.meta": "f2a44ece28891240e6bfa1d01af29428adcadbe82c80e287eb33cd5e58f1f90a",
|
||||
"addons/core/Configs/ERR/AdminMenus.conf": "a7f889f260e43656136fc159342129e5f48748d28c426f65f9faac37b624cf94",
|
||||
"addons/core/Configs/ERR/AdminMenus.conf.meta": "5faf5ddc0a51c1d0f82d60af00d91edc22fe9c39aac50d8d04b772a2c9271be1",
|
||||
"addons/core/Configs/ERR/Content/Registry.conf": "546a5a407c9e6c25c9157ae4392898fa3f40c04c7956649e62201f9ac0ba187c",
|
||||
"addons/core/Configs/ERR/Content/Registry.conf.meta": "046aa988bf8e972afcf7a60b6f2312f605f016f98625dbbd9cd75ebf9ea88800",
|
||||
"addons/core/Configs/ERR/EveronSites.conf": "b479000b36154a6a3af6b519b086251d981ef63cb86a973d00529499cfd4238c",
|
||||
"addons/core/Configs/ERR/EveronSites.conf.meta": "d5fe68025ef4e7c1e16082cf5873b7c7172eacbfaef4ab29b7e0e3c98f8dca56",
|
||||
"addons/core/Configs/ERR/Factions/MissionVehicles.conf": "5f74f4a7089c7e7b54ad62463159ce8f0bab25a6b85268dc3872ea6001e048fb",
|
||||
"addons/core/Configs/ERR/Factions/MissionVehicles.conf.meta": "00c368b6271d3440cc8bf627b03a384d247635723923fc5ad2104b8b4007b035",
|
||||
"addons/core/Configs/ERR/Factions/US.conf": "4c3da063abb6e3ee39a6e4e0c5a9ffd5eae9d209d58470ea18b45dba5528db1b",
|
||||
"addons/core/Configs/ERR/Factions/US.conf.meta": "a05469aef028e9e17eba545e81e2af2887b59f0fef17dac51a23c7b8155e381b",
|
||||
"addons/core/Configs/ERR/Factions/USSR.conf": "407ea27f80a64c5184c089d43cdd0bdabfc5974c84f1d2a245081fdf2afa3f30",
|
||||
"addons/core/Configs/ERR/Factions/USSR.conf.meta": "057397d97096346791b51b7b13fbe18af218bcf78e9984d9c01b49a2403de611",
|
||||
"addons/core/Configs/ERR/GameOverScreens.conf": "45231061911a1462d45fb6db0fecc2b0793f17b5ae71017174954ff4fbd20236",
|
||||
"addons/core/Configs/ERR/GameOverScreens.conf.meta": "eb654b745c0b97b41c991694569ef4877a3c2315e0826f9556337cd68690843e",
|
||||
"addons/core/Configs/ERR/ObserverInput.conf": "e898d645911e85b6ba902366cb23890d8bb14bbd2c485102e6d49ea728e9b054",
|
||||
"addons/core/Configs/ERR/ObserverInput.conf.meta": "9714dce6c38ee81bcc513c8ebef51d7af63fd15bcdda2e40946937c625dc3ba4",
|
||||
"addons/core/Configs/ERR/Tests/LegacySites.conf": "3864eb638d0ed4dd313013d2d787f975ae6ce4bbe238569919f76b73d5f7ca42",
|
||||
"addons/core/Configs/ERR/Tests/LegacySites.conf.meta": "097ac7daef4fc18344699a8471fdd77f9d158f8c70bab394068d7a08e4777ac0",
|
||||
"addons/core/Configs/ERR/VanillaFactions.conf": "db890b61b48ee15484041fd2245a862d9fa9e7ba21b1229c281ff37c448a35b1",
|
||||
"addons/core/Configs/ERR/VanillaFactions.conf.meta": "ac77725c0c21974991ef47a466b88122e939de8b46fd23e68889e7674cbb2bfd",
|
||||
"addons/core/Materials/ERRM_MapRenderTarget.emat": "9877c5b175c5f30dd80bea36c711b21ab38219aa7075adf9ae79932e1d4c76bc",
|
||||
"addons/core/Materials/ERRM_MapRenderTarget.emat.meta": "5ee1a705f8011a44020ca2691f5ccc17d44bf596fef9a36e98d1f267eb58fab0",
|
||||
"addons/core/Missions/ERR_Everon.conf": "936619df7789a6b4b80bd763f702526e1145ab4d5c300516c07e7e70dfe57d92",
|
||||
"addons/core/Missions/ERR_Everon.conf.meta": "0a23c98bf83873536c65fa17d38dea33a1d7b9e4ab210f90633ff688abcc551f",
|
||||
"addons/core/Missions/ERR_Everon_Base.conf": "b46e1f4aeef383a8dbe7ef2b5c12a55a8111856c52ba103a1e508c01b82dfc61",
|
||||
"addons/core/Missions/ERR_Everon_Base.conf.meta": "9b16267c644d815bcf98a3a3aaff30b93d7224fb24d3a2bac66730a28011f317",
|
||||
"addons/core/Prefabs/ERR/AssaultParachute.et": "b556997989605e2a05982418b7e9f26d12d427eb3ea6e3bc50e85c67ea7aa4b8",
|
||||
"addons/core/Prefabs/ERR/AssaultParachute.et.meta": "e9dea2d533ad1ce6ce689e0e2815c97a6382e1b90c35ce1efa88a885527e8e18",
|
||||
"addons/core/Prefabs/ERR/ConvoyCrew.et": "29a525a29605386dfcf292dc2316e9fc7650ce39db9fbaa4ce4372ad0de66db4",
|
||||
"addons/core/Prefabs/ERR/ConvoyCrew.et.meta": "0a909a15b7dcca34202ded136b3bdbbe8898153c9589e1cce1388433d362da02",
|
||||
"addons/core/Prefabs/ERR/Epinephrine.et": "e09c70b3d47a4dc5b92fd332c8222b99554b28a8a0c93cb5caac18eec4501571",
|
||||
"addons/core/Prefabs/ERR/Epinephrine.et.meta": "f1861f0b0ccace1b7b3d9c29768e37a83bf0c6fae5a411b1bd61d6b2eec7ed89",
|
||||
"addons/core/Prefabs/ERR/Extraction.et": "4d24c7811415c06386205e5bf5c132a23fcf21e96c75c7657ccd5f3ac914ac50",
|
||||
"addons/core/Prefabs/ERR/Extraction.et.meta": "c2a838633001e2e3a202d39cb33aa744ebcd996017ad032359069641b5d13853",
|
||||
"addons/core/Prefabs/ERR/FactionManager.et": "5a7b330f33956cc1781c8dc843e8186f2d37a98653ab36e7c42b9019df3c89f5",
|
||||
"addons/core/Prefabs/ERR/FactionManager.et.meta": "9bf53d5e4bcbe60dd1a4ece1409fc23c349055c860708620f5f6e9716290a97d",
|
||||
"addons/core/Prefabs/ERR/FieldCover.et": "e403d5cb3a5966f0cea81f15e0e825d482ba1a7a27b12498319ba45a414963b0",
|
||||
"addons/core/Prefabs/ERR/FieldCover.et.meta": "83461ffa76244e1d69f20b053a9741ccdf7d127009182bd540a7e5ba6150c3d9",
|
||||
"addons/core/Prefabs/ERR/GameMode.et": "4d3e5e2df28cbb8292c436ae05d4fc7e5ca789a123450a6968d9a718b6ecef20",
|
||||
"addons/core/Prefabs/ERR/GameMode.et.meta": "106769cd34525c9d440057072836cb7401a6752148c40edd7fe61c03bd58328c",
|
||||
"addons/core/Prefabs/ERR/Items/EmergencyRepairKit.et": "0157bc235eeb78a0e1373b561fd22a1dcb225c833ff7c2d4abae688bca0ec92a",
|
||||
"addons/core/Prefabs/ERR/Items/EmergencyRepairKit.et.meta": "da6f065cb2414b5a4cbe9ef0f0b1295b2466f26ad406c0bddb83845d4598efb3",
|
||||
"addons/core/Prefabs/ERR/MortarCrew.et": "21086e886fe3199624208c9a866f883ec109f7d08746c2b56b497e59857fb656",
|
||||
"addons/core/Prefabs/ERR/MortarCrew.et.meta": "fdfd4fcd21a67c3d698fa4aadc4e5c76e665cebf4be86013bc389e493fab63cc",
|
||||
"addons/core/Prefabs/ERR/ObjectiveDocument.et": "5118ecaf9d2038eea44a4b785f05d2e78781dc1387249aa58a6782c7e6752e14",
|
||||
"addons/core/Prefabs/ERR/ObjectiveDocument.et.meta": "eaba9903be147b98a1106e2273e2270773df73b9b22f2cd4e20136a8e281382e",
|
||||
"addons/core/Prefabs/ERR/Patrol.et": "d3eca8fe9197c57a27a285cdb3baf168b037b198d92b39df28a338ae2cfd0c74",
|
||||
"addons/core/Prefabs/ERR/Patrol.et.meta": "7c5da568fa2fcf0709ee2959253cb068d7c7ed7c2467af3b247a7e70efd96b69",
|
||||
"addons/core/Prefabs/ERR/Prison.et": "c0ca5219aa2d6322ba7d094b92000640b259112ff6611a745c0713c8875d09d4",
|
||||
"addons/core/Prefabs/ERR/Prison.et.meta": "2e426ef3de68f4107907854b51e818bc11d84649a01c538e3f18dd9ebec766bb",
|
||||
"addons/core/Prefabs/ERR/PrisonBackpack.et": "ae750f08fc9af645561f59d7e5809bd22f4cec26ca8fa6409d09730765958c91",
|
||||
"addons/core/Prefabs/ERR/PrisonBackpack.et.meta": "7da6aa45467250edc910ad7230dc244d1b041e8a6f55881eb3747ce51f9212ba",
|
||||
"addons/core/Prefabs/ERR/PrisonDoor.et": "b0ff94443fe334032113a663917de23f920a8b73ffadc9c8e9f11b545022b3d2",
|
||||
"addons/core/Prefabs/ERR/PrisonDoor.et.meta": "b8f3db52bf186426026668fbb9d633c3cbd4d438ab301fba880398f6a0e781c2",
|
||||
"addons/core/Prefabs/ERR/PrisonPatrol.et": "dc88c31cf5c415573a874f74e13572f0add9e6122c8fcad507fe82860d789c47",
|
||||
"addons/core/Prefabs/ERR/PrisonPatrol.et.meta": "5528e0d778dce5ed39c3e504c6427625527127b23269d20fbf12ca2bffe52ffe",
|
||||
"addons/core/Prefabs/ERR/PrisonWall.et": "9b410b7c85bb001be4601ad93f750e17b8318b850306d84f29c4dc1b02723d7c",
|
||||
"addons/core/Prefabs/ERR/PrisonWall.et.meta": "0294bc5dbb56f90f8153d62459bb0a6ad91d76a479812bbec9c7db182fdb58d6",
|
||||
"addons/core/Prefabs/ERR/Prisoner.et": "e6d345ac98ad9ec742bf6c3b8686839c3256df9833d31bb4bdf605cd41d0ba5c",
|
||||
"addons/core/Prefabs/ERR/Prisoner.et.meta": "974278ba49fafeed70b619a0067bbc70ae4d534be441c812ce2bef4a5fb3c022",
|
||||
"addons/core/Prefabs/ERR/Radio.et": "99b1d80b49d1e7f9d30aa4283772a36fd66e962369f288f5fe329c7fc911bb1b",
|
||||
"addons/core/Prefabs/ERR/Radio.et.meta": "bc91137b68ba550217914fb8892bb44d7e1920e4a1086f3e506623832a44212f",
|
||||
"addons/core/Prefabs/ERR/RadioCamp.et": "5e6216bf8e6e160da5d9f966c8f7c2081bf6dd16dfac1b89d682a14b3b8a6716",
|
||||
"addons/core/Prefabs/ERR/RadioCamp.et.meta": "3cb8d3d553ab6d8d111a74f721cf4e69ee1011cbb90248b83cdc7275697276bd",
|
||||
"addons/core/Prefabs/ERR/ReconCrew.et": "33953379ceb725bcd0cbe505cd643c94adaed17dbbfa95dadd404d6b8e345715",
|
||||
"addons/core/Prefabs/ERR/ReconCrew.et.meta": "2653e8c5b1cdb857de5badd5959ddd9826a6a298ab3c166628dbb835d7104d90",
|
||||
"addons/core/Prefabs/ERR/ReconHelicopter.et": "c609ee59ab145e0a1936f95726b86df643f5cc425c43924029e9c1abc69ebbd0",
|
||||
"addons/core/Prefabs/ERR/ReconHelicopter.et.meta": "d80f1c3d6a6f9104562b89729996c1c92ad240f08cdcf48d4c390a09d2047c08",
|
||||
"addons/core/Prefabs/ERR/ReconPilotEast.et": "d961f329823b991eafed80d085de1bd89b44a4575be98d8dac385d6b77741ba2",
|
||||
"addons/core/Prefabs/ERR/ReconPilotEast.et.meta": "d73df3c896359a54514d2efcd851f579feca7a2619b76c2a38f607649a31a4f1",
|
||||
"addons/core/Prefabs/ERR/ReconPilotWest.et": "8f8221927269352e55c8026ee5b00e026c281c79af6684b386f24e7dc4675fb8",
|
||||
"addons/core/Prefabs/ERR/ReconPilotWest.et.meta": "4785f23bb492c6af420ee7896ddbfc9767e0db1e06de95207df2dc949a1b1814",
|
||||
"addons/core/Prefabs/ERR/ScoutEast.et": "b4593aab494c4f21637757b3daff4f349da77b4c48ec5e8a4a2917a6a12cf811",
|
||||
"addons/core/Prefabs/ERR/ScoutEast.et.meta": "7c72af632537f687e92f30b52d97fc7d70dadd39d33f37e0b1004f9596f55012",
|
||||
"addons/core/Prefabs/ERR/ScoutGroup.et": "8dc0fce8ee3decc1c367411b00b6d8518df9d8458281cec0881464a2ce906af0",
|
||||
"addons/core/Prefabs/ERR/ScoutGroup.et.meta": "62e43fd152ce57aa4ceb2187a49e4aecdeb9f428b3a1e8318f86bb83dbf8a259",
|
||||
"addons/core/Prefabs/ERR/ScoutRadioPack.et": "f1c4911e2556c0e0caa8b896982190c5e7d0433e850b4610e19442caf9117607",
|
||||
"addons/core/Prefabs/ERR/ScoutRadioPack.et.meta": "825506e3d814d30a52d5dbf64124470641c07a0453543ba71426841e6c783635",
|
||||
"addons/core/Prefabs/ERR/ScoutWest.et": "2681b5a3f8621adb44195f0dbca6f3e79ef7decad8f9d0dd0e1a801ec2549627",
|
||||
"addons/core/Prefabs/ERR/ScoutWest.et.meta": "22a6607b4b8fe606ca75a634a0b2ac3178e7bedae7f9ff01e7a4989f3b39a3da",
|
||||
"addons/core/Prefabs/ERR/SpectatorCamera.et": "cd2dcaca0df4c39816e582c594591a535525ed77827ffa3de24d27b79996544d",
|
||||
"addons/core/Prefabs/ERR/SpectatorCamera.et.meta": "2a16558f93915e31908a674509e9c89976470f78a84a1174a64b94b68b8f77ad",
|
||||
"addons/core/Prefabs/ERR/SupplyCache.et": "4c97b0514fe7e723046f40f0b396f6573b12eb9981ca5df70b9e8aa3cf29323e",
|
||||
"addons/core/Prefabs/ERR/SupplyCache.et.meta": "2f5de3a4aab1e9d0e35521f48dc57b95bd6e449c383dec4f1c258e10295904cb",
|
||||
"addons/core/Prefabs/ERR/SupplySite.et": "f33681281d26676fac88736091834d5a7b87fe0fa01883582542df964dcfc694",
|
||||
"addons/core/Prefabs/ERR/SupplySite.et.meta": "b41944b12eb3528ff7a3ac94258b18c66ef161aa6a38894aee300a1ac4798630",
|
||||
"addons/core/Prefabs/ERR/TerrainConnector.et": "cf9cb28f5a3f41931380864a29ac979aa1d5e843456f564ac7a7de78f88e1aa8",
|
||||
"addons/core/Prefabs/ERR/TerrainConnector.et.meta": "9f37c979ae282aabca29ce84beb0db851ee8b9a19f7f3a64d387c175616127f3",
|
||||
"addons/core/Prefabs/ERR/TerrainTakeover.et": "6206fe2eb4e2c6c1a9e3ddf2898535ae08981c115adc6ddd926017ac20d4b535",
|
||||
"addons/core/Prefabs/ERR/TerrainTakeover.et.meta": "64e953e378ab1d2a75ee7246f876f4ef93ae8a4e3fddb5002d7ba37268cb959d",
|
||||
"addons/core/Prefabs/ERR/Vehicles/SupplyUAZ.et": "29f157849f413261494a63d4d5b0a97e8bbe61cb076efcd59d6c940653f23352",
|
||||
"addons/core/Prefabs/ERR/Vehicles/SupplyUAZ.et.meta": "544c94cf1c26757a10654de7856cd6cc48b821ce3bdc02add13cfe6514269915",
|
||||
"addons/core/Prefabs/ERR/Vehicles/SupplyUAZRearSeats.et": "cd4acdcecad299445952aea0adefc85f2b9e9b43f8186f100e0505c5621b3656",
|
||||
"addons/core/Prefabs/ERR/Vehicles/SupplyUAZRearSeats.et.meta": "123955da03462befb90e293b3bb856d49cc761166611b38ff47cb48ec2fbe95c",
|
||||
"addons/core/Prefabs/Items/Equipment/ERRM_SharedFieldMap.et": "30dee8e9eab459ea32a7d835a9fb89d43df47708057cab829f067b25a24cd581",
|
||||
"addons/core/Prefabs/Items/Equipment/ERRM_SharedFieldMap.et.meta": "59093e9072c50d99c86da7609c87541016da5bcf92b98d99db4c524e1c8e0e8d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Actions.c": "cabb21413c90b252d29708b62228d0679c51c76e39ee0e483953ad7061b2b9d6",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Actors.c": "980d30964845600a261693cc5c5553e1472e8e54867bdc06133ff5c7b36c9128",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AdminMenu.c": "3b96a99832ed40002598a186dd8de1d21410c2e690df37a6319fdbeeeb4eeab0",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AdminPlayerTools.c": "56780d78bf423f54f482d8620f821b8d3eb0199194482b4f12e37f5ec3f55ef5",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AdminProvider.c": "62e05aceb49da07fed7aa89fb6b2f8eb3efc373ac09eff286fba983c3ad570eb",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AdminService.c": "c3e008c8bbf538c5bca3a82a975decc7a283a38543a9d2639d90a81ce592c51e",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AdminSession.c": "89d92f138a201ad7e752e027ba3930a2e0bf647d7b3a2c576346c1467f8bea3a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AirfieldPlacement.c": "0c2f4d31a2abc8f3f90bda20546543f2a1cf9306e9bf93351dda1883b990770e",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_CasualtyGroundSupport.c": "26f0c7ebe8563bb022f7c383674e7d8997b0cc35c7ea321d39d74f2493c1ed9d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_CasualtyLoadingFeedback.c": "e46988b92243df19a5400eca7406d6969b8b1174d8e3efad48fa0a936cbfdd07",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_CasualtyLoadingGameMode.c": "f4cf19440583ac24346831a2d5f1d7f734c031e46dc266c499972329dd0533cf",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_CasualtyLoadingOwner.c": "6346a05ddda030f7d9b6f026f977d5f018ee16002fa519e8c49dcd700c323dc7",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_CasualtyLoadingService.c": "e6f22813f7a04e2c0ea000d054407191f2be8badfa6c51e062e9dbf84b95c67b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ChatClient.c": "4bf937ff74e0483c17fa0103f73bfee3afd8f67a37c0c248b08772a17bfcc349",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ContentDefinition.c": "bc1dd4ed27ec894cf4bd617fb226cb8140b544aaa9ef6d8654e815d81e7f6fd0",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ContentMenu.c": "c82b173496e7524b4570b2afc5493cb970252a19aed740a53011988db7700a01",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ContentPolicy.c": "3a4987345d5a7159234a7e1a08bf5ac40dd5a23031e9b1953fc43a0501d9924b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ContentSettings.c": "572cc36f88255a37e8f0e9f284fb96e07c6c4032506b34b8c77d688f02785400",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_DebugAI.c": "bc5529c60591a6c36c39cdb753b1af948690fc3c3c725f38a8d595b41a499235",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_DebugIntel.c": "a9f6e7e42a3214879a21052e897f9afc0653c12ff15d5bee7766e26a1af6ab17",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_DiscoveryMap.c": "dd350bdda1bdf427ab8cefab623fe60cce5332b062d1a29686fe52f73e9e83f1",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_DiscoveryService.c": "34458b3a4f7062375c9c5b5599308e9cea67fc4a316420836f6694ca2c646ffc",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_DocumentComponent.c": "885c8ab4fc9e64c3f8b40056536096c8c32b011cb340b25ca00353a96da15c69",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_EnvironmentService.c": "c0fae5bf51ccf507bc84d44adc2d1dff021cd83653c0468b7645a4f343e3fc8d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Epinephrine.c": "e04c53c2f665997023b2daee744265cd22f4f0101444e4d9b0efbd114f74fa9a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_EscapeGameOverScreenInfo.c": "6e3c65e55b05cf205040c310b9086faa41b88582c7f9faf49c010afd59da5b07",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ExtractionPlacement.c": "6d388fe21f08b25cbdb5dfa4e198d94a4c80c5092f8d17630a4654e18bf8b3f5",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ExtractionService.c": "91357099b4e0401196b67d48963be54be30e56bdba57cdac67dc20b8ebb883e9",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_FactionCatalog.c": "a5a8c53acf21259e14f59cb919535910f30dabff5a9179c664bc6a7a4d744d6e",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_FactionMenu.c": "3549339d7796e67fc646680493c226e545b6fb95daa0270af9901795bdf8ed50",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_FactionSelection.c": "077c09491f4444fc58f093975fe9b3c294ea1fc966abef847ee5f13aa795ee29",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ForceLedger.c": "0ecaf1c5a52568e751678b0b1236e91e6adb538f8fbf010d38b61a312d1bf253",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ForceSimulation.c": "2bbb8888c752a3ffc042f3059937526d25ae288af5b4db024cc683025a2605ce",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_GameMode.c": "89a29c03ba225acbbb9fa3c58a721aced91a77bc0c77046d697005a6d7c48bb9",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_GeneratedSites.c": "4a23058f27e3ab074df6f8f7bb6f08a8fd26fbadb8943c6fb8d64dcc9c221751",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_InitialGuardRules.c": "8c9d6b1dc4c2af96284e34f487eebb5a221091016ef108006dec60b4e3836558",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Inventory.c": "b63e679d5b650b63a7a91dfc3f42a62f4be58b0338f6df3313e66d29a93bebf6",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_InventoryLayout.c": "1af5df45a0f06d19a017954e9bf4b2d722e2d2389f8ffdeb1d024435b9a08be8",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ItemPreferences.c": "1c4bc25a6ce9b5ee10f82592f955b0019d13ddb4084a783fadb98d267bd0f4fc",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ItemStationTeardown.c": "102e61216e86e47ad243cc19f6f66da1fcd8cc77f8799354ceaeb4a6dc74622e",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_JournalText.c": "87a6c19429f12c5630ed73170ca9a8a6d2255d6cd38b130166a78aacd15f2de0",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_LoadCasualtyAction.c": "1521af55a34110ce1c971b5226a618a9c6d931622abaae4946d9c0d27c05a273",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_LocalAdmission.c": "a076922d1ae966cf5cc67b198166ca6b1288358fb7f641207a4681e6a610a2ae",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Location.c": "5b0b058130ccac02bdc1dcd6474d51a6f45da4dd52ed6c37906692fca3f194b1",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_MissionRules.c": "f7abc38ad8fdc375ce7fa63a65f0bade027d22b37e09de5e3f94ba8c3c63cc41",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeAdminMenuProbe.c": "ec8d04b6550b39b9688ef67b40cb2f14a9d98c8fff62b6dfb8d4a43e19b4e4c6",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeAdminSettingsProbe.c": "73e58561e08e50008d33f81eed606dd4e8109fb69a627e892d28ca468d641f7d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeAggressionProbe.c": "85a34cf60018e90d974cb67ab0f358f3073b6b9f02c563ec200e062f1d249e3b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeAirfieldMarkersProbe.c": "593e59664dbd7330b52674e5d4cc7b70db588cb75a1259ed808b74602501d541",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeBaseSmoke.c": "e811e985048e07365569acde6b5f045d57e9bbf13927f85b74e6e347fd2ef571",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeCasualtyGroundProbe.c": "41f40db3735f8aa535c0061458d9892dcfe972222b8a17238e67b65633c46bc3",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeCasualtyLoadProbe.c": "94c533deea654ec546df3f1ebb7cd213e6cd79b1125d99bab5303ef8e87b018b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeCasualtyRemoveProbe.c": "4cfc06f6d43b709b63caae714155ffc562502e884f49a9825623c6f46972ad36",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeCasualtyServiceProbe.c": "02a9d70d1f4ba20326df199ba41489cfbae12b7bb0aab441361421cee38ad9ea",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeContentProbe.c": "cb0ccfe1e1575f9ed80a8054058b471f3543378db059e6af2fed8eb42a262638",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeContractsProbe.c": "421e4b901afe6118b6bf44e8f678db8ccdf1c83b39da0cf816baa36f7275ccdd",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeDefinitionProbe.c": "904d8d4c3e7b91d49dadfe495fc7b4f02f300e4aaf7efa348df5ca17ec4a6e8a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeDiscoveryMapProbe.c": "9ed32d391d08d937ff4f9f33bd11c3a204ce9a2230c2d72b6761e0320b31f24f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeEndProbe.c": "2dec80c7e247ec5c257407f95d9d5edd42fe0cc44d6af9f724f6ebd10d8a355f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeEnemyLoadoutProbe.c": "0885ca69f6fa7f53414ce1792cfc3c49916886b8173783c634135c13c5d37429",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeEnvironmentProbe.c": "9e2e66ac5d9778a1c65445348e3300cbb90b68b3f669f3ddf1d2b78a858430eb",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeEnvironmentRestartProbe.c": "7a836a206381b8e8de24c92baf141beb30575723675dd6948d738169ed26b507",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeExtractionProbe.c": "4b09385f73206de004f4f3c38a6192fd658a0d2012482dc5a84dff1599ceb972",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeFactionCatalogProbe.c": "e3011fa47b67122d7b2c23af8f91b7a217599d6cc114c4f9de628744b9c948d3",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeFactionSelectionProbe.c": "946879d822e7be2d48e03a3140aa1aad3e3d44dd18507fa3cdedb4c6d7dd81b9",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeFlightFootprint.c": "a8862a04ac784426f431e2e9cd3f1f19f820b38a081674d29549b8b00277c72d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeFlightInputDiagnostics.c": "e231dcbdaefe81beb3bd587cb73be8fb3d3184bc86d18dbaa30a43ca4ba5dcc4",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeGeneratedSitesProbe.c": "83ab84ca4de7b545963896ed9d14e6545c23a2b940515730d74acc2f6b3dd18d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeGuardCancelProbe.c": "bfa1c65cb039e19c3d1891e4a3fd33469e974c97cc7f42128efc31a8f587d514",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeInfantryCombatProbe.c": "3e4c4382ab91f470357268927b954fd101685df6b2244df483f0422d6c0d7da2",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeItemStationTeardownProbe.c": "028588a49fa9207087cd27ec9396979cd932daa071e2490bd0262cd71dae30d9",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeLifecycleProbe.c": "b613a5dbe81c2032fd02d31070ae5631166d14d0b02cdbec3b63b782f0bbc115",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeLocalAdmissionProbe.c": "29c860ca3e51cdaa463f3c23a98ff45ee0499ce9bbc322046e0efbed587f1e09",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeMedicalCombatProbe.c": "3b9c557b249728c518f9d6b5cb24b725ecc32a69ae12df069cdfb0da70a80b95",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeMedicalProbe.c": "4b73e7cf35e9c8fddb089678a320ea58272e1b6611318d0a762754c171eff785",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeObjectiveLossProbe.c": "6b80fd317a8c6e8583bd4337870838a86251e3b8a681d0aa07e8b52e22abfc48",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeObjectiveMapProbe.c": "2d07b895cb74af58454605fb1c536a566733e3001a9375dd0677d5f274bf2a46",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeObjectiveProbe.c": "71fbd2479a70048f83d8b262ad1452f427be2c0afc8b071ef13bff477b9dc29b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeOpeningToolsProbe.c": "0c5d6b96ed5a7cb1cf0d098d534dc7190449ed09339062e6b584729d53a61b4c",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeOptionalSiteProbe.c": "19fc19e22348e6213827f620f4fdb5dc26144ea2ce0032de773e79c1fb155a26",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativePatrolProbe.c": "5a2af71cc4cfccc7dd7c23584398a38290eba1a8894cd46f6967252ff3a7087c",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativePoweredFlightProbe.c": "7ae5d6f85bbbebec214c66bfdf6f53b93c802f109fcd5bcfbad82c5d61d6cb9f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativePreparationStatusProbe.c": "456f4d7c3272b89d5f4c6713617baa5596d846994152356fee9a659839bcd8b5",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativePrisonBreachProbe.c": "4889e0d3799a6a21b9f84bcb2f2ae78dce16269cccd1329f8d1fcc1a23e7c22c",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativePrisonerProbe.c": "e2d1ffe5d269add55124601ff0d1dc032a398c7d51010736e6f0e4cbeb38ae86",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeProgressionIdentityProbe.c": "1d3fe20bce761a1082964bca9bad9ce79d1fa8b04db9b5053bae98da380ae560",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeRadioCampProbe.c": "67f1a088f9b20e5f0f11ed3aa083ffa772bffd7c74d85b7a307344ac3d47e09a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeRadioDefenceProbe.c": "ff1e27a0ce75b123a86e7df31354831880ca62ea90b6c5d7c60d3ba168e1fff0",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeRadioInteractionProbe.c": "7b27552d233813894f65c08eb9f04b547c4ca9ff96a2370a763cb94bd4a555c3",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeRecoveryProbe.c": "d6691f22432b3ce37d07d448aeb3ce7feb5cd09e852e0167b5e4676afd24bb21",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeResultProbe.c": "c9a89cae067f221b9f33965b4d3ab311b78ba79ea383ca65032c4001df44b472",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeSiteGarrisonsProbe.c": "1a5ff8747d7501862085b5e9162043fee3b05e82c73c516ad8093aaada53dc60",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeSiteSurvey.c": "ac0203753ade0cdff6d483132797a80e4520f14546c3f9dea288281cd4cd9c4f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeStationTeardownDiagnostic.c": "172248540932174341882861bb78a9720a6cf89981fcdf9dffe63f7fd4023f2c",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeTimeProbe.c": "4a2c6ffd947334a0a71eac083d10c853c524b00042e9ea0987497c4cee60c3d5",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeTransportProbe.c": "7a3fe04aa1c5ed1061ff63d9d5180e8b925932b62d76c1ffb49bd3dc23530993",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeTransportSurvey.c": "816df9a2a9072f9f7db75e56168d8a8fa1e665ee8f7dfa79af011b354254909d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeVehicleSecurityProbe.c": "59bce863142e4fba4b6fb2f544bb8a58061f0e99b1a38e5ac291cea919563763",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ObjectiveDetails.c": "628ebd166dcb5544c2d18dce489619984887ee18296cf2446fc6d894f89dd5bf",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ObjectiveMapPresentation.c": "f189f8987244e10df61ac577bbbc952ed5c4e6c431a8e3cd192a10fe2e8c3bec",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ObjectiveText.c": "8d75b35ab977be0cb1a2e3d3d1cb8ef875cdbfba69c35784c3d195451ef944c6",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Objectives.c": "b9cd5bb3b193c20401175c4328405be1b121e8a60b7b5999910b6a71f834af8a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c": "682aa5549470c69fc72d261fbfcf9f7a04a41a66e0101b269ccc00bc282ad05d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_OrdinaryGateObserver.c": "1e376ca83c0005cf532539e14d7f47363c47b7c5b03fe3d55405a59208705fd7",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_OrdinaryInputObserver.c": "808a46cc1e4b31b65dbbbcdd55493f594ebb3974bbec38b4a2402334c653c0ef",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_OrdinaryLookObserver.c": "d5a1b636938d65e9d11c78b31487f2924400c34d98e2ec812954a2a224da7dc0",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_OrdinaryScenarioLaunch.c": "a4e7e56d174ebd15b4730202dae4c8ed06144f3f27c23ed49c99e2d297dc421a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerAdminCommands.c": "eca9baa3b07da48a740851583540840f8bf5f65f7be854902a3778f76967faad",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerEscapeResult.c": "3124ab4d2d6d471409a47aba0af528790734df53243c03fe0cdac3e407047c14",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerGuide.c": "e49bab76a015ab545088af7e5eaa3cc45811907db4e5b1c5fa8c18afef34b62f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerMedical.c": "270454d50235bc28df9bba4fde2689934578874c4eca9560afb9f0f6324d7215",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerRoster.c": "c038dac0f749bd29ebc35a4e952904b41a34417ba738904d5efcd1833a263e0b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PreparationStatus.c": "43a406822a0361f916abc363f27e0c578ee94855036981d876bf47c68233c71a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonAI.c": "8709753e86dc2ca777183f08cb8fbb9851e0c0de495ca01520ebcb1b3dfa59b9",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonEscapeService.c": "535cf17e21fc8e62716c7d9da88d976ee2f526501c6a4752b822f1252e742bf4",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonPatrol.c": "284618391ee2da84dbcda4a99ca3c072eae3d67b8a9ef40a86827b9ea00810f9",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_RadioDefenceService.c": "f5f0bf1352141f933d0edb5630e30541e001dc07e1d2c8529b29cc159c126b13",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_RadioFeedback.c": "545262e65e9b3645d9e514a0521e442fe08f0c2e4e7c83ba638c5105d09818db",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_RecoveryService.c": "86f9a7769e3ccb19fc61127fe2fee2ad0dd825487a5cd59f334de8b9741c93cf",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Resources.c": "d0169aac6fd0c38928c47a9002ded0fd8ba558ea3314a600e792d307ac1ca3ee",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c": "81bf30d358c7b100248be17cbc2e6beb2f107300c939aede8a92d273eb7dae1f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ServerItemCatalog.c": "e306b9d179b97d0478f821be3ca606c71908a21a4f87085b37a5fd18c4e3575d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ServerItemsMenu.c": "c0c3d9d1cf4615f38c793f1250315bbdb9349f33afa0841851da89433a303c3e",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Settings.c": "1b466886e9f5fbde1de0be20395e3887046c6b5a9a239dae92cecbfb721714c2",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SiteDefinition.c": "2088c45798f0eced702e0abd97550b8d1fc97ebcc34c3d497978b7424f6c670d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SiteDiscoveryRules.c": "dcbce3b7aee89bfc47a50e9de89209758af1f1bb462b99af67a8ad7a26e9f229",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SiteService.c": "210646e679056f4a46ef454a5abe15bd42d48349aa376ccfbb6b3b2ebeac6812",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SiteSquads.c": "183883783a4e8399cf62851a4cc65a5d091d25131bc53b4323cf17286315b2e0",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnClientScene.c": "b56f99a91cdc53bdb93d320d10839339022d7e61e6b101eee8296271abd7d3cb",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnFinalization.c": "c949642366ad124dd2abc4111cd598eb94aa257fbae01b6fb3a0f1882a23e83e",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnLogic.c": "47a39c951be71bfcf1cc50526d507d0f9d8ef2fc09408697b6fe6b0dc5a529e2",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnPlacement.c": "270bf465808d23627a11e720c6632d4fea35d0c679e63aa64046ef8874d68ceb",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnSceneInventory.c": "76b79d5bd9d1f367e1ed19724302b7d49f8b0ede85cf19e89c452582ef651f8f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnSceneLease.c": "20de7f96bacc9037580d88d4424664b4554b2c082d82e7d29913e9dfe390b594",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Supplies.c": "752e8a56b0b8cde33cf532032cb0a203553017b686faac2e19bee06f7a9c0713",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainCatalog.c": "ba2e68543a8c929492d06f8725a743d26849a630d6f8c09be0f23894f92d0990",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainMenu.c": "1da542ef60ff7c1897b1f12432c636ef6a08cc710bac520ef186589d506e9a54",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainPreference.c": "ec25e5c3a5b97527f9cd70341c10d69d7124048089d26883ac2f8c9b3fd2815d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainSelection.c": "ee75cac068b99e7403f5eaabe556152513cf5d973602cc6e6b8967d25402f6ad",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainSites.c": "63ad027dac5bafb9e93805c361cbb27bf82af0567ea181a760ddedea98204c06",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeover.c": "487f1d004aa4f7a5f58c12c37724b26fcd54fc62b4096d48f3a7436d9958274c",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverClient.c": "458b84b3fb31c5a2bd1b7c3d65f25e0aeab91f5505ef0813231435c8a3c7d2b7",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverManagers.c": "5b7661a5617b7124a10d3c18e087a4e2363a967eecc5c87ecc94573f2abc07f2",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverPresentation.c": "d66a29a44e999cc3f2e56ba9c3c6bc936c027747312e08778ce301c1837f2112",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverSettings.c": "da22775930b13e41d4a71409e37af3bb99e4cc1d94bdea0e3b59fbdcd4d40132",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TimeService.c": "63867760876d523a1794f619118a52c5cd387d5d92756cd286b2c16a348a6c7b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_UnconsciousCameraProbe.c": "66b1c4591b0ecc67cb08418a115bb02ed2b0168b7ae970f1e0a1ea335c59dc5b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_UnconsciousWeapon.c": "24d31bc857486638e55c605283c17cde0cdbd3c5c6021eac1ec93611387b10d1",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_VehicleSecurityActions.c": "bd7d0aeb4abcc9d8044e4888f9a92f9e5fc97c098bdfa3bbad57e1a29697a779",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_VehicleSecurityComponent.c": "2ffec4e646584ec546147fc4b08249825cb32b531aba26467b586d760485adc3",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ViewDistance.c": "8514f72f8207f12570d2317e64094a45507a1647dfd45a1501499ef81e90189f",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AirAmmoMission.c": "0a471a40c8f7dfb797d8d35e41c5b1ff91e8d2ab13b43cf866e46c0e4ab4ed9d",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AirAssaultLoad.c": "5554994115cd7d56152ac78ba53034bf934b71c5634b17d35e96884dbb3b8a24",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AirAssaultSystem.c": "2eaf9816eee3e1cd2ac70744a6ef7ea5a90d0a3eebdf925e384028a7edde6a42",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AirReconSystem.c": "f8a0e3cf4552d8133efd33c2126ea15a7694676773a7047c15e00021c1d9721e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AirSupportPolicy.c": "b5dad011d5a0574a406d6469e0744a9d37700cb41930dada28c5143d556acf53",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftAmmo.c": "2be282406d0c45ab2e35b0ced791b5e494c662588f536701a4e82e6f5fb7c913",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftControlBridge.c": "b8f0141644d8f043c6db4c2d4bb93215c548e5c27ab5468c66e996d62bdf905a",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftFieldEnvelope.c": "0f53c7d9ab167a804eb904d63725decebb53fd5796fdf27864a75abed5679838",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftFieldFlight.c": "c04e264cff9963821852094c451502d31bf9c96d4deae2e8ff8a2373b53b5665",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftFieldRules.c": "30be68f4ca2db7e169f6f0432815821179cddf9159f7b150341dc8eb479bbd17",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftFlight.c": "aa25a8e67a30c48677bf528236402b7f8e5d815dc18df93b878520ad3d1fe8a7",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftFlightEnvelope.c": "8125cef61fb283d0d7c4dd2df3c04b062b448f1101fd48dd53f4f4097df5fef3",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftRouteEnvelope.c": "e8fcef844239c47a1f2f36be420afe943bc450091b05859435406b46c87fa511",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftRouteFlight.c": "1d8d92bf6d97429d68a9f62018232e90cc124cc8d7445efeabe863706a31e2b9",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftService.c": "3f05209227986b5c6bfb193f8f53783cfd4ef083bce898e22257c3e63daed913",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AirfieldSupport.c": "c6d88ede0b1b6bac5a7075b6e2e783ccfa8850210aabd28bfa4bcdb698cc5f87",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AssaultParachute.c": "a93c996059e7166988e9d1b0b498cbfd36f19a1cf8520cb4e9215a8884752e18",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AutomaticEquipment.c": "5832cbd3a43748e77f4e7f1b59361801c6d0445e0e15165e97d7a897b338709e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_BodyRecovery.c": "47d9374095d04f5f1339c220caf518a40938ffc20f891b1b51ae38579e7e1c25",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyBoardingProgress.c": "328f8a5eabb8174b7727eee1d675db83a2eb0be7b8e834db4e950a645ca5d7a3",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyBudget.c": "ca49c274146d56e30cc93fa1652cc34c0fe68ea95725b0b3e33ab3673a6463c4",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyCombat.c": "5c4b7576b28d51edea868cbb91a0ab31a40e6ddb558f1b8a38e07e20fdb2969d",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyMineStock.c": "4497d6b790a45df2b76d18541ddaf299b406def88b868caa29f096849026a42c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyPlacement.c": "4d0e8b6e147486fb65db82a1060f0c5641c1319ba9e418bdec99cb5a422a7fbe",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyRoadPlan.c": "fae404f85cae63e63976938a5fdb73efc988ba25c5846564f12edca04fb88551",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c": "b59016d83cb9d40136b98b4e0067ea490bc553574fc53603593efd5522dbed20",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyStartup.c": "360228fd45aa6ecd9426e36120b5e637d2feee1e3a4a89aac8207526b871c87e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyEquipment.c": "c52d4a3cc0e71eb136487c42df2fcd5bd6da1c33d31dbd3d6429c237dcf4a8d9",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyGunnerSkill.c": "adf8bac5a6f7e40b2f9caff5fe9be590c4e4ce2b2b387ba39e741a3c4f4cc564",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyLoadout.c": "a48f32dbd7f209bf08fce2840aaffc1781c571fc679763be58987f6b082e776c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyMapMarking.c": "0f161fd3e08a54e3e1063679127549e482c40253a022e9d06cd9ee4d740b8625",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyMedicine.c": "05d341a9a000bfa2084057bd67d476d6e35adca6c8518d74c27cd31b551bbb95",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyOptics.c": "759e0a88ff30cb781ee69b8a935865d63ede5b1d97f31d344018f650a967c37f",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyRadio.c": "3a97e5c3de9ff33873d97c6b2d84ae505cfc4f3146e047b928e76ec3b22c8789",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemySkillAwareness.c": "00c450d033e43fc45c9e2cb64a1d81161d123cc594a4292cfc5f68eb6c17ee60",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EngineerMine.c": "e0098577626e719283cf4b40936e7ec038076c4bab9269fbad0b5ad3292e01e3",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EngineerMineSite.c": "b629fecc29711e57f364f2a9c4c3e884b0978d9674311678f2e78b561683681a",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_FiniteMagazineReservation.c": "8278590ced1936d618905b0cbd0b5aebddda238d426a6a2a52e455fc36bbb502",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_GroundDepotHQ.c": "8748325b0810ea053b28d0ed4c00a75fceae1c0d1cd3b30efb4dafdc397632ac",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_GroundDepotLoading.c": "36da884a833e7a29743308490a3ce5ae299f7a451d87205a227163892aca4ed0",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_GroundDepotTransfer.c": "1946db0615130b0039b42ec80db12d9cc89b1b3ca5a7eca7d9112eb69c3bdcea",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c": "5d66c53baf8762e76ae59e28721edaa348014af6d988d42891d9a84a6cf44969",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQAdminProvider.c": "e9bb5f2f3ec270074964fc8607e5a07c23f224308a4446837598d93e201d7775",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQAmmoReporting.c": "95c024cbdd35c02d9b0c3d389d29bb042e5dda6ffd2f5f0d896078274b3adb35",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQAmmoSampler.c": "8bb0ec1422887feaa340307d3c60bae50fc82d424fce6390876e580ff8b75e8b",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQBodyObserver.c": "f59421c1e0c83f0f480d7dd4d048b4b0a41193ed8fe8f2f627d2b6a696420ea1",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQCoverage.c": "8d9668858c47b7e9fee2b12d3ebd4e7cb88146b817cf615c4b0dade26c81b2ec",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQDeliveredCasualties.c": "1f738127e55e843ee2705007cedb5c08741f52f63c75eca7c80242177af82273",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQDoctrine.c": "6335728e89ed40ce11b811f00b61bfea76e94dd3379e4357cbfc438b05522bb0",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQJournal.c": "53563ff54fc4e0656713d6eaec5e88ddf69fd4bfa8ef6a67cfa13380d24031e1",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQLocalAwareness.c": "124260280ac8eda0e920bfcfc86932c9b21df6b1f7a6aa8acfc43feea3da149f",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQLocalHandoff.c": "2dd700dae9d9b232d22ca63ff01af75a3f5cc213ac24b7c0a43ffa38bc0b9ffa",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMemory.c": "ec2047a477fb4273e22ae6bb68e7365f321f55c583b5d2c48c4abf2ac38e3b7d",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMineSampler.c": "2a0fdc4d27e947a1d77b02ae23f757c7fa074eb456f817b7df0766583d757f55",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedAmmoSampler.c": "88ea0fc0e13d7e3c22c7ce8b652816d1d137e4e6e37a72f48f0bdf515b936ae7",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": "cee3ae301ac2f211c290450b644a7942c9786a2a40d06f16e2a47efa96fd15b6",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c": "a37edcf66e43945f331db47fe81cb2e0e767a7aaf8115244dcf9e6295932be98",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQNetwork.c": "0b73be993628a55e3b21c8670d540e39c938093dd5cb4d586e800ac530509b25",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQObservation.c": "57ad3f0b68d150791e30a7e4bd7dfedfb1ddd78a1c76f228e67b33ea9cb7144e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c": "ab6ca0e1a7f36c104e664db06a74c80a528cbfd1de599af42e2194ef0a4f4ca3",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQReport.c": "12ea8ba08753d1e6d38b237087c1209cd2f9574b53c3ff0f27c082bed029c9ea",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQRoadIntercept.c": "0897e82d7805ef56f05f2b9c23903280285ea6bfa51796a2df86bb564ece78f7",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQStatusSampler.c": "b937eaed913a5095afa8e678dc1c36b5c805541bf8cd5be927069533d6baed1c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQTask.c": "1147904bae6d72e9de47ce17bdada3dab489de4ed6e781dbf15bdbb30599ff6e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQWatch.c": "abe634cf6003ae920ec1ed18e305ed462920204960894520d3dd11f7e5ef1c33",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HelicopterAvailability.c": "60eaef2f6d4b2fd44861c5a4434ef97e732e40a5ff7401e2da451ed2dd9c2762",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HunterCombat.c": "df1c319d10efd07ed6f971cf0e41d8d7e377b04ea7f0fe143bf94df733fd1d9c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HunterCrewRecovery.c": "e76af0fbf6f436a9c3c9d4c7b779bc08a78f6fe705c08bf6d9757bbeb65ee8eb",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HunterOpeningPlan.c": "0b6275221dab491f148426df05e132579f3acc20de5559226c3b38975618355c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HunterService.c": "79369d5cc30cf628f02df0023157ab423e40497325dfd6a73b218757f2b689d4",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoAccess.c": "ab4262f78d54782c5ecbf5fff4ebe3da16d72329658d7e18c0c35387dc8b3c7d",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoTransfer.c": "99fa26beb0c0869d0cd694500f3bfc0705c2c1cf674dea90d98fb93a59570ef7",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryCombat.c": "5684fcbee128a35107312452297f7ae2bec28aaa1b63f619fa649f36df8ea476",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryMineTransfer.c": "02c399413f80879c22b4891e8ec807c67a6235677095fc4b98bafdc360d6944c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c": "b01fa948f204d49acbfbdfc5fa641b1d914e0eb91081e92a93816ae52ae88b1e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantrySupplyPool.c": "c8e454573cf064ff392291d6feebf1f23be64390b5b8e944c03dd065f9f46b42",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarAimSolution.c": "59e8343d09f7a44871d8d3335d6ad1d02216abb5deed30d38b5ec9ba3f2c0649",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarBattery.c": "157024afc8fd3c1ffeea6ae923ff091e5773b9ffcf5f5864f8040d001e689110",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarCourierTransfer.c": "968e58ca700b4a11e5e04a5c159696f514a0b1bce531210172b913e495f7e2b7",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarFiring.c": "f2597d44c10d10de2462aa5e5450a22b8a5ad0eb99bd0cc09a8cf1ca2dd4a350",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarLogistics.c": "b3624e4a00a2bdef16f6c100bbd79910e3bb8c2ae8ec5fe2ad5aa70bffecd303",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarPrediction.c": "05863b2b4f9f70cdb288f91c722cbea163c13f2a2927dc485482e5bd4fa7d57f",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarReadiness.c": "4c3799b3f2681e977669de0cbfaeb307d9a682b04365bfc9f59079e762965758",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarRefresh.c": "52ae8d9d6cad92f42d68c634091b80a474d2666b6c06d63a8c30a3fab71494c4",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarReportLedger.c": "42a96040b63e56677353e55164ae23031c90f70b4d6673a9f412ad7b19e636ae",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarSalvo.c": "c128850c7a78a4d8fee00ad38f48b265f1fbd1d9b24b092ddc5c205b0f0ad6da",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarStation.c": "cbbd7c86f722b664c85ab7c070ae1131d2ce5c41d1dacb4381bff9c1720a1866",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarSupplyTransfer.c": "ad71084f507ad9c32d3055e7ec6844a1ec5ddf568c0216ab8391e902b56e417c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarSystem.c": "bc290ee8bed299d869598cbf41ec281653e0cc7f3c64c7214613df8a0fc83829",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoPickup.c": "5f7ca5b31f3b405c2a6ca32ff91042f5b4dac6ab9640ce67fb10d7a74236ba69",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoTransfer.c": "0b195784497672fdaf651f336c14f7be98c257f98c85cfbeb4f96fe1a401999b",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MountedMagazineSeed.c": "b42d0aac142907433df8bedc40de24c94813544016cf5f451846f1980250f995",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MountedResupply.c": "c26ad43705765be1c20eca37fbe2978b8c647151f6620ed37141657a4b7dc312",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeAirAssaultProbe.c": "c1d55b338b157b758e6ffb993d5f689029b28b13929797c91e5dcb15b5e561ea",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeAirLogisticsProbe.c": "58fe863775d0ad575cb566c78cef696c9826691cf49ef9adde76376be7316db1",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeAirReconProbe.c": "c1216e0512a0268e4dbb82429e228334401c0c3cac52ba4486ae6dc91e41576d",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeAircraftControlProbe.c": "0cf3e72ccdc42e6ef20fb71d743f0e037a9e69b2e082c720fa705628cf790205",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeAircraftFieldProbe.c": "b64907c7566a894ca2f3c49ede5d972d4087a938da1a0ca87bcbd7c2203b2c52",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeAircraftRouteProbe.c": "8d0dca97e5106d1c84b64992c789e1af0689514beefc027e713ca6ab901fc470",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeMortarCourierProbe.c": "740c8bebbd4128a9311b1167d1058549475d6b46f81c9b6c0d19650f4b50616f",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeMortarFiringProbe.c": "ee570d21bede8baebefc799399712d9582b3e30ebda7299f6c941777f02ad2a2",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeMortarSalvoProbe.c": "fb9ccc6b36247ab3ad828ffbb2556abf5c2838d93ea6cd1ad259c0a015142f5e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeMortarStationProbe.c": "a110f0abc753cb1d2f781e457e637dd5346ecd2e924eb5eb3a5c7dca3d1e2340",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeMortarTransferProbe.c": "399c21940ef10777b46fdb3e905430ce43404ce1de2f4a6bb62f9c61c1a0689c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativePatrolHQProbe.c": "a58f57530e333976dc96fc5a0f9cb6cf0cb069c55788824950c5fb2f965d8e26",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeScoutProbe.c": "41b39b1abe5e51a5b8a428d533cccbfb0dda9d7a570228dc3489408c60543f17",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeTownProbe.c": "ade130f49fc9d868281cee7fde383f084f70ed8d4b876dacebbaed8538e84999",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ObjectiveDefence.c": "83ef91c2118521aedb4c1838b2e47909c80e0950086449b98ced168f1fd87080",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ReconAirports.c": "7551347d776d6e0eec818d6468cacbca7ac53daee7317e0a1618ca3d39ebc762",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ReconCrewLocks.c": "684bc5b9858c7ed90ed2ca6d52fc0e731a7e37a116db776090257b72a63621ab",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ReconFlightComponent.c": "4966dad504b3f8f28a9ffc8ac17cc548b00066fe05213c49c662ea56de34c9d4",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ScoutSystem.c": "63287a252e4e7d5aefce840279f26ce3e1f286cb79cadb32596c37897cec7119",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SettlementSafety.c": "e5c0ebc95521a9a33a3f7d9cca6c551dda862a08b7af20b017877aba9584795b",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyColumn.c": "35678ec625fb0a3c0871e2c373fcff49816440a97a165ed25c8798dd72ef059f",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyService.c": "5b1578afbf6da736453cc9d3872979eddd1649746aa32d3b0c7a9e3cab50057e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoySystem.c": "f09aa817c27d6af0ccfb544f4ab1ae2afafb63d626641e03d87cfe159475d821",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyTactics.c": "dc3acdffc8ca48f11b131f66d30d679ca68f3b3004344bdc78723bc9305915e0",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SupportScenario.c": "a7b46253e1bb37cea7c29bb557fbf43ca207022d0f1cc927349441b2d1615279",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_TownGarrisons.c": "57f862476751087972fc78d0b27ab44e72361e27d32f15d55f9fe7fd7d6bd0e0",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_VehicleAlarmPerception.c": "45ac71d25e10a057a26a5b0fc7128935baaf47f4c0f87208a4420fa52086fd98",
|
||||
"addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelAdminProvider.c": "d63ab5c547021aeb61d398b8a773afeedee4662d3ce15d9fd9d0e9e37262f8b7",
|
||||
"addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelDiscoveryFeedback.c": "62f2c408383f410782d9d41fa50acd6bd8cf66eeb80d2b79f91852b9d2cf8d97",
|
||||
"addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelEvidence.c": "6bbf8d3b83e2c09fd9a23a9a5e5e11d7c29af2e618116bee613562fc45af2b33",
|
||||
"addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelReroll.c": "d8b04de9ea789732cc78ebce9aa60786c86fe46d05f671cf251eefa68a214b59",
|
||||
"addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelService.c": "2911325c79a7a372628b18ffa0e110d11919ce2c2cd7b19277bfd608fdb03dfc",
|
||||
"addons/core/Scripts/Game/ERR/20_Intel/ERR_MineIntel.c": "3ce16cebaa9703b2f9803df7c11614f94de6460a9136dab8f80dcad41ae2b1c2",
|
||||
"addons/core/Scripts/Game/ERR/30_Map/ERRM_NativeMapOwnershipProbe.c": "32418602f085e3edba7343f42a6a691630b533be262002f29248e8ae87a38254",
|
||||
"addons/core/Scripts/Game/ERR/30_Map/ERRM_SharedMapComponent.c": "3a10f6ea25bf88a400bc3e6c339823433845c5db4f2f423835ccf99a1b1a843f",
|
||||
"addons/core/Scripts/Game/ERR/30_Map/ERR_ScenarioMap.c": "75fa1d742129ef7f33c005cd48f56db7f49ee7bde3c5428532b0b501b032b0a3",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_AdminCommands.c": "282fe1463add7af8771aa58507b254e69d2d06f50274d4c77321db073f8394f0",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeAlarmHearingProbe.c": "5dbb6a48909b76cd141e3a79ef01d2e68cafee55153821c4471f85c761fff94f",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeConvoyCoverProbe.c": "d2450140cf9bdf09210473234605b4c0d818ea5b91f9532209166a35c5d13701",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeConvoyProbe.c": "4bdc920e87fa768b0ef0ed893c70ddbbcc9255d74163d7b657fd2ec7c4f14c32",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeHQBodyProbe.c": "811244cc8297817efbfc3ee4edf01c868eae23c88127ba0c72e98ee7422da72f",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeHQMemoryProbe.c": "d8fec6c138bb0f5bdbf99ee82ffc3ac9b44d1045d8833da63f8aa5f39beeb7e5",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeHQResponseProof.c": "0f8b09fd8753234c91fd6baaca0e2f812e409ae1c4be8f340b648ae59b69c2f3",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeInfantryTransferProbe.c": "9b2de4dc22bdadd51b0271219a971dc8dc07f70ed3534f306acf03ed81b29424",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeLocalHandoffProbe.c": "0d4d61c424a16008595f86fa61f3b848caf9bd6e9ebbacea9c10254d88ba378e",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMaintenanceProbe.c": "852b9b397efc3f919c97fec63bc3df1a850cd7833421af8441994afd191951d4",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMortarProbe.c": "ddb892141500c192c0787b011d57dd8c07dd8c9a3ca5fdc407f6c271897bde0a",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMountedAmmoProbe.c": "8d197f7a1398625e5a19db3284b0bf72c6ee6e032843a3e4d9a4e58de42b6825",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMountedCourierProbe.c": "12710e9e5548fe8856f7a52f3566338369a8a1eedf0bd92af16229271872ada5",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMountedInterceptProbe.c": "34b0b1e38a59f14c4e83d93ec412779945388e6f4d697bda2f6cb90f519a6372",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMountedSwapProbe.c": "a6721fcaaccadf8eddb06687c7d587d0a3e377d5d1bb0b8e72e630fede8690ac",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeRadioProbe.c": "84524163c63402755b4f834f74dfe6d8e5c397dc26031f532593c6f79f5a93b1",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c": "254d1192138dd9864048693dd02d78b317e874cef57fc0b13226768e024d58be",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSupplyConvoyProbe.c": "701311de6a2c9fe8f0534a22056323c34072df26595dc70a7d4df45bc06e6c13",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_OrdinaryScenarioLaunchChat.c": "a747af09599ce4d0c52051f18f00c4624d24d137e328341b580b82e896c6bf65",
|
||||
"addons/core/Scripts/GameLib/ERR/00_Core/ERR_DiagnosticNoSplash.c": "0ea1b402f8927b6d68bf6f7e5caa2967f7e3a39147634faab147796b791933b5",
|
||||
"addons/core/Scripts/WorkbenchGame/ERR/00_Core/ERR_ValidateBaseWorldPlugin.c": "49a38c37b51e5318c7dcbcb01543dd9a6300d053f0d39808b3974f4edc45b871",
|
||||
"addons/core/Scripts/WorkbenchGame/ERR/40_Chat/ERR_ValidateWorldPlugin.c": "f567518b54f43b88747df2838cdfd6ebb34605c0919a3e7d271d28dd8b67a9b6",
|
||||
"addons/core/UI/layouts/ERR/AdminMenu.layout": "e8d570f7bf3b8c4067ad81d47bca7f35514b2f8c8748f5fa5f775fbe27debeac",
|
||||
"addons/core/UI/layouts/ERR/AdminMenu.layout.meta": "9deeece996f43b1f1fed6b5ec89279645c7b6424250200a42580cc088142e9bf",
|
||||
"addons/core/UI/layouts/ERR/AdminValue.layout": "cd811af6d2fb187f01c74e20ea07ad85669546b0ad70efae2ce52921ce26cbca",
|
||||
"addons/core/UI/layouts/ERR/AdminValue.layout.meta": "56b3d2335db400c1308e07c4a35d278b23ef32013dfd6dc82951866327428d08",
|
||||
"addons/core/UI/layouts/ERR/ContentRow.layout": "62d2c95814c99d906266fa96c6112e04a7fab1667e1a05e558d1d3974adc2d76",
|
||||
"addons/core/UI/layouts/ERR/ContentRow.layout.meta": "8c4bc13b41e7aee38e0ca640a97813ed1a6964a00e2cff9e1fe4ebd71fc2fdc6",
|
||||
"addons/core/UI/layouts/ERR/ItemRow.layout": "8ac6fbc7fe0ce0ffc3185516197303bb892a4415948f75ae06f3bc289fefcac6",
|
||||
"addons/core/UI/layouts/ERR/ItemRow.layout.meta": "33eaef9c403ea3a030e58ec17f78e46af457048243571ad5856fff04101fb60b",
|
||||
"addons/core/UI/layouts/ERR/PauseModsButton.layout": "d21d436291e2c1b262e33dc228b0d60a1d188dc9e85cfda6d2373f8281431b79",
|
||||
"addons/core/UI/layouts/ERR/PauseModsButton.layout.meta": "9ed6adabd8f53eb9a562d7186789876bba5c4202155ca894c1af866ca47354f3",
|
||||
"addons/core/UI/layouts/ERR/PreparationStatus.layout": "bd269cf8dc6f27735423743dd5b78242550518414448a0256e5e12fa6b92c459",
|
||||
"addons/core/UI/layouts/ERR/PreparationStatus.layout.meta": "75b12176a443bb911fc4ad446982d762201fda42ed38736b79bc5439fca31ccc",
|
||||
"addons/core/UI/layouts/ERRMap/ObjectiveMapElement.layout": "b364752c44896d67dad07e466a04f4504a50813fb36ee7c34c9189ea9677b4e9",
|
||||
"addons/core/UI/layouts/ERRMap/ObjectiveMapElement.layout.meta": "8691fe2eaf46568bca4a9f8128b247a8da810bb87a5546aa7ca3334e740270d6",
|
||||
"addons/core/UI/layouts/ERRMap/SharedMapRenderTarget.layout": "6e83a9c70282aff43b23a08b732739e7a241a31e8abf200498c90a745d3d60dd",
|
||||
"addons/core/UI/layouts/ERRMap/SharedMapRenderTarget.layout.meta": "5c846445cc089e2e08899ba703059d4adb805ff30ab5a16409a3cd59718cbd54",
|
||||
"addons/core/Worlds/ERR_Everon.ent": "22324bc7eb0d80dec8a1f3bd61a9df9201b72f749f4e751766b0c9d0b7d46b87",
|
||||
"addons/core/Worlds/ERR_Everon.ent.meta": "19d1903ca72baa326b4724a6791d0da1104a1e53f3f31dc1ca41a856efd68e83",
|
||||
"addons/core/Worlds/ERR_Everon_Layers/default.layer": "6bcb7fbb27b6282719dae8a37486e518d4cbaea385f2ea9af1918b36bb28b175",
|
||||
"addons/core/addon.gproj": "73e973a1f1c5910393e16f86a3a40d792031bb2fa3fc7f4dd87a06a16c3e34a4"
|
||||
}
|
||||
@@ -0,0 +1,978 @@
|
||||
{
|
||||
"addons/core/AI/BehaviorTrees/ERR/MortarRapidFire.bt": "16b263bcb2fb3ae361367a600753a829425620d2e6b350a891c51fba47437ab6",
|
||||
"addons/core/AI/BehaviorTrees/ERR/MortarRapidFire.bt.meta": "f2a44ece28891240e6bfa1d01af29428adcadbe82c80e287eb33cd5e58f1f90a",
|
||||
"addons/core/Configs/ERR/AdminMenus.conf": "a7f889f260e43656136fc159342129e5f48748d28c426f65f9faac37b624cf94",
|
||||
"addons/core/Configs/ERR/AdminMenus.conf.meta": "5faf5ddc0a51c1d0f82d60af00d91edc22fe9c39aac50d8d04b772a2c9271be1",
|
||||
"addons/core/Configs/ERR/Content/Registry.conf": "546a5a407c9e6c25c9157ae4392898fa3f40c04c7956649e62201f9ac0ba187c",
|
||||
"addons/core/Configs/ERR/Content/Registry.conf.meta": "046aa988bf8e972afcf7a60b6f2312f605f016f98625dbbd9cd75ebf9ea88800",
|
||||
"addons/core/Configs/ERR/EveronSites.conf": "b479000b36154a6a3af6b519b086251d981ef63cb86a973d00529499cfd4238c",
|
||||
"addons/core/Configs/ERR/EveronSites.conf.meta": "d5fe68025ef4e7c1e16082cf5873b7c7172eacbfaef4ab29b7e0e3c98f8dca56",
|
||||
"addons/core/Configs/ERR/Factions/MissionVehicles.conf": "5f74f4a7089c7e7b54ad62463159ce8f0bab25a6b85268dc3872ea6001e048fb",
|
||||
"addons/core/Configs/ERR/Factions/MissionVehicles.conf.meta": "00c368b6271d3440cc8bf627b03a384d247635723923fc5ad2104b8b4007b035",
|
||||
"addons/core/Configs/ERR/Factions/US.conf": "4c3da063abb6e3ee39a6e4e0c5a9ffd5eae9d209d58470ea18b45dba5528db1b",
|
||||
"addons/core/Configs/ERR/Factions/US.conf.meta": "a05469aef028e9e17eba545e81e2af2887b59f0fef17dac51a23c7b8155e381b",
|
||||
"addons/core/Configs/ERR/Factions/USSR.conf": "407ea27f80a64c5184c089d43cdd0bdabfc5974c84f1d2a245081fdf2afa3f30",
|
||||
"addons/core/Configs/ERR/Factions/USSR.conf.meta": "057397d97096346791b51b7b13fbe18af218bcf78e9984d9c01b49a2403de611",
|
||||
"addons/core/Configs/ERR/GameOverScreens.conf": "45231061911a1462d45fb6db0fecc2b0793f17b5ae71017174954ff4fbd20236",
|
||||
"addons/core/Configs/ERR/GameOverScreens.conf.meta": "eb654b745c0b97b41c991694569ef4877a3c2315e0826f9556337cd68690843e",
|
||||
"addons/core/Configs/ERR/ObserverInput.conf": "e898d645911e85b6ba902366cb23890d8bb14bbd2c485102e6d49ea728e9b054",
|
||||
"addons/core/Configs/ERR/ObserverInput.conf.meta": "9714dce6c38ee81bcc513c8ebef51d7af63fd15bcdda2e40946937c625dc3ba4",
|
||||
"addons/core/Configs/ERR/Tests/LegacySites.conf": "3864eb638d0ed4dd313013d2d787f975ae6ce4bbe238569919f76b73d5f7ca42",
|
||||
"addons/core/Configs/ERR/Tests/LegacySites.conf.meta": "097ac7daef4fc18344699a8471fdd77f9d158f8c70bab394068d7a08e4777ac0",
|
||||
"addons/core/Configs/ERR/VanillaFactions.conf": "db890b61b48ee15484041fd2245a862d9fa9e7ba21b1229c281ff37c448a35b1",
|
||||
"addons/core/Configs/ERR/VanillaFactions.conf.meta": "ac77725c0c21974991ef47a466b88122e939de8b46fd23e68889e7674cbb2bfd",
|
||||
"addons/core/Materials/ERRM_MapRenderTarget.emat": "9877c5b175c5f30dd80bea36c711b21ab38219aa7075adf9ae79932e1d4c76bc",
|
||||
"addons/core/Materials/ERRM_MapRenderTarget.emat.meta": "5ee1a705f8011a44020ca2691f5ccc17d44bf596fef9a36e98d1f267eb58fab0",
|
||||
"addons/core/Missions/ERR_Everon.conf": "936619df7789a6b4b80bd763f702526e1145ab4d5c300516c07e7e70dfe57d92",
|
||||
"addons/core/Missions/ERR_Everon.conf.meta": "0a23c98bf83873536c65fa17d38dea33a1d7b9e4ab210f90633ff688abcc551f",
|
||||
"addons/core/Missions/ERR_Everon_Base.conf": "b46e1f4aeef383a8dbe7ef2b5c12a55a8111856c52ba103a1e508c01b82dfc61",
|
||||
"addons/core/Missions/ERR_Everon_Base.conf.meta": "9b16267c644d815bcf98a3a3aaff30b93d7224fb24d3a2bac66730a28011f317",
|
||||
"addons/core/Prefabs/ERR/AssaultParachute.et": "b556997989605e2a05982418b7e9f26d12d427eb3ea6e3bc50e85c67ea7aa4b8",
|
||||
"addons/core/Prefabs/ERR/AssaultParachute.et.meta": "e9dea2d533ad1ce6ce689e0e2815c97a6382e1b90c35ce1efa88a885527e8e18",
|
||||
"addons/core/Prefabs/ERR/ConvoyCrew.et": "29a525a29605386dfcf292dc2316e9fc7650ce39db9fbaa4ce4372ad0de66db4",
|
||||
"addons/core/Prefabs/ERR/ConvoyCrew.et.meta": "0a909a15b7dcca34202ded136b3bdbbe8898153c9589e1cce1388433d362da02",
|
||||
"addons/core/Prefabs/ERR/Epinephrine.et": "e09c70b3d47a4dc5b92fd332c8222b99554b28a8a0c93cb5caac18eec4501571",
|
||||
"addons/core/Prefabs/ERR/Epinephrine.et.meta": "f1861f0b0ccace1b7b3d9c29768e37a83bf0c6fae5a411b1bd61d6b2eec7ed89",
|
||||
"addons/core/Prefabs/ERR/Extraction.et": "4d24c7811415c06386205e5bf5c132a23fcf21e96c75c7657ccd5f3ac914ac50",
|
||||
"addons/core/Prefabs/ERR/Extraction.et.meta": "c2a838633001e2e3a202d39cb33aa744ebcd996017ad032359069641b5d13853",
|
||||
"addons/core/Prefabs/ERR/FactionManager.et": "5a7b330f33956cc1781c8dc843e8186f2d37a98653ab36e7c42b9019df3c89f5",
|
||||
"addons/core/Prefabs/ERR/FactionManager.et.meta": "9bf53d5e4bcbe60dd1a4ece1409fc23c349055c860708620f5f6e9716290a97d",
|
||||
"addons/core/Prefabs/ERR/FieldCover.et": "e403d5cb3a5966f0cea81f15e0e825d482ba1a7a27b12498319ba45a414963b0",
|
||||
"addons/core/Prefabs/ERR/FieldCover.et.meta": "83461ffa76244e1d69f20b053a9741ccdf7d127009182bd540a7e5ba6150c3d9",
|
||||
"addons/core/Prefabs/ERR/GameMode.et": "4d3e5e2df28cbb8292c436ae05d4fc7e5ca789a123450a6968d9a718b6ecef20",
|
||||
"addons/core/Prefabs/ERR/GameMode.et.meta": "106769cd34525c9d440057072836cb7401a6752148c40edd7fe61c03bd58328c",
|
||||
"addons/core/Prefabs/ERR/Items/EmergencyRepairKit.et": "0157bc235eeb78a0e1373b561fd22a1dcb225c833ff7c2d4abae688bca0ec92a",
|
||||
"addons/core/Prefabs/ERR/Items/EmergencyRepairKit.et.meta": "da6f065cb2414b5a4cbe9ef0f0b1295b2466f26ad406c0bddb83845d4598efb3",
|
||||
"addons/core/Prefabs/ERR/MortarCrew.et": "21086e886fe3199624208c9a866f883ec109f7d08746c2b56b497e59857fb656",
|
||||
"addons/core/Prefabs/ERR/MortarCrew.et.meta": "fdfd4fcd21a67c3d698fa4aadc4e5c76e665cebf4be86013bc389e493fab63cc",
|
||||
"addons/core/Prefabs/ERR/ObjectiveDocument.et": "5118ecaf9d2038eea44a4b785f05d2e78781dc1387249aa58a6782c7e6752e14",
|
||||
"addons/core/Prefabs/ERR/ObjectiveDocument.et.meta": "eaba9903be147b98a1106e2273e2270773df73b9b22f2cd4e20136a8e281382e",
|
||||
"addons/core/Prefabs/ERR/Patrol.et": "d3eca8fe9197c57a27a285cdb3baf168b037b198d92b39df28a338ae2cfd0c74",
|
||||
"addons/core/Prefabs/ERR/Patrol.et.meta": "7c5da568fa2fcf0709ee2959253cb068d7c7ed7c2467af3b247a7e70efd96b69",
|
||||
"addons/core/Prefabs/ERR/Prison.et": "c0ca5219aa2d6322ba7d094b92000640b259112ff6611a745c0713c8875d09d4",
|
||||
"addons/core/Prefabs/ERR/Prison.et.meta": "2e426ef3de68f4107907854b51e818bc11d84649a01c538e3f18dd9ebec766bb",
|
||||
"addons/core/Prefabs/ERR/PrisonBackpack.et": "ae750f08fc9af645561f59d7e5809bd22f4cec26ca8fa6409d09730765958c91",
|
||||
"addons/core/Prefabs/ERR/PrisonBackpack.et.meta": "7da6aa45467250edc910ad7230dc244d1b041e8a6f55881eb3747ce51f9212ba",
|
||||
"addons/core/Prefabs/ERR/PrisonDoor.et": "b0ff94443fe334032113a663917de23f920a8b73ffadc9c8e9f11b545022b3d2",
|
||||
"addons/core/Prefabs/ERR/PrisonDoor.et.meta": "b8f3db52bf186426026668fbb9d633c3cbd4d438ab301fba880398f6a0e781c2",
|
||||
"addons/core/Prefabs/ERR/PrisonPatrol.et": "dc88c31cf5c415573a874f74e13572f0add9e6122c8fcad507fe82860d789c47",
|
||||
"addons/core/Prefabs/ERR/PrisonPatrol.et.meta": "5528e0d778dce5ed39c3e504c6427625527127b23269d20fbf12ca2bffe52ffe",
|
||||
"addons/core/Prefabs/ERR/PrisonWall.et": "9b410b7c85bb001be4601ad93f750e17b8318b850306d84f29c4dc1b02723d7c",
|
||||
"addons/core/Prefabs/ERR/PrisonWall.et.meta": "0294bc5dbb56f90f8153d62459bb0a6ad91d76a479812bbec9c7db182fdb58d6",
|
||||
"addons/core/Prefabs/ERR/Prisoner.et": "e6d345ac98ad9ec742bf6c3b8686839c3256df9833d31bb4bdf605cd41d0ba5c",
|
||||
"addons/core/Prefabs/ERR/Prisoner.et.meta": "974278ba49fafeed70b619a0067bbc70ae4d534be441c812ce2bef4a5fb3c022",
|
||||
"addons/core/Prefabs/ERR/Radio.et": "99b1d80b49d1e7f9d30aa4283772a36fd66e962369f288f5fe329c7fc911bb1b",
|
||||
"addons/core/Prefabs/ERR/Radio.et.meta": "bc91137b68ba550217914fb8892bb44d7e1920e4a1086f3e506623832a44212f",
|
||||
"addons/core/Prefabs/ERR/RadioCamp.et": "5e6216bf8e6e160da5d9f966c8f7c2081bf6dd16dfac1b89d682a14b3b8a6716",
|
||||
"addons/core/Prefabs/ERR/RadioCamp.et.meta": "3cb8d3d553ab6d8d111a74f721cf4e69ee1011cbb90248b83cdc7275697276bd",
|
||||
"addons/core/Prefabs/ERR/ReconCrew.et": "33953379ceb725bcd0cbe505cd643c94adaed17dbbfa95dadd404d6b8e345715",
|
||||
"addons/core/Prefabs/ERR/ReconCrew.et.meta": "2653e8c5b1cdb857de5badd5959ddd9826a6a298ab3c166628dbb835d7104d90",
|
||||
"addons/core/Prefabs/ERR/ReconHelicopter.et": "c609ee59ab145e0a1936f95726b86df643f5cc425c43924029e9c1abc69ebbd0",
|
||||
"addons/core/Prefabs/ERR/ReconHelicopter.et.meta": "d80f1c3d6a6f9104562b89729996c1c92ad240f08cdcf48d4c390a09d2047c08",
|
||||
"addons/core/Prefabs/ERR/ReconPilotEast.et": "d961f329823b991eafed80d085de1bd89b44a4575be98d8dac385d6b77741ba2",
|
||||
"addons/core/Prefabs/ERR/ReconPilotEast.et.meta": "d73df3c896359a54514d2efcd851f579feca7a2619b76c2a38f607649a31a4f1",
|
||||
"addons/core/Prefabs/ERR/ReconPilotWest.et": "8f8221927269352e55c8026ee5b00e026c281c79af6684b386f24e7dc4675fb8",
|
||||
"addons/core/Prefabs/ERR/ReconPilotWest.et.meta": "4785f23bb492c6af420ee7896ddbfc9767e0db1e06de95207df2dc949a1b1814",
|
||||
"addons/core/Prefabs/ERR/ScoutEast.et": "b4593aab494c4f21637757b3daff4f349da77b4c48ec5e8a4a2917a6a12cf811",
|
||||
"addons/core/Prefabs/ERR/ScoutEast.et.meta": "7c72af632537f687e92f30b52d97fc7d70dadd39d33f37e0b1004f9596f55012",
|
||||
"addons/core/Prefabs/ERR/ScoutGroup.et": "8dc0fce8ee3decc1c367411b00b6d8518df9d8458281cec0881464a2ce906af0",
|
||||
"addons/core/Prefabs/ERR/ScoutGroup.et.meta": "62e43fd152ce57aa4ceb2187a49e4aecdeb9f428b3a1e8318f86bb83dbf8a259",
|
||||
"addons/core/Prefabs/ERR/ScoutRadioPack.et": "f1c4911e2556c0e0caa8b896982190c5e7d0433e850b4610e19442caf9117607",
|
||||
"addons/core/Prefabs/ERR/ScoutRadioPack.et.meta": "825506e3d814d30a52d5dbf64124470641c07a0453543ba71426841e6c783635",
|
||||
"addons/core/Prefabs/ERR/ScoutWest.et": "2681b5a3f8621adb44195f0dbca6f3e79ef7decad8f9d0dd0e1a801ec2549627",
|
||||
"addons/core/Prefabs/ERR/ScoutWest.et.meta": "22a6607b4b8fe606ca75a634a0b2ac3178e7bedae7f9ff01e7a4989f3b39a3da",
|
||||
"addons/core/Prefabs/ERR/SpectatorCamera.et": "cd2dcaca0df4c39816e582c594591a535525ed77827ffa3de24d27b79996544d",
|
||||
"addons/core/Prefabs/ERR/SpectatorCamera.et.meta": "2a16558f93915e31908a674509e9c89976470f78a84a1174a64b94b68b8f77ad",
|
||||
"addons/core/Prefabs/ERR/SupplyCache.et": "4c97b0514fe7e723046f40f0b396f6573b12eb9981ca5df70b9e8aa3cf29323e",
|
||||
"addons/core/Prefabs/ERR/SupplyCache.et.meta": "2f5de3a4aab1e9d0e35521f48dc57b95bd6e449c383dec4f1c258e10295904cb",
|
||||
"addons/core/Prefabs/ERR/SupplySite.et": "f33681281d26676fac88736091834d5a7b87fe0fa01883582542df964dcfc694",
|
||||
"addons/core/Prefabs/ERR/SupplySite.et.meta": "b41944b12eb3528ff7a3ac94258b18c66ef161aa6a38894aee300a1ac4798630",
|
||||
"addons/core/Prefabs/ERR/TerrainConnector.et": "cf9cb28f5a3f41931380864a29ac979aa1d5e843456f564ac7a7de78f88e1aa8",
|
||||
"addons/core/Prefabs/ERR/TerrainConnector.et.meta": "9f37c979ae282aabca29ce84beb0db851ee8b9a19f7f3a64d387c175616127f3",
|
||||
"addons/core/Prefabs/ERR/TerrainTakeover.et": "6206fe2eb4e2c6c1a9e3ddf2898535ae08981c115adc6ddd926017ac20d4b535",
|
||||
"addons/core/Prefabs/ERR/TerrainTakeover.et.meta": "64e953e378ab1d2a75ee7246f876f4ef93ae8a4e3fddb5002d7ba37268cb959d",
|
||||
"addons/core/Prefabs/ERR/Vehicles/SupplyUAZ.et": "29f157849f413261494a63d4d5b0a97e8bbe61cb076efcd59d6c940653f23352",
|
||||
"addons/core/Prefabs/ERR/Vehicles/SupplyUAZ.et.meta": "544c94cf1c26757a10654de7856cd6cc48b821ce3bdc02add13cfe6514269915",
|
||||
"addons/core/Prefabs/ERR/Vehicles/SupplyUAZRearSeats.et": "cd4acdcecad299445952aea0adefc85f2b9e9b43f8186f100e0505c5621b3656",
|
||||
"addons/core/Prefabs/ERR/Vehicles/SupplyUAZRearSeats.et.meta": "123955da03462befb90e293b3bb856d49cc761166611b38ff47cb48ec2fbe95c",
|
||||
"addons/core/Prefabs/Items/Equipment/ERRM_SharedFieldMap.et": "30dee8e9eab459ea32a7d835a9fb89d43df47708057cab829f067b25a24cd581",
|
||||
"addons/core/Prefabs/Items/Equipment/ERRM_SharedFieldMap.et.meta": "59093e9072c50d99c86da7609c87541016da5bcf92b98d99db4c524e1c8e0e8d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Actions.c": "cabb21413c90b252d29708b62228d0679c51c76e39ee0e483953ad7061b2b9d6",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Actors.c": "980d30964845600a261693cc5c5553e1472e8e54867bdc06133ff5c7b36c9128",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AdminMenu.c": "3b96a99832ed40002598a186dd8de1d21410c2e690df37a6319fdbeeeb4eeab0",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AdminPlayerTools.c": "56780d78bf423f54f482d8620f821b8d3eb0199194482b4f12e37f5ec3f55ef5",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AdminProvider.c": "62e05aceb49da07fed7aa89fb6b2f8eb3efc373ac09eff286fba983c3ad570eb",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AdminService.c": "c3e008c8bbf538c5bca3a82a975decc7a283a38543a9d2639d90a81ce592c51e",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AdminSession.c": "89d92f138a201ad7e752e027ba3930a2e0bf647d7b3a2c576346c1467f8bea3a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AirfieldPlacement.c": "0c2f4d31a2abc8f3f90bda20546543f2a1cf9306e9bf93351dda1883b990770e",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_CasualtyGroundSupport.c": "26f0c7ebe8563bb022f7c383674e7d8997b0cc35c7ea321d39d74f2493c1ed9d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_CasualtyLoadingFeedback.c": "e46988b92243df19a5400eca7406d6969b8b1174d8e3efad48fa0a936cbfdd07",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_CasualtyLoadingGameMode.c": "f4cf19440583ac24346831a2d5f1d7f734c031e46dc266c499972329dd0533cf",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_CasualtyLoadingOwner.c": "6346a05ddda030f7d9b6f026f977d5f018ee16002fa519e8c49dcd700c323dc7",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_CasualtyLoadingService.c": "e6f22813f7a04e2c0ea000d054407191f2be8badfa6c51e062e9dbf84b95c67b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ChatClient.c": "4bf937ff74e0483c17fa0103f73bfee3afd8f67a37c0c248b08772a17bfcc349",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ContentDefinition.c": "bc1dd4ed27ec894cf4bd617fb226cb8140b544aaa9ef6d8654e815d81e7f6fd0",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ContentMenu.c": "c82b173496e7524b4570b2afc5493cb970252a19aed740a53011988db7700a01",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ContentPolicy.c": "3a4987345d5a7159234a7e1a08bf5ac40dd5a23031e9b1953fc43a0501d9924b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ContentSettings.c": "572cc36f88255a37e8f0e9f284fb96e07c6c4032506b34b8c77d688f02785400",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_DebugAI.c": "bc5529c60591a6c36c39cdb753b1af948690fc3c3c725f38a8d595b41a499235",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_DebugIntel.c": "a9f6e7e42a3214879a21052e897f9afc0653c12ff15d5bee7766e26a1af6ab17",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_DiscoveryMap.c": "dd350bdda1bdf427ab8cefab623fe60cce5332b062d1a29686fe52f73e9e83f1",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_DiscoveryService.c": "34458b3a4f7062375c9c5b5599308e9cea67fc4a316420836f6694ca2c646ffc",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_DocumentComponent.c": "885c8ab4fc9e64c3f8b40056536096c8c32b011cb340b25ca00353a96da15c69",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_EnvironmentService.c": "c0fae5bf51ccf507bc84d44adc2d1dff021cd83653c0468b7645a4f343e3fc8d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Epinephrine.c": "e04c53c2f665997023b2daee744265cd22f4f0101444e4d9b0efbd114f74fa9a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_EscapeGameOverScreenInfo.c": "6e3c65e55b05cf205040c310b9086faa41b88582c7f9faf49c010afd59da5b07",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ExtractionPlacement.c": "6d388fe21f08b25cbdb5dfa4e198d94a4c80c5092f8d17630a4654e18bf8b3f5",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ExtractionService.c": "91357099b4e0401196b67d48963be54be30e56bdba57cdac67dc20b8ebb883e9",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_FactionCatalog.c": "a5a8c53acf21259e14f59cb919535910f30dabff5a9179c664bc6a7a4d744d6e",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_FactionMenu.c": "3549339d7796e67fc646680493c226e545b6fb95daa0270af9901795bdf8ed50",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_FactionSelection.c": "077c09491f4444fc58f093975fe9b3c294ea1fc966abef847ee5f13aa795ee29",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ForceLedger.c": "0ecaf1c5a52568e751678b0b1236e91e6adb538f8fbf010d38b61a312d1bf253",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ForceSimulation.c": "2bbb8888c752a3ffc042f3059937526d25ae288af5b4db024cc683025a2605ce",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_GameMode.c": "89a29c03ba225acbbb9fa3c58a721aced91a77bc0c77046d697005a6d7c48bb9",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_GeneratedSites.c": "4a23058f27e3ab074df6f8f7bb6f08a8fd26fbadb8943c6fb8d64dcc9c221751",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_InitialGuardRules.c": "8c9d6b1dc4c2af96284e34f487eebb5a221091016ef108006dec60b4e3836558",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Inventory.c": "b63e679d5b650b63a7a91dfc3f42a62f4be58b0338f6df3313e66d29a93bebf6",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_InventoryLayout.c": "1af5df45a0f06d19a017954e9bf4b2d722e2d2389f8ffdeb1d024435b9a08be8",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ItemPreferences.c": "1c4bc25a6ce9b5ee10f82592f955b0019d13ddb4084a783fadb98d267bd0f4fc",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ItemStationTeardown.c": "102e61216e86e47ad243cc19f6f66da1fcd8cc77f8799354ceaeb4a6dc74622e",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_JournalText.c": "87a6c19429f12c5630ed73170ca9a8a6d2255d6cd38b130166a78aacd15f2de0",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_LoadCasualtyAction.c": "1521af55a34110ce1c971b5226a618a9c6d931622abaae4946d9c0d27c05a273",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_LocalAdmission.c": "a076922d1ae966cf5cc67b198166ca6b1288358fb7f641207a4681e6a610a2ae",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Location.c": "5b0b058130ccac02bdc1dcd6474d51a6f45da4dd52ed6c37906692fca3f194b1",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_MissionRules.c": "f7abc38ad8fdc375ce7fa63a65f0bade027d22b37e09de5e3f94ba8c3c63cc41",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeAdminMenuProbe.c": "ec8d04b6550b39b9688ef67b40cb2f14a9d98c8fff62b6dfb8d4a43e19b4e4c6",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeAdminSettingsProbe.c": "73e58561e08e50008d33f81eed606dd4e8109fb69a627e892d28ca468d641f7d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeAggressionProbe.c": "85a34cf60018e90d974cb67ab0f358f3073b6b9f02c563ec200e062f1d249e3b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeAirfieldMarkersProbe.c": "593e59664dbd7330b52674e5d4cc7b70db588cb75a1259ed808b74602501d541",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeBaseSmoke.c": "e811e985048e07365569acde6b5f045d57e9bbf13927f85b74e6e347fd2ef571",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeCasualtyGroundProbe.c": "41f40db3735f8aa535c0061458d9892dcfe972222b8a17238e67b65633c46bc3",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeCasualtyLoadProbe.c": "94c533deea654ec546df3f1ebb7cd213e6cd79b1125d99bab5303ef8e87b018b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeCasualtyRemoveProbe.c": "4cfc06f6d43b709b63caae714155ffc562502e884f49a9825623c6f46972ad36",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeCasualtyServiceProbe.c": "02a9d70d1f4ba20326df199ba41489cfbae12b7bb0aab441361421cee38ad9ea",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeContentProbe.c": "cb0ccfe1e1575f9ed80a8054058b471f3543378db059e6af2fed8eb42a262638",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeContractsProbe.c": "421e4b901afe6118b6bf44e8f678db8ccdf1c83b39da0cf816baa36f7275ccdd",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeDefinitionProbe.c": "904d8d4c3e7b91d49dadfe495fc7b4f02f300e4aaf7efa348df5ca17ec4a6e8a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeDiscoveryMapProbe.c": "9ed32d391d08d937ff4f9f33bd11c3a204ce9a2230c2d72b6761e0320b31f24f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeEndProbe.c": "2dec80c7e247ec5c257407f95d9d5edd42fe0cc44d6af9f724f6ebd10d8a355f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeEnemyLoadoutProbe.c": "0885ca69f6fa7f53414ce1792cfc3c49916886b8173783c634135c13c5d37429",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeEnvironmentProbe.c": "9e2e66ac5d9778a1c65445348e3300cbb90b68b3f669f3ddf1d2b78a858430eb",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeEnvironmentRestartProbe.c": "7a836a206381b8e8de24c92baf141beb30575723675dd6948d738169ed26b507",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeExtractionProbe.c": "4b09385f73206de004f4f3c38a6192fd658a0d2012482dc5a84dff1599ceb972",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeFactionCatalogProbe.c": "e3011fa47b67122d7b2c23af8f91b7a217599d6cc114c4f9de628744b9c948d3",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeFactionSelectionProbe.c": "946879d822e7be2d48e03a3140aa1aad3e3d44dd18507fa3cdedb4c6d7dd81b9",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeFlightFootprint.c": "a8862a04ac784426f431e2e9cd3f1f19f820b38a081674d29549b8b00277c72d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeFlightInputDiagnostics.c": "e231dcbdaefe81beb3bd587cb73be8fb3d3184bc86d18dbaa30a43ca4ba5dcc4",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeGeneratedSitesProbe.c": "83ab84ca4de7b545963896ed9d14e6545c23a2b940515730d74acc2f6b3dd18d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeGuardCancelProbe.c": "bfa1c65cb039e19c3d1891e4a3fd33469e974c97cc7f42128efc31a8f587d514",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeInfantryCombatProbe.c": "3e4c4382ab91f470357268927b954fd101685df6b2244df483f0422d6c0d7da2",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeItemStationTeardownProbe.c": "028588a49fa9207087cd27ec9396979cd932daa071e2490bd0262cd71dae30d9",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeLifecycleProbe.c": "b613a5dbe81c2032fd02d31070ae5631166d14d0b02cdbec3b63b782f0bbc115",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeLocalAdmissionProbe.c": "29c860ca3e51cdaa463f3c23a98ff45ee0499ce9bbc322046e0efbed587f1e09",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeMedicalCombatProbe.c": "3b9c557b249728c518f9d6b5cb24b725ecc32a69ae12df069cdfb0da70a80b95",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeMedicalProbe.c": "4b73e7cf35e9c8fddb089678a320ea58272e1b6611318d0a762754c171eff785",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeObjectiveLossProbe.c": "6b80fd317a8c6e8583bd4337870838a86251e3b8a681d0aa07e8b52e22abfc48",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeObjectiveMapProbe.c": "2d07b895cb74af58454605fb1c536a566733e3001a9375dd0677d5f274bf2a46",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeObjectiveProbe.c": "71fbd2479a70048f83d8b262ad1452f427be2c0afc8b071ef13bff477b9dc29b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeOpeningToolsProbe.c": "0c5d6b96ed5a7cb1cf0d098d534dc7190449ed09339062e6b584729d53a61b4c",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeOptionalSiteProbe.c": "19fc19e22348e6213827f620f4fdb5dc26144ea2ce0032de773e79c1fb155a26",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativePatrolProbe.c": "5a2af71cc4cfccc7dd7c23584398a38290eba1a8894cd46f6967252ff3a7087c",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativePoweredFlightProbe.c": "7ae5d6f85bbbebec214c66bfdf6f53b93c802f109fcd5bcfbad82c5d61d6cb9f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativePreparationStatusProbe.c": "456f4d7c3272b89d5f4c6713617baa5596d846994152356fee9a659839bcd8b5",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativePrisonBreachProbe.c": "4889e0d3799a6a21b9f84bcb2f2ae78dce16269cccd1329f8d1fcc1a23e7c22c",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativePrisonerProbe.c": "e2d1ffe5d269add55124601ff0d1dc032a398c7d51010736e6f0e4cbeb38ae86",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeProgressionIdentityProbe.c": "1d3fe20bce761a1082964bca9bad9ce79d1fa8b04db9b5053bae98da380ae560",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeRadioCampProbe.c": "67f1a088f9b20e5f0f11ed3aa083ffa772bffd7c74d85b7a307344ac3d47e09a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeRadioDefenceProbe.c": "ff1e27a0ce75b123a86e7df31354831880ca62ea90b6c5d7c60d3ba168e1fff0",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeRadioInteractionProbe.c": "7b27552d233813894f65c08eb9f04b547c4ca9ff96a2370a763cb94bd4a555c3",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeRecoveryProbe.c": "d6691f22432b3ce37d07d448aeb3ce7feb5cd09e852e0167b5e4676afd24bb21",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeResultProbe.c": "c9a89cae067f221b9f33965b4d3ab311b78ba79ea383ca65032c4001df44b472",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeSiteGarrisonsProbe.c": "1a5ff8747d7501862085b5e9162043fee3b05e82c73c516ad8093aaada53dc60",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeSiteSurvey.c": "ac0203753ade0cdff6d483132797a80e4520f14546c3f9dea288281cd4cd9c4f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeStationTeardownDiagnostic.c": "172248540932174341882861bb78a9720a6cf89981fcdf9dffe63f7fd4023f2c",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeTimeProbe.c": "4a2c6ffd947334a0a71eac083d10c853c524b00042e9ea0987497c4cee60c3d5",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeTransportProbe.c": "7a3fe04aa1c5ed1061ff63d9d5180e8b925932b62d76c1ffb49bd3dc23530993",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeTransportSurvey.c": "816df9a2a9072f9f7db75e56168d8a8fa1e665ee8f7dfa79af011b354254909d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeVehicleSecurityProbe.c": "59bce863142e4fba4b6fb2f544bb8a58061f0e99b1a38e5ac291cea919563763",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ObjectiveDetails.c": "628ebd166dcb5544c2d18dce489619984887ee18296cf2446fc6d894f89dd5bf",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ObjectiveMapPresentation.c": "f189f8987244e10df61ac577bbbc952ed5c4e6c431a8e3cd192a10fe2e8c3bec",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ObjectiveText.c": "8d75b35ab977be0cb1a2e3d3d1cb8ef875cdbfba69c35784c3d195451ef944c6",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Objectives.c": "b9cd5bb3b193c20401175c4328405be1b121e8a60b7b5999910b6a71f834af8a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c": "682aa5549470c69fc72d261fbfcf9f7a04a41a66e0101b269ccc00bc282ad05d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_OrdinaryGateObserver.c": "1e376ca83c0005cf532539e14d7f47363c47b7c5b03fe3d55405a59208705fd7",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_OrdinaryInputObserver.c": "808a46cc1e4b31b65dbbbcdd55493f594ebb3974bbec38b4a2402334c653c0ef",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_OrdinaryLookObserver.c": "d5a1b636938d65e9d11c78b31487f2924400c34d98e2ec812954a2a224da7dc0",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_OrdinaryScenarioLaunch.c": "a4e7e56d174ebd15b4730202dae4c8ed06144f3f27c23ed49c99e2d297dc421a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerAdminCommands.c": "eca9baa3b07da48a740851583540840f8bf5f65f7be854902a3778f76967faad",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerEscapeResult.c": "3124ab4d2d6d471409a47aba0af528790734df53243c03fe0cdac3e407047c14",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerGuide.c": "e49bab76a015ab545088af7e5eaa3cc45811907db4e5b1c5fa8c18afef34b62f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerMedical.c": "270454d50235bc28df9bba4fde2689934578874c4eca9560afb9f0f6324d7215",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerRoster.c": "c038dac0f749bd29ebc35a4e952904b41a34417ba738904d5efcd1833a263e0b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PreparationStatus.c": "43a406822a0361f916abc363f27e0c578ee94855036981d876bf47c68233c71a",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonAI.c": "8709753e86dc2ca777183f08cb8fbb9851e0c0de495ca01520ebcb1b3dfa59b9",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonEscapeService.c": "535cf17e21fc8e62716c7d9da88d976ee2f526501c6a4752b822f1252e742bf4",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonPatrol.c": "284618391ee2da84dbcda4a99ca3c072eae3d67b8a9ef40a86827b9ea00810f9",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_RadioDefenceService.c": "f5f0bf1352141f933d0edb5630e30541e001dc07e1d2c8529b29cc159c126b13",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_RadioFeedback.c": "545262e65e9b3645d9e514a0521e442fe08f0c2e4e7c83ba638c5105d09818db",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_RecoveryService.c": "86f9a7769e3ccb19fc61127fe2fee2ad0dd825487a5cd59f334de8b9741c93cf",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Resources.c": "d0169aac6fd0c38928c47a9002ded0fd8ba558ea3314a600e792d307ac1ca3ee",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c": "81bf30d358c7b100248be17cbc2e6beb2f107300c939aede8a92d273eb7dae1f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ServerItemCatalog.c": "e306b9d179b97d0478f821be3ca606c71908a21a4f87085b37a5fd18c4e3575d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ServerItemsMenu.c": "c0c3d9d1cf4615f38c793f1250315bbdb9349f33afa0841851da89433a303c3e",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Settings.c": "1b466886e9f5fbde1de0be20395e3887046c6b5a9a239dae92cecbfb721714c2",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SiteDefinition.c": "2088c45798f0eced702e0abd97550b8d1fc97ebcc34c3d497978b7424f6c670d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SiteDiscoveryRules.c": "dcbce3b7aee89bfc47a50e9de89209758af1f1bb462b99af67a8ad7a26e9f229",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SiteService.c": "210646e679056f4a46ef454a5abe15bd42d48349aa376ccfbb6b3b2ebeac6812",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SiteSquads.c": "183883783a4e8399cf62851a4cc65a5d091d25131bc53b4323cf17286315b2e0",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnClientScene.c": "b56f99a91cdc53bdb93d320d10839339022d7e61e6b101eee8296271abd7d3cb",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnFinalization.c": "c949642366ad124dd2abc4111cd598eb94aa257fbae01b6fb3a0f1882a23e83e",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnLogic.c": "47a39c951be71bfcf1cc50526d507d0f9d8ef2fc09408697b6fe6b0dc5a529e2",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnPlacement.c": "270bf465808d23627a11e720c6632d4fea35d0c679e63aa64046ef8874d68ceb",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnSceneInventory.c": "76b79d5bd9d1f367e1ed19724302b7d49f8b0ede85cf19e89c452582ef651f8f",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnSceneLease.c": "20de7f96bacc9037580d88d4424664b4554b2c082d82e7d29913e9dfe390b594",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Supplies.c": "752e8a56b0b8cde33cf532032cb0a203553017b686faac2e19bee06f7a9c0713",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainCatalog.c": "ba2e68543a8c929492d06f8725a743d26849a630d6f8c09be0f23894f92d0990",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainMenu.c": "1da542ef60ff7c1897b1f12432c636ef6a08cc710bac520ef186589d506e9a54",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainPreference.c": "ec25e5c3a5b97527f9cd70341c10d69d7124048089d26883ac2f8c9b3fd2815d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainSelection.c": "ee75cac068b99e7403f5eaabe556152513cf5d973602cc6e6b8967d25402f6ad",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainSites.c": "63ad027dac5bafb9e93805c361cbb27bf82af0567ea181a760ddedea98204c06",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeover.c": "487f1d004aa4f7a5f58c12c37724b26fcd54fc62b4096d48f3a7436d9958274c",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverClient.c": "458b84b3fb31c5a2bd1b7c3d65f25e0aeab91f5505ef0813231435c8a3c7d2b7",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverManagers.c": "5b7661a5617b7124a10d3c18e087a4e2363a967eecc5c87ecc94573f2abc07f2",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverPresentation.c": "d66a29a44e999cc3f2e56ba9c3c6bc936c027747312e08778ce301c1837f2112",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverSettings.c": "da22775930b13e41d4a71409e37af3bb99e4cc1d94bdea0e3b59fbdcd4d40132",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TimeService.c": "63867760876d523a1794f619118a52c5cd387d5d92756cd286b2c16a348a6c7b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_UnconsciousCameraProbe.c": "66b1c4591b0ecc67cb08418a115bb02ed2b0168b7ae970f1e0a1ea335c59dc5b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_UnconsciousWeapon.c": "24d31bc857486638e55c605283c17cde0cdbd3c5c6021eac1ec93611387b10d1",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_VehicleSecurityActions.c": "bd7d0aeb4abcc9d8044e4888f9a92f9e5fc97c098bdfa3bbad57e1a29697a779",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_VehicleSecurityComponent.c": "2ffec4e646584ec546147fc4b08249825cb32b531aba26467b586d760485adc3",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ViewDistance.c": "8514f72f8207f12570d2317e64094a45507a1647dfd45a1501499ef81e90189f",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AirAmmoMission.c": "0a471a40c8f7dfb797d8d35e41c5b1ff91e8d2ab13b43cf866e46c0e4ab4ed9d",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AirAssaultLoad.c": "5554994115cd7d56152ac78ba53034bf934b71c5634b17d35e96884dbb3b8a24",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AirAssaultSystem.c": "2eaf9816eee3e1cd2ac70744a6ef7ea5a90d0a3eebdf925e384028a7edde6a42",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AirReconSystem.c": "f8a0e3cf4552d8133efd33c2126ea15a7694676773a7047c15e00021c1d9721e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AirSupportPolicy.c": "b5dad011d5a0574a406d6469e0744a9d37700cb41930dada28c5143d556acf53",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftAmmo.c": "2be282406d0c45ab2e35b0ced791b5e494c662588f536701a4e82e6f5fb7c913",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftControlBridge.c": "b8f0141644d8f043c6db4c2d4bb93215c548e5c27ab5468c66e996d62bdf905a",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftFieldEnvelope.c": "0f53c7d9ab167a804eb904d63725decebb53fd5796fdf27864a75abed5679838",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftFieldFlight.c": "c04e264cff9963821852094c451502d31bf9c96d4deae2e8ff8a2373b53b5665",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftFieldRules.c": "30be68f4ca2db7e169f6f0432815821179cddf9159f7b150341dc8eb479bbd17",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftFlight.c": "aa25a8e67a30c48677bf528236402b7f8e5d815dc18df93b878520ad3d1fe8a7",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftFlightEnvelope.c": "8125cef61fb283d0d7c4dd2df3c04b062b448f1101fd48dd53f4f4097df5fef3",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftRouteEnvelope.c": "e8fcef844239c47a1f2f36be420afe943bc450091b05859435406b46c87fa511",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftRouteFlight.c": "1d8d92bf6d97429d68a9f62018232e90cc124cc8d7445efeabe863706a31e2b9",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftService.c": "3f05209227986b5c6bfb193f8f53783cfd4ef083bce898e22257c3e63daed913",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AirfieldSupport.c": "c6d88ede0b1b6bac5a7075b6e2e783ccfa8850210aabd28bfa4bcdb698cc5f87",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AssaultParachute.c": "a93c996059e7166988e9d1b0b498cbfd36f19a1cf8520cb4e9215a8884752e18",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_AutomaticEquipment.c": "5832cbd3a43748e77f4e7f1b59361801c6d0445e0e15165e97d7a897b338709e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_BodyRecovery.c": "47d9374095d04f5f1339c220caf518a40938ffc20f891b1b51ae38579e7e1c25",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyBoardingProgress.c": "328f8a5eabb8174b7727eee1d675db83a2eb0be7b8e834db4e950a645ca5d7a3",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyBudget.c": "ca49c274146d56e30cc93fa1652cc34c0fe68ea95725b0b3e33ab3673a6463c4",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyCombat.c": "5c4b7576b28d51edea868cbb91a0ab31a40e6ddb558f1b8a38e07e20fdb2969d",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyMineStock.c": "4497d6b790a45df2b76d18541ddaf299b406def88b868caa29f096849026a42c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyPlacement.c": "4d0e8b6e147486fb65db82a1060f0c5641c1319ba9e418bdec99cb5a422a7fbe",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyRoadPlan.c": "fae404f85cae63e63976938a5fdb73efc988ba25c5846564f12edca04fb88551",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c": "b59016d83cb9d40136b98b4e0067ea490bc553574fc53603593efd5522dbed20",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyStartup.c": "360228fd45aa6ecd9426e36120b5e637d2feee1e3a4a89aac8207526b871c87e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyEquipment.c": "c52d4a3cc0e71eb136487c42df2fcd5bd6da1c33d31dbd3d6429c237dcf4a8d9",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyGunnerSkill.c": "adf8bac5a6f7e40b2f9caff5fe9be590c4e4ce2b2b387ba39e741a3c4f4cc564",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyLoadout.c": "a48f32dbd7f209bf08fce2840aaffc1781c571fc679763be58987f6b082e776c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyMapMarking.c": "0f161fd3e08a54e3e1063679127549e482c40253a022e9d06cd9ee4d740b8625",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyMedicine.c": "05d341a9a000bfa2084057bd67d476d6e35adca6c8518d74c27cd31b551bbb95",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyOptics.c": "759e0a88ff30cb781ee69b8a935865d63ede5b1d97f31d344018f650a967c37f",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyRadio.c": "3a97e5c3de9ff33873d97c6b2d84ae505cfc4f3146e047b928e76ec3b22c8789",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemySkillAwareness.c": "00c450d033e43fc45c9e2cb64a1d81161d123cc594a4292cfc5f68eb6c17ee60",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EngineerMine.c": "e0098577626e719283cf4b40936e7ec038076c4bab9269fbad0b5ad3292e01e3",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EngineerMineSite.c": "b629fecc29711e57f364f2a9c4c3e884b0978d9674311678f2e78b561683681a",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_FiniteMagazineReservation.c": "8278590ced1936d618905b0cbd0b5aebddda238d426a6a2a52e455fc36bbb502",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_GroundDepotHQ.c": "8748325b0810ea053b28d0ed4c00a75fceae1c0d1cd3b30efb4dafdc397632ac",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_GroundDepotLoading.c": "36da884a833e7a29743308490a3ce5ae299f7a451d87205a227163892aca4ed0",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_GroundDepotTransfer.c": "1946db0615130b0039b42ec80db12d9cc89b1b3ca5a7eca7d9112eb69c3bdcea",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c": "5d66c53baf8762e76ae59e28721edaa348014af6d988d42891d9a84a6cf44969",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQAdminProvider.c": "e9bb5f2f3ec270074964fc8607e5a07c23f224308a4446837598d93e201d7775",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQAmmoReporting.c": "95c024cbdd35c02d9b0c3d389d29bb042e5dda6ffd2f5f0d896078274b3adb35",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQAmmoSampler.c": "8bb0ec1422887feaa340307d3c60bae50fc82d424fce6390876e580ff8b75e8b",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQBodyObserver.c": "f59421c1e0c83f0f480d7dd4d048b4b0a41193ed8fe8f2f627d2b6a696420ea1",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQCoverage.c": "8d9668858c47b7e9fee2b12d3ebd4e7cb88146b817cf615c4b0dade26c81b2ec",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQDeliveredCasualties.c": "1f738127e55e843ee2705007cedb5c08741f52f63c75eca7c80242177af82273",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQDoctrine.c": "6335728e89ed40ce11b811f00b61bfea76e94dd3379e4357cbfc438b05522bb0",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQJournal.c": "53563ff54fc4e0656713d6eaec5e88ddf69fd4bfa8ef6a67cfa13380d24031e1",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQLocalAwareness.c": "124260280ac8eda0e920bfcfc86932c9b21df6b1f7a6aa8acfc43feea3da149f",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQLocalHandoff.c": "2dd700dae9d9b232d22ca63ff01af75a3f5cc213ac24b7c0a43ffa38bc0b9ffa",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMemory.c": "ec2047a477fb4273e22ae6bb68e7365f321f55c583b5d2c48c4abf2ac38e3b7d",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMineSampler.c": "2a0fdc4d27e947a1d77b02ae23f757c7fa074eb456f817b7df0766583d757f55",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedAmmoSampler.c": "88ea0fc0e13d7e3c22c7ce8b652816d1d137e4e6e37a72f48f0bdf515b936ae7",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": "cee3ae301ac2f211c290450b644a7942c9786a2a40d06f16e2a47efa96fd15b6",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c": "a37edcf66e43945f331db47fe81cb2e0e767a7aaf8115244dcf9e6295932be98",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQNetwork.c": "0b73be993628a55e3b21c8670d540e39c938093dd5cb4d586e800ac530509b25",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQObservation.c": "57ad3f0b68d150791e30a7e4bd7dfedfb1ddd78a1c76f228e67b33ea9cb7144e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c": "ab6ca0e1a7f36c104e664db06a74c80a528cbfd1de599af42e2194ef0a4f4ca3",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQReport.c": "12ea8ba08753d1e6d38b237087c1209cd2f9574b53c3ff0f27c082bed029c9ea",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQRoadIntercept.c": "0897e82d7805ef56f05f2b9c23903280285ea6bfa51796a2df86bb564ece78f7",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQStatusSampler.c": "b937eaed913a5095afa8e678dc1c36b5c805541bf8cd5be927069533d6baed1c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQTask.c": "1147904bae6d72e9de47ce17bdada3dab489de4ed6e781dbf15bdbb30599ff6e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQWatch.c": "abe634cf6003ae920ec1ed18e305ed462920204960894520d3dd11f7e5ef1c33",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HelicopterAvailability.c": "60eaef2f6d4b2fd44861c5a4434ef97e732e40a5ff7401e2da451ed2dd9c2762",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HunterCombat.c": "df1c319d10efd07ed6f971cf0e41d8d7e377b04ea7f0fe143bf94df733fd1d9c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HunterCrewRecovery.c": "e76af0fbf6f436a9c3c9d4c7b779bc08a78f6fe705c08bf6d9757bbeb65ee8eb",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HunterOpeningPlan.c": "0b6275221dab491f148426df05e132579f3acc20de5559226c3b38975618355c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HunterService.c": "79369d5cc30cf628f02df0023157ab423e40497325dfd6a73b218757f2b689d4",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoAccess.c": "ab4262f78d54782c5ecbf5fff4ebe3da16d72329658d7e18c0c35387dc8b3c7d",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoTransfer.c": "99fa26beb0c0869d0cd694500f3bfc0705c2c1cf674dea90d98fb93a59570ef7",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryCombat.c": "5684fcbee128a35107312452297f7ae2bec28aaa1b63f619fa649f36df8ea476",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryMineTransfer.c": "02c399413f80879c22b4891e8ec807c67a6235677095fc4b98bafdc360d6944c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c": "b01fa948f204d49acbfbdfc5fa641b1d914e0eb91081e92a93816ae52ae88b1e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantrySupplyPool.c": "c8e454573cf064ff392291d6feebf1f23be64390b5b8e944c03dd065f9f46b42",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarAimSolution.c": "59e8343d09f7a44871d8d3335d6ad1d02216abb5deed30d38b5ec9ba3f2c0649",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarBattery.c": "157024afc8fd3c1ffeea6ae923ff091e5773b9ffcf5f5864f8040d001e689110",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarCourierTransfer.c": "968e58ca700b4a11e5e04a5c159696f514a0b1bce531210172b913e495f7e2b7",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarFiring.c": "f2597d44c10d10de2462aa5e5450a22b8a5ad0eb99bd0cc09a8cf1ca2dd4a350",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarLogistics.c": "b3624e4a00a2bdef16f6c100bbd79910e3bb8c2ae8ec5fe2ad5aa70bffecd303",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarPrediction.c": "05863b2b4f9f70cdb288f91c722cbea163c13f2a2927dc485482e5bd4fa7d57f",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarReadiness.c": "4c3799b3f2681e977669de0cbfaeb307d9a682b04365bfc9f59079e762965758",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarRefresh.c": "52ae8d9d6cad92f42d68c634091b80a474d2666b6c06d63a8c30a3fab71494c4",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarReportLedger.c": "42a96040b63e56677353e55164ae23031c90f70b4d6673a9f412ad7b19e636ae",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarSalvo.c": "c128850c7a78a4d8fee00ad38f48b265f1fbd1d9b24b092ddc5c205b0f0ad6da",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarStation.c": "cbbd7c86f722b664c85ab7c070ae1131d2ce5c41d1dacb4381bff9c1720a1866",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarSupplyTransfer.c": "ad71084f507ad9c32d3055e7ec6844a1ec5ddf568c0216ab8391e902b56e417c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarSystem.c": "bc290ee8bed299d869598cbf41ec281653e0cc7f3c64c7214613df8a0fc83829",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoPickup.c": "5f7ca5b31f3b405c2a6ca32ff91042f5b4dac6ab9640ce67fb10d7a74236ba69",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoTransfer.c": "0b195784497672fdaf651f336c14f7be98c257f98c85cfbeb4f96fe1a401999b",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MountedMagazineSeed.c": "b42d0aac142907433df8bedc40de24c94813544016cf5f451846f1980250f995",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MountedResupply.c": "c26ad43705765be1c20eca37fbe2978b8c647151f6620ed37141657a4b7dc312",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeAirAssaultProbe.c": "c1d55b338b157b758e6ffb993d5f689029b28b13929797c91e5dcb15b5e561ea",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeAirLogisticsProbe.c": "58fe863775d0ad575cb566c78cef696c9826691cf49ef9adde76376be7316db1",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeAirReconProbe.c": "c1216e0512a0268e4dbb82429e228334401c0c3cac52ba4486ae6dc91e41576d",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeAircraftControlProbe.c": "0cf3e72ccdc42e6ef20fb71d743f0e037a9e69b2e082c720fa705628cf790205",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeAircraftFieldProbe.c": "b64907c7566a894ca2f3c49ede5d972d4087a938da1a0ca87bcbd7c2203b2c52",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeAircraftRouteProbe.c": "8d0dca97e5106d1c84b64992c789e1af0689514beefc027e713ca6ab901fc470",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeMortarCourierProbe.c": "740c8bebbd4128a9311b1167d1058549475d6b46f81c9b6c0d19650f4b50616f",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeMortarFiringProbe.c": "ee570d21bede8baebefc799399712d9582b3e30ebda7299f6c941777f02ad2a2",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeMortarSalvoProbe.c": "fb9ccc6b36247ab3ad828ffbb2556abf5c2838d93ea6cd1ad259c0a015142f5e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeMortarStationProbe.c": "a110f0abc753cb1d2f781e457e637dd5346ecd2e924eb5eb3a5c7dca3d1e2340",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeMortarTransferProbe.c": "399c21940ef10777b46fdb3e905430ce43404ce1de2f4a6bb62f9c61c1a0689c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativePatrolHQProbe.c": "a58f57530e333976dc96fc5a0f9cb6cf0cb069c55788824950c5fb2f965d8e26",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeScoutProbe.c": "41b39b1abe5e51a5b8a428d533cccbfb0dda9d7a570228dc3489408c60543f17",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeTownProbe.c": "ade130f49fc9d868281cee7fde383f084f70ed8d4b876dacebbaed8538e84999",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ObjectiveDefence.c": "83ef91c2118521aedb4c1838b2e47909c80e0950086449b98ced168f1fd87080",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ReconAirports.c": "7551347d776d6e0eec818d6468cacbca7ac53daee7317e0a1618ca3d39ebc762",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ReconCrewLocks.c": "684bc5b9858c7ed90ed2ca6d52fc0e731a7e37a116db776090257b72a63621ab",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ReconFlightComponent.c": "4966dad504b3f8f28a9ffc8ac17cc548b00066fe05213c49c662ea56de34c9d4",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ScoutSystem.c": "63287a252e4e7d5aefce840279f26ce3e1f286cb79cadb32596c37897cec7119",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SettlementSafety.c": "e5c0ebc95521a9a33a3f7d9cca6c551dda862a08b7af20b017877aba9584795b",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyColumn.c": "35678ec625fb0a3c0871e2c373fcff49816440a97a165ed25c8798dd72ef059f",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyService.c": "5b1578afbf6da736453cc9d3872979eddd1649746aa32d3b0c7a9e3cab50057e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoySystem.c": "f09aa817c27d6af0ccfb544f4ab1ae2afafb63d626641e03d87cfe159475d821",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyTactics.c": "dc3acdffc8ca48f11b131f66d30d679ca68f3b3004344bdc78723bc9305915e0",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SupportScenario.c": "a7b46253e1bb37cea7c29bb557fbf43ca207022d0f1cc927349441b2d1615279",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_TownGarrisons.c": "57f862476751087972fc78d0b27ab44e72361e27d32f15d55f9fe7fd7d6bd0e0",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_VehicleAlarmPerception.c": "45ac71d25e10a057a26a5b0fc7128935baaf47f4c0f87208a4420fa52086fd98",
|
||||
"addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelAdminProvider.c": "d63ab5c547021aeb61d398b8a773afeedee4662d3ce15d9fd9d0e9e37262f8b7",
|
||||
"addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelDiscoveryFeedback.c": "62f2c408383f410782d9d41fa50acd6bd8cf66eeb80d2b79f91852b9d2cf8d97",
|
||||
"addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelEvidence.c": "6bbf8d3b83e2c09fd9a23a9a5e5e11d7c29af2e618116bee613562fc45af2b33",
|
||||
"addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelReroll.c": "d8b04de9ea789732cc78ebce9aa60786c86fe46d05f671cf251eefa68a214b59",
|
||||
"addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelService.c": "2911325c79a7a372628b18ffa0e110d11919ce2c2cd7b19277bfd608fdb03dfc",
|
||||
"addons/core/Scripts/Game/ERR/20_Intel/ERR_MineIntel.c": "3ce16cebaa9703b2f9803df7c11614f94de6460a9136dab8f80dcad41ae2b1c2",
|
||||
"addons/core/Scripts/Game/ERR/30_Map/ERRM_NativeMapOwnershipProbe.c": "32418602f085e3edba7343f42a6a691630b533be262002f29248e8ae87a38254",
|
||||
"addons/core/Scripts/Game/ERR/30_Map/ERRM_SharedMapComponent.c": "3a10f6ea25bf88a400bc3e6c339823433845c5db4f2f423835ccf99a1b1a843f",
|
||||
"addons/core/Scripts/Game/ERR/30_Map/ERR_ScenarioMap.c": "75fa1d742129ef7f33c005cd48f56db7f49ee7bde3c5428532b0b501b032b0a3",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_AdminCommands.c": "282fe1463add7af8771aa58507b254e69d2d06f50274d4c77321db073f8394f0",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeAlarmHearingProbe.c": "5dbb6a48909b76cd141e3a79ef01d2e68cafee55153821c4471f85c761fff94f",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeConvoyCoverProbe.c": "d2450140cf9bdf09210473234605b4c0d818ea5b91f9532209166a35c5d13701",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeConvoyProbe.c": "4bdc920e87fa768b0ef0ed893c70ddbbcc9255d74163d7b657fd2ec7c4f14c32",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeHQBodyProbe.c": "811244cc8297817efbfc3ee4edf01c868eae23c88127ba0c72e98ee7422da72f",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeHQMemoryProbe.c": "d8fec6c138bb0f5bdbf99ee82ffc3ac9b44d1045d8833da63f8aa5f39beeb7e5",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeHQResponseProof.c": "0f8b09fd8753234c91fd6baaca0e2f812e409ae1c4be8f340b648ae59b69c2f3",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeInfantryTransferProbe.c": "9b2de4dc22bdadd51b0271219a971dc8dc07f70ed3534f306acf03ed81b29424",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeLocalHandoffProbe.c": "0d4d61c424a16008595f86fa61f3b848caf9bd6e9ebbacea9c10254d88ba378e",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMaintenanceProbe.c": "852b9b397efc3f919c97fec63bc3df1a850cd7833421af8441994afd191951d4",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMortarProbe.c": "ddb892141500c192c0787b011d57dd8c07dd8c9a3ca5fdc407f6c271897bde0a",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMountedAmmoProbe.c": "8d197f7a1398625e5a19db3284b0bf72c6ee6e032843a3e4d9a4e58de42b6825",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMountedCourierProbe.c": "12710e9e5548fe8856f7a52f3566338369a8a1eedf0bd92af16229271872ada5",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMountedInterceptProbe.c": "34b0b1e38a59f14c4e83d93ec412779945388e6f4d697bda2f6cb90f519a6372",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMountedSwapProbe.c": "a6721fcaaccadf8eddb06687c7d587d0a3e377d5d1bb0b8e72e630fede8690ac",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeRadioProbe.c": "84524163c63402755b4f834f74dfe6d8e5c397dc26031f532593c6f79f5a93b1",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c": "254d1192138dd9864048693dd02d78b317e874cef57fc0b13226768e024d58be",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSupplyConvoyProbe.c": "701311de6a2c9fe8f0534a22056323c34072df26595dc70a7d4df45bc06e6c13",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_OrdinaryScenarioLaunchChat.c": "a747af09599ce4d0c52051f18f00c4624d24d137e328341b580b82e896c6bf65",
|
||||
"addons/core/Scripts/GameLib/ERR/00_Core/ERR_DiagnosticNoSplash.c": "0ea1b402f8927b6d68bf6f7e5caa2967f7e3a39147634faab147796b791933b5",
|
||||
"addons/core/Scripts/WorkbenchGame/ERR/00_Core/ERR_ValidateBaseWorldPlugin.c": "49a38c37b51e5318c7dcbcb01543dd9a6300d053f0d39808b3974f4edc45b871",
|
||||
"addons/core/Scripts/WorkbenchGame/ERR/40_Chat/ERR_ValidateWorldPlugin.c": "f567518b54f43b88747df2838cdfd6ebb34605c0919a3e7d271d28dd8b67a9b6",
|
||||
"addons/core/UI/layouts/ERR/AdminMenu.layout": "e8d570f7bf3b8c4067ad81d47bca7f35514b2f8c8748f5fa5f775fbe27debeac",
|
||||
"addons/core/UI/layouts/ERR/AdminMenu.layout.meta": "9deeece996f43b1f1fed6b5ec89279645c7b6424250200a42580cc088142e9bf",
|
||||
"addons/core/UI/layouts/ERR/AdminValue.layout": "cd811af6d2fb187f01c74e20ea07ad85669546b0ad70efae2ce52921ce26cbca",
|
||||
"addons/core/UI/layouts/ERR/AdminValue.layout.meta": "56b3d2335db400c1308e07c4a35d278b23ef32013dfd6dc82951866327428d08",
|
||||
"addons/core/UI/layouts/ERR/ContentRow.layout": "62d2c95814c99d906266fa96c6112e04a7fab1667e1a05e558d1d3974adc2d76",
|
||||
"addons/core/UI/layouts/ERR/ContentRow.layout.meta": "8c4bc13b41e7aee38e0ca640a97813ed1a6964a00e2cff9e1fe4ebd71fc2fdc6",
|
||||
"addons/core/UI/layouts/ERR/ItemRow.layout": "8ac6fbc7fe0ce0ffc3185516197303bb892a4415948f75ae06f3bc289fefcac6",
|
||||
"addons/core/UI/layouts/ERR/ItemRow.layout.meta": "33eaef9c403ea3a030e58ec17f78e46af457048243571ad5856fff04101fb60b",
|
||||
"addons/core/UI/layouts/ERR/PauseModsButton.layout": "d21d436291e2c1b262e33dc228b0d60a1d188dc9e85cfda6d2373f8281431b79",
|
||||
"addons/core/UI/layouts/ERR/PauseModsButton.layout.meta": "9ed6adabd8f53eb9a562d7186789876bba5c4202155ca894c1af866ca47354f3",
|
||||
"addons/core/UI/layouts/ERR/PreparationStatus.layout": "bd269cf8dc6f27735423743dd5b78242550518414448a0256e5e12fa6b92c459",
|
||||
"addons/core/UI/layouts/ERR/PreparationStatus.layout.meta": "75b12176a443bb911fc4ad446982d762201fda42ed38736b79bc5439fca31ccc",
|
||||
"addons/core/UI/layouts/ERRMap/ObjectiveMapElement.layout": "b364752c44896d67dad07e466a04f4504a50813fb36ee7c34c9189ea9677b4e9",
|
||||
"addons/core/UI/layouts/ERRMap/ObjectiveMapElement.layout.meta": "8691fe2eaf46568bca4a9f8128b247a8da810bb87a5546aa7ca3334e740270d6",
|
||||
"addons/core/UI/layouts/ERRMap/SharedMapRenderTarget.layout": "6e83a9c70282aff43b23a08b732739e7a241a31e8abf200498c90a745d3d60dd",
|
||||
"addons/core/UI/layouts/ERRMap/SharedMapRenderTarget.layout.meta": "5c846445cc089e2e08899ba703059d4adb805ff30ab5a16409a3cd59718cbd54",
|
||||
"addons/core/Worlds/ERR_Everon.ent": "22324bc7eb0d80dec8a1f3bd61a9df9201b72f749f4e751766b0c9d0b7d46b87",
|
||||
"addons/core/Worlds/ERR_Everon.ent.meta": "19d1903ca72baa326b4724a6791d0da1104a1e53f3f31dc1ca41a856efd68e83",
|
||||
"addons/core/Worlds/ERR_Everon_Layers/default.layer": "6bcb7fbb27b6282719dae8a37486e518d4cbaea385f2ea9af1918b36bb28b175",
|
||||
"addons/core/addon.gproj": "73e973a1f1c5910393e16f86a3a40d792031bb2fa3fc7f4dd87a06a16c3e34a4",
|
||||
"tests/helicopter_availability_regression.py": "3f256b7f5db246723b75caa2cc247249881cba95bde71ea1c90733404fe19f35",
|
||||
"tests/mortar_salvo_regression.py": "0f5d5f5d33f64ff43f5a2b954fe4eec032a00697f51f09efa10284537a3682db",
|
||||
"tests/test_mission_rules.py": "f534ae447fe1db6ae939430c3b14568d2bf1b84956dca295cce04211411d9ee7",
|
||||
"tests/policy_harness.py": "e861c30037b3e456f0c8ef1c02e9fe1417b601ffb15d2037974715008865e4be",
|
||||
"tests/test_time_policy.py": "45ae5768f6e87009b4074937cbc2e384c59341e905a3f250672bd2a110b2e6c5",
|
||||
"tests/test_radio_defence_policy.py": "48c9c01dc6135a3f6d2187b0974645758040f041c3c2c2c26b14222fc572dfe1",
|
||||
"tests/test_performance_comparison.py": "5f932de9e9898acdd379f26c777a35c884269522dbf8c41991a7a25bf61c369a",
|
||||
"tests/test_release_check.py": "8b6e755ed922ad129ba01c228989610a31f3a79841dc7cdddd4ddbd0e510971b",
|
||||
"tests/test_player_admission_policy.py": "745ec0a9e491ffcf5bec53ebf67ecee8333f4dcfa5d19bafbde31cbd1cad7f8b",
|
||||
"tests/test_recovery_policy.py": "adc4966ab92f0849d08636d93da5bd02b9162d1ad7369b325a1a357903abaa55",
|
||||
"tests/test_admin_setting_policy.py": "69af6f74e262ee4981ac428ac1ab85561f8b365aed5879e20d315ededca9ee22",
|
||||
"tests/test_prison_placement_policy.py": "34539744426f0b30e41883203484ed60606df8ca319324014f07e833bcbf854b",
|
||||
"tests/test_environment_policy.py": "66b86e66f47c4ce397c6093a50a724bbe8dedbf3db53e0dd7d53ff33243a0b52",
|
||||
"tests/test_faction_catalog_scope.py": "c8fab92575f2621f47ed8685243338aa9ea717bb809c2ef9e8e0884af9df263b",
|
||||
"tests/test_environment_restart_runner.py": "05bc09bc45db70ba4a635b65b0a96aa38437ba50c98640b47ed8d0683c3a5f71",
|
||||
"tests/test_inventory_enumeration_policy.py": "95cbfd7c4351cb9a8e1cd6783524be79f77255e5996299919a61dcfce8b0f21a",
|
||||
"tests/test_site_discovery_policy.py": "40fda37b86d469a1cf8fcb7a1fb18abcaab0df44ad09f50018744d42f8f4f649",
|
||||
"tests/test_headless_game_runner.py": "0d84160076ec9961e7315ccd50d6ce45ff971328c298649505fbba6305c22f31",
|
||||
"tests/test_initial_guard_policy.py": "0c80e744db1ce0a0d0746c43d867f5d2f47d9b92bc48d6561ceeb28a204d8743",
|
||||
"tests/test_casualty_loading_policy.py": "cdf2c0abe4b794502f18228b173db47c4093ea062378ec9e3971e0c4f9e3bf1f",
|
||||
"tests/test_casualty_reservation_death.py": "fd68dce04ea48ee2a22fac499c32a5c53847a25e3f14eaba3f50437d2c040f25",
|
||||
"tests/test_casualty_ground_policy.py": "6543c594293d7122242ec68d506a92dc81464d110cdb7496a225555cee338c83",
|
||||
"tests/test_guard_cancellation_policy.py": "f8f134b6d525a3c775677b2a95c562b12aa84ba287318c08c833d684ad0bd9d8",
|
||||
"tests/test_radio_feedback_policy.py": "6b576472f81a35bd4df4dae3f19925b046eedbba262ad67f9bfe1ce9d220a487",
|
||||
"tests/test_extraction_placement_policy.py": "d924f163d98c78989938ca01eca5935891fb454266810f4817cbe49651b84ae2",
|
||||
"tests/test_casualty_remove_probe.py": "e3b8ac386bb8e4a9832215cbbe1cacf7a5bd1314467bd4b07ed4a57b2db75458",
|
||||
"tests/test_casualty_incapacitation.py": "55b5cfb7b3eae496a8cc18888872d11331ecc01afb7326fbee3b8d65047dbe93",
|
||||
"tests/test_diagnostic_no_splash.py": "d99539a8b3277c2f1ac3e3bfa4c897876fe21e704c9ae33c542eed79fc2e9e65",
|
||||
"tests/test_native_mortar_probe.py": "217801a6eddd48f76e99363a72a9812c0f48766ebd2faf8e188f4466ed1e4088",
|
||||
"tests/test_local_admission.py": "0bde82f6d5c31c4187ed0aa1935940b8b9e634868b3a4820eca073580ad136b4",
|
||||
"tests/test_dedicated_preflight.py": "0d654375db6b8eb91d0b92663b50e32776aaa7ecafbfdb83c00f04826df5236e",
|
||||
"tests/test_mortar_aim_renewal.py": "cf228b7f83d247d93277ec4c8644eb1090a20336a7e9ee2b90086c78199a70ca",
|
||||
"tests/test_native_mortar_transfer.py": "3b2a201a73c9ff2509276428988e4a98023c6aa94f8eeb42bc06c6cc604473d2",
|
||||
"tests/test_dedicated_native.py": "f0323b20743221bd1ad46d6462246c2a0e2be84b395e40faa39007ef330aaa3c",
|
||||
"tests/test_mortar_report_ledger.py": "fd2346e06c37a9e7621e1861e0f472b72ac14786fd2bcfbd785b62700d55ffde",
|
||||
"tests/test_native_local_admission_probe.py": "16864bc359ab607dae3919442bd18f191320721411a8156e23be999f24e4454f",
|
||||
"tests/test_powered_flight_policy.py": "86bfd5851be6fe3a187e1fe7fd2b7b0965c891cf29b2260b0df292255c5c547a",
|
||||
"tests/test_prison_escape_policy.py": "d5d54588116eb730a8fda046f25fc4edc89a5345f85774cbc0f0a9b3adb85b98",
|
||||
"tests/test_powered_flight_pad.py": "690c1c6e05f3520a6d4d6c45a0fc5d9184e30a12b24356b3126bdb35c4171b82",
|
||||
"tests/test_item_station_teardown.py": "4e0f508dea6172862be82d6c7f295ede2bfb2412c390a0937a75ab176655f4b6",
|
||||
"tests/test_source_bundle.py": "074ad87ee7ab0b41ab94a7cb28efca66a975c24b8e2574122a13a52a4d491d0a",
|
||||
"tests/test_native_flight_footprint.py": "96923c018608f0c4d24294a9f8e769e2037be1fac8f1d51f4d73463706e134ad",
|
||||
"tests/test_native_flight_input_diagnostics.py": "355e2e45b86b9a46c68efb0563d16bef7a032cd7758169704b4b26ea92bee2bc",
|
||||
"tests/test_mortar_logistics.py": "98cdf38e43eb564163d8b409dfc95c4ae23e775c423651f05698acd52a7ef5a2",
|
||||
"tests/test_mortar_courier_storage.py": "0eb75fbcdcd712ff1c2220dcdd87cff6f3ed1e6e8f1c889916daa6b84a4359f5",
|
||||
"tests/test_progression_identity.py": "2fb3725aa735e00b20ba7a4ac45f8e087d4346c58a176bee091bab0a9755c1df",
|
||||
"tests/test_discovery_map_position.py": "0644aea60d4c546e819ef417771563ab7598e84d5e13db2fa63416ae1fe6ddb0",
|
||||
"tests/test_vanilla_game_control.py": "8ab3777a5bd0f8213efae08bd6db70a05790af992c2abfda8e5e5aa079beebbb",
|
||||
"tests/test_archive_game_control.py": "8126e2be91f9ee8c09fceb82d03bb0d9c51247909189a424c3e24dd2b1ed22ce",
|
||||
"tests/test_flight_action_runner.py": "aa14ebe85fce7c0435f48aceec1d4463b33fe8a5792b4e935b543dff94f4ad02",
|
||||
"tests/test_native_package.py": "0776a1eedede7185aa36c7fe816ea6231e6f475c0f6d210a2c732b23dc6d765e",
|
||||
"tests/test_spectator_guidance.py": "26c02deb1a707f51dcf577a9ccb789bf33f432b41d9404d36a7b1b3c8802298f",
|
||||
"tests/test_native_mortar_salvo.py": "f45625eb92d16adc8b3eb57bc72b77c8829d46c2c7e5bef40d302fc90252ba72",
|
||||
"tests/test_pak_inventory.py": "8f0b55bb295117c226900bd641d25175db1021a365866a5ce6482a78d08c7f41",
|
||||
"tests/test_packed_core_game.py": "ddac4281f74999b1e60c2052bbdee36c97699192cfd5174a88b6f01ee740111e",
|
||||
"tests/test_mortar_refresh.py": "0ac74cff8aa10fb06ecb75d17eb7ca3a6b80bf5194522bffec583a44fc01a419",
|
||||
"tests/test_archive_packed.py": "37712810b7c6fd11a2df35665df81546f8b98682dc74b317ad1f9ad6ad274d86",
|
||||
"tests/test_native_mortar_refresh.py": "60f3c4fcb063506e9bd85e4abd3cd2fc802304bc71933f3e31d88f5b896aaa7b",
|
||||
"tests/test_mortar_activity_completion.py": "a0db3b63962bc95de42ec12ef17c53730464ac7421a4475020957121513586ec",
|
||||
"tests/test_packed_chat_game.py": "d23c099d17a0dda09e5ee28931a4850f9bf6df5a7874581342eacbd16860565e",
|
||||
"tests/test_shared_map_ownership.py": "486eb3a8a04a47878b87ec3952c613542871077af1de27ea7299fa3d72caff1d",
|
||||
"tests/test_native_shared_map_ownership.py": "672d248550339dc6350e548034c50b3440d44b91668457f3efb2d91c8445c3dc",
|
||||
"tests/test_participant_selection.py": "e153f69ffa60d186dc3b1a238df1d48f2a0623b2dd8331d2cbbbdd57cce5529e",
|
||||
"tests/test_ordinary_screenshot.py": "eadd4f8ae2a8057d0ac7bca5e8f4abc260faa10cddfca31b8552d7b49669ab33",
|
||||
"tests/test_aircraft_flight_policy.py": "147a27fb57c0b01cded35eae26af4394d7f41eeeb68856f564972e1c89c629c9",
|
||||
"tests/test_aircraft_flight_envelope.py": "6499ec174ed68dbfc07bd49275eaff8a7e60865615bbaefee354de7233a6ff13",
|
||||
"tests/test_native_aircraft_control.py": "3ffed5429896254d96b67ff3fdd01f65689f526e6a1766cc6874e2e63f2e26f3",
|
||||
"tests/test_aircraft_translation.py": "983fcff4800e777cd1c42126431ca94e7c03bc06b5fb34a243704b8ca55dc36d",
|
||||
"tests/test_action_feedback.py": "1fec11a218b6da69c8c40ce232523582f0ad390f8357e74f24daccf5d46d3814",
|
||||
"tests/test_spawn_retry_budget.py": "c182acfcfd52cd74a94dc3336d7603d14668850a684b758cc7545a98a5d14beb",
|
||||
"tests/test_native_stats.py": "a9ce90c74f0ae7e5c40e451f6b1d60dadde831c3d858710a70929390d5c70f7c",
|
||||
"tests/test_native_end_lifecycle.py": "8b187723c9039e68e3e590c812c91dcffe1e5917d4e450ed798dc177888fda7a",
|
||||
"tests/test_native_end_runner.py": "aed94be9b370a412937303cc5b66f0151c2cd64c3e1358f983f9ea53eb0d1057",
|
||||
"tests/test_escape_result_wording.py": "7b3613cccd4f24d3ef4665dfc1854cfc958a69018a59f9b8090617f0185a82a8",
|
||||
"tests/test_preparation_status.py": "e43cb7ce81232a31d00219004574dc2d1806760e0b6f0c49693725ffcb1b4e9e",
|
||||
"tests/test_headless_display_probe.py": "d4d6c9907d7419ca888d5d9ba7448cbe6f5d2e53810f74c85660dfbdfc9f7e2d",
|
||||
"tests/test_headless_display_runner.py": "f03a908e813c9ca4dc2ff13b3ade08549931e39490b8636742390a304c73974f",
|
||||
"tests/test_rough_intel_feedback.py": "d7395ecc498dda46e1c9a4c8fbf98a64f2bbd013de7806517fb8008756e7b40d",
|
||||
"tests/test_aircraft_route_envelope.py": "08b16d4e7f97db96e3f88af0cfccd9b6bc083f6f25ec33cffd7ec36b18798729",
|
||||
"tests/test_aircraft_route_policy.py": "b717734a07a59ada837f05c36172110bd7d0b5a5a277e8fae55a11dcab3e2aa2",
|
||||
"tests/test_aircraft_route_probe.py": "bd8cb998fef33db8f26342eec0198c54b7715662d333530e2bede572c81f162f",
|
||||
"tests/test_ordinary_input_observer.py": "09cf661ae47f53cb429a7ab17c2b752ae30a04368a260468227b0678148b2434",
|
||||
"tests/test_ordinary_input_runner.py": "1ed373818673a438647fc7074c4c689f5bac0a91e8d55edf29d466b96f6703f8",
|
||||
"tests/test_private_prefix_cleanup.py": "bc6d3fcf6a5ee5b5266bfc4516642d2f73e71dfc870c527d67cb5a47964dede0",
|
||||
"tests/test_runner_private_cleanup.py": "4380874af7553391ea7e1e234c8a3f8e61922e0f16b1bf9bd4821cc479e5edf4",
|
||||
"tests/test_headless_input_proof.py": "55c84de6dac3a9c44bd1ab4113d306544025a405c613f6e7d26550328e84f6c1",
|
||||
"tests/test_headless_input_exercise_runner.py": "0b812647718a3d7145905a3ed17fef97b025d57b1372773d2c1069a3473a9b1f",
|
||||
"tests/test_inventory_control_runner.py": "f325fe1975f7a56fbbb1a6357206d2ac0c9e7c32c396473fdfaf1cb80ad39b11",
|
||||
"tests/test_inventory_layout.py": "8e176f8a87c300b8062a37ee34b22a105978c7d9c6147b506757135f4f48a1ff",
|
||||
"tests/test_radio_waiting_text.py": "2aa54d8126b3fddae2b77d496f56d2d6e6d393c6d5124bdb5598713d9c29d629",
|
||||
"tests/test_relative_look_runner.py": "131d829c60a7bbfe3e047af8e38d255e5ecb9d84b7d2188d14422dde724fa05e",
|
||||
"tests/test_ordinary_look_observer.py": "cd5842e54c9ccab16766637bf2ab4e4e0b98f4d597b5927b0c2ec3f720fa3668",
|
||||
"tests/test_ordinary_look_evidence.py": "1b98be4b69a5e5bacf46791f1e009fa3c73cdaff875c012837c9888443a61633",
|
||||
"tests/test_ordinary_gate_observer.py": "78ff734604724a525809567ecda88f4d5fcd67816a6ba4960b72d0685f1a8b00",
|
||||
"tests/test_ordinary_gate_evidence.py": "2bb3cff082b3fe45f668933a5039fd06a8d31562049cdd41c4f32181136ea5df",
|
||||
"tests/test_prison_approach_proof.py": "ca71ba6c8cc20dad52d2966984676a111004333c24bc408f1ec84312e4208039",
|
||||
"tests/test_prison_approach_evidence.py": "660e03b6f9932947c1aafc4c043c2a2c5bca776433c2146df18e8bb90c606a9a",
|
||||
"tests/test_aircraft_route_demand.py": "96d9a21c49605cb671525f977928a21c57f1d0b441460a2624892006e7057efb",
|
||||
"tests/test_prison_alignment_proof.py": "04c96825ea03b95c3966e3a53ce2c15990d63388386d84f2bff3e6b6838b18c0",
|
||||
"tests/test_prison_alignment_evidence.py": "214af04ff9b0e8ebf0bc97f528233f5d55fccc7623392a65c6dd286ed415e724",
|
||||
"tests/test_aircraft_control_bridge.py": "bae6f1711dcc5f55f7a813af251fc210ed017c58a8067c03d91e936824542ef0",
|
||||
"tests/test_ordinary_gate_choices.py": "0e21f2933a4862eb5fd2f04b3b32dc7de6b2ca72d920aaa332d179edadb932cf",
|
||||
"tests/test_prison_action_selection_proof.py": "1f09fb84b73ea916bb39ef985c58aa6886223e60cd425f929b3a020eef74ebae",
|
||||
"tests/test_prison_action_selection_evidence.py": "1fee0421669721618dc2f9a3039f945d470b83b186661d3b1f00291ff8b78e9f",
|
||||
"tests/test_aircraft_service.py": "9c19a09354ccfe8b41961204d80c3621d11e6704909b7266fa00e6c197bfd20b",
|
||||
"tests/test_aircraft_service_cancel_runner.py": "1220e7c45c50590d12c9a5192eb4482e7e6942d7c00f36d9a79b5ca70bd1429d",
|
||||
"tests/test_command_maintenance.py": "be2c942aa11a5f874448e94f06925f2f1de2210a14881fb7e486842119cb673c",
|
||||
"tests/test_aircraft_field_rules.py": "dbed3a499eeddf2513e3582667ea2bd90fff6ed26057891c7df6e196c3d00e84",
|
||||
"tests/test_aircraft_field_envelope.py": "67e60717a92580e3c4caa4974994cd92a96c98562be805b31319b3585f296639",
|
||||
"tests/test_aircraft_field_runner.py": "822717f77702c335667b226f025aa6525415689b492f9ed42dd256d4c893429d",
|
||||
"tests/test_prison_coarse_facing.py": "fe0ceebb74dba77005d6a7e31baa1a50156cc66f79a1d90eba4db9b27b099acb",
|
||||
"tests/test_prison_held_action_proof.py": "349ee99d36f18fb7c70382ce95f1e9d84d819575a45b6f5429394d1702f1f227",
|
||||
"tests/test_prison_held_action_evidence.py": "bbb032736774bc5db5fab7758faeaf038eeaae9eb49ba706a52f1e9bfe09fd01",
|
||||
"tests/test_prison_held_input_adapter.py": "56fa60ae69bf280e1c2e90e7882fa213914623980b6800a4312c6f1017b8f632",
|
||||
"tests/test_prison_held_runner.py": "d50905e899c2d0d92ca2f4c51dccbf2166ee21b8c6d2d4b218da459b724c5a4c",
|
||||
"tests/test_aircraft_field_flight.py": "ae5e18f0e2efe5ad01523cb1cf9bc64139094aeaac7477a900a9abd8e6ae8f18",
|
||||
"tests/test_aircraft_field_probe.py": "fe4e5b58bbdca0f366fe4c614f7506f2a05fa29fabcde345e322cdb3d62db897",
|
||||
"tests/test_initial_admission_exhaustion.py": "14d6959a1185f154389be1405c49e2f7c1c9a3f27295e26074ac4ec74380057d",
|
||||
"tests/test_casualty_feedback.py": "2d8bada9a3fd7583af4fd4f07033dd9eac28ca3bdf34a4557c8985b7a1714dbc",
|
||||
"tests/test_intel_admin.py": "f2704740a380de8e93c7ceace414744f938442cffc6bca77a3439b4ae36aade6",
|
||||
"tests/test_intel_reroll.py": "d102cff7467323440273eb457b5dfed6610d27f32c52c6fb7801ce93348ef5ae",
|
||||
"tests/test_admin_player_tools.py": "7520c768b760ef3a03dd2f17376077fc17695360a89f91df88af924899a74517",
|
||||
"tests/test_admin_session.py": "6e1850c625e18460a5d64ccbadac992b663a46bd612fcc16b6006f19b3014896",
|
||||
"tests/test_project_schema.py": "0e25ade3ee0083618c1b2837aaaef36ce33663d78f35d862e1dbec256f4d6e40",
|
||||
"tests/test_admin_tick_delivery.py": "8f61a6aaf07b0c41df2f415a1956d8f09fe61a7d4f256775ca62511030937a5d",
|
||||
"tests/test_hq_doctrine.py": "5c18ee72454cf92132dedfb4bf581d174d67fc2e423158347ebe52ca74e8a416",
|
||||
"tests/test_force_ledger.py": "3dc268db304b9465d53056edfcd8cedcc15591b411c55ae8f9bd43e163ff9c49",
|
||||
"tests/test_hq_body_observer.py": "6cae50ef0a2b2fe35845f42e90998c517c061fc417cadfac375bacc89bbe603a",
|
||||
"tests/test_convoy_budget.py": "481364c432715336825466a9ff199edeedaa2d3621daba90c0503addfa6010f7",
|
||||
"tests/test_convoy_dispatch.py": "47e77f544ec6f3f34346dc6b5082f9df0a2d869897e094ca993a92c7315468c0",
|
||||
"tests/test_convoy_admission.py": "0ace42a7e1afee7413e45a3909e7ea5c29dbb8c0e692acc3dc9a35ea7ffd4adf",
|
||||
"tests/test_native_package_external.py": "8eb3eaee401640521c67a51607104a28de89ea9c32b3b6dffd05d9d3026fca3d",
|
||||
"tests/test_convoy_road_plan.py": "62bb655a84f90e934bb894f8e472a8a06905d72e1c0b05bd641e46eb8f4873b4",
|
||||
"tests/test_convoy_placement.py": "dcbf572747a85edbfac341e7e358b1987150a2eb721708a3a025639be228a7c3",
|
||||
"tests/test_actor_group.py": "0d808be40d13866bf8bd983d62ea3a8e7772e9c49a41109fffb02c5352153a4d",
|
||||
"tests/test_external_addons.py": "4e2c3fa2c92661c49ea0c492648b736bebd54f24cd244f3fe27710a0c7a487ab",
|
||||
"tests/test_external_profile_review.py": "a35e9670a8f5eb47d28b4d6afe498e5a58b2ab2dbdc59912a206010ac8dc5240",
|
||||
"tests/test_hq_delivered_casualties.py": "c22ad9e395b36b41e8292b034f170e14b5d13a6a02b2a81dc619dd37110ad905",
|
||||
"tests/test_hq_ammo_dispatch.py": "6c7a4cbaa41ca3bee01082ffab376fc2db5b08757aac88c5e8caf99b39677d0a",
|
||||
"tests/test_convoy_combat.py": "140d5e8c8c978f97f7f15238bf2aa4efc6ca4d9b6370ef5a0c68bf469cec0b9a",
|
||||
"tests/test_native_hq_body_probe.py": "f2b761fadf013d55d4e9c2ae11c5349fc054e767cf950aa632a43b8f4fe1d758",
|
||||
"tests/test_enemy_optics.py": "06361eccef16d60a6f5072f9b126814a8f686c478049760047bb2ae8d19b5c52",
|
||||
"tests/test_native_convoy_cover_probe.py": "68681753a1ab89a8e8e07ba7f701190bf860d07762e5a06e87da7ba606fc0de6",
|
||||
"tests/test_vehicle_security.py": "f08a1cffb0860ac7ad93e94f4cb8b7cc37b8c49b3dee72b1d310fc72d9f95422",
|
||||
"tests/test_vehicle_security_native_probe.py": "977384c5ec07588bc77aa9aeae45b008c02d55d31169e46984a1947e987545c3",
|
||||
"tests/test_vehicle_entry_diagnostics.py": "49e8b4cd8274e08de628e89f4471ddb47823af38d98b464336e8c98a4308dbda",
|
||||
"tests/test_hq_radio_node.py": "2297ae23b7a5fece845c13f16432dece6e2dde55ecc097a5be6311f470dd5242",
|
||||
"tests/test_native_maintenance_stock.py": "7acbb488f7075428c08a10cf0e975ca0d508f3bb6416eddcc2991da93a5fad91",
|
||||
"tests/test_packed_external_game.py": "c5581fa3225bbc5b160fafc26c65783eb7d551489ab101b17f7a6acc1d8d9e63",
|
||||
"tests/test_intel_registration.py": "41cd929a29ea5e6b37d856cfc873c33fa2a8130e3aad30146f522ce217875488",
|
||||
"tests/test_convoy_startup.py": "2c9106023ecb6dcfb5c96c2edc98b51695c325c685b2ced37b999b6cd4504c65",
|
||||
"tests/test_failure_result_refresh.py": "076444d2c740609ea4290de1525cd5d7fbd99beef3c48c134afb581422a3dcfe",
|
||||
"tests/test_convoy_auto_settings.py": "fe4a1e237e31bfac11f07b1986bff859f6b4712aa7759ce9e359e28e5ec2aec1",
|
||||
"tests/test_settings_migration.py": "5a82ba8672b72fb388a95e297d53452e45679ed3b94f5c7d4598455179038aef",
|
||||
"tests/test_native_memory_deadlines.py": "f2d3f0feda862318ab936031487743218a8c8241d192b34e2b1ab63a5aa2cc48",
|
||||
"tests/test_enemy_gunner_skill.py": "67172901ed950517977e535c1237e8eaab10d22d52452bbe05fe16bfbb4089fe",
|
||||
"tests/test_native_infantry_fixture_lifecycle.py": "c20ed9dd152489a8242ec0b97135b0691c9c104783bb843b0715cdd8d7c2e156",
|
||||
"tests/test_infantry_ammo_access.py": "bd80e4405c3f2029553b5e932ec49c6370b8366cd4e9ed03be89393f86c07dbf",
|
||||
"tests/test_infantry_placement_settle.py": "1490a797e016b99a222280f57875cc83096980022d1784495a80b8a406d400f7",
|
||||
"tests/test_infantry_resupply_waypoints.py": "df3596515e0bb6a4cbf1e6f07d56755344415ad1deeffb8a70f71e8ed73a139e",
|
||||
"tests/hq_network_regression.py": "63c7525b26ae6b6e2369307250e89223a77e7e28321daf4309ab76f21ca1bc50",
|
||||
"tests/test_hq_report_selection.py": "4bd4c4800c217584ad9e1b4a37968350eb9a160e0151fc8b0375781736726cc0",
|
||||
"tests/test_hq_local_handoff.py": "0fdbcac1958b0d12296753e7c9d92700079ea6c5c8e33d0e21e8318f997b79e7",
|
||||
"tests/test_hq_local_handoff_intake.py": "c51f0ca082456331eb5856bf6dad864c3852adc20beba9b6829bdef978b11d88",
|
||||
"tests/test_native_local_handoff_probe.py": "5a0904863ea00fd7ad262fcf91d8c1e55036b2041f16b043104f333c37d8c7ba",
|
||||
"tests/test_hq_road_intercept.py": "ba4ce9af49acdbf5c0c04b849761ea36abe2ec7d68ccde1f0cd731fb9e3ed451",
|
||||
"tests/test_prefab_identity.py": "2a1c6e4cb1ead0c7b36b8fffb2ba0c59dc7e025dafaf8344a8158d38fe17582d",
|
||||
"tests/test_prison_ai.py": "557015a4a3191aede258dcd7dee4ec4f555c3efde02331687185ed124cebb4fc",
|
||||
"tests/test_spawn_scene_inventory.py": "f29bee9c629072f51caf66b680013b18f608bbfc5135a1f020e1a2904e89cdd5",
|
||||
"tests/test_spawn_scene_lease.py": "adaf38b259cc36af526884d3d2312ccec0cbe6eca18d7aed2c1a066be8ea0da6",
|
||||
"tests/test_content_policy.py": "260eedd838802647af59f33a15510d2c721aa80a7540d54e47f22734041cb49e",
|
||||
"tests/test_random_settings.py": "75fbcba53ff2331811fb91051bbae1b2c28108fa9477d98372e0eb057655f225",
|
||||
"tests/test_hq_convoy_combat.py": "07a4cd8e3bcf50ced71de05d0713a7944849a38b3f33cdea27f145bd53b9708e",
|
||||
"tests/test_faction_selection.py": "77e4789499b3560e729a115197c5b02d032c291ae1bd44b1cac5a2c2c476e450",
|
||||
"tests/test_generated_sites.py": "111095f40133b025ab67380b6cdcbc25ce0ecbc03379d1a015db6fdb0e5f2b25",
|
||||
"tests/test_debug_ai.py": "7f4b847ba11e8da937eba7be3c4c0ab898b44567f6f553ff842da54c1e7a85c2",
|
||||
"tests/test_mortar_station.py": "6570f18f787b7366ec5341da68dd99acf541cbbc31c6db7a9a3ae967506456ec",
|
||||
"tests/test_scouts.py": "25c6bb1bcc9cb171d12cb1e59b5b278e256a7a4b257b5ce3a1d76d60d459bb59",
|
||||
"tests/test_air_recon.py": "ed8938ac85c2e539bc5cddff339809624bdaecfff63ff4b2ab728d006548dc46",
|
||||
"tests/test_air_support_policy.py": "bedd5437ba0667c371c390cb8c4fb5a3096e8ef6a71c8bac2091264b8d2eebf7",
|
||||
"tests/test_intel_body_inspection.py": "04dd19efafc796f349976d29df77cb71b95615e527ad9add4fceda1f6105fc94",
|
||||
"tests/test_air_logistics_orders.py": "adf7181998f522f8825b71ef10955c9311aba9086e5bb601cfd47599baa46d8a",
|
||||
"tests/test_enemy_awareness.py": "881289417a1e4af219546b113d018e9bc6a402a16cf4bfa7e983ab240f41c2aa",
|
||||
"tests/test_faction_menu.py": "9e6381a4786487962358362b75a2287532a688ab27df756e84801e2bb6dc2e55",
|
||||
"tests/test_hunter_opening.py": "8d791372f465b6242a386d81b8e2136865390cbea5dfdcbfc6bb8b4f717c041d",
|
||||
"tests/test_journal_readability.py": "47764126522e7d7a90100cd9beff741bf49e4d8390111f977919d3e323a5c7df",
|
||||
"tests/test_player_medical.py": "e4b3140a3afc0161db53f261e5480e51f1ca09f85657e25b0efb7dbba3336e3f",
|
||||
"tests/test_gameplay_session_regressions.py": "d9968b06627ce10412e5b1165987de662385d5dcb29090c5626b90fb59df52f2",
|
||||
"tests/test_field_sites.py": "fddb0e44e72fdfe32bb94347a14da64efbd2208e4f252754e80c620fbecda56c",
|
||||
"tests/test_town_garrisons.py": "3674f713d7adc62ec9098f7923991c9f6b1d0edfc1431ccfd82fc8031ddae17f",
|
||||
"tests/test_unconscious_controls.py": "74d4008a5c02ad0087b55030c177c8320b7099c11b97a14d93acf0ad14941356",
|
||||
"tests/test_hq_local_awareness.py": "00c8023f85e88ffc1113353836962c31e8e541a32c9b2f1f66b91eaff5b923af",
|
||||
"tests/test_hunter_combat.py": "240472940515795b205d5caf530392ce416e3e0d2b97e473bb935930f29287d6",
|
||||
"tests/test_enemy_medicine.py": "d13ab1d4ebe81614e00a9a579c6bcc6bd5b2c608187458f414e47f47686d7ad0",
|
||||
"tests/test_infantry_combat.py": "03bc85811c52bd894ab8545794135227fc33b911a51f4e70d89fbeb2673de8ee",
|
||||
"tests/test_hunter_profiles.py": "bdc7898666a7e1078ece35b5dc9bf903d2218843abb24addcea9353fe6b3f7f3",
|
||||
"tests/test_gameplay_review_fixes.py": "5f988d63ea4b2a8a364b181982ddd462baa628ab66e3a35d0fb5ea6f20c50b0b",
|
||||
"tests/test_convoy_column.py": "8c5de7616d4f8c3ed15bcc29bbb57f04dcaaca0626683b81547e2170fc5f9a62",
|
||||
"tests/test_spectator_recovery.py": "8bb59c51df003e24532ad9476af4c8ff3b7f4276dca25e8162b91eb77c7b67c2",
|
||||
"tests/test_server_log_regressions.py": "733084d7d2e33eb96740768c48d9e25bd7f3ffa9081949e38d0776c6822f5748",
|
||||
"tests/test_mortar_load_ack.py": "6eceee33969ec268d904f062bd1ac12ec4269cf87acd235469052b193b048774",
|
||||
"tests/test_body_recovery_lifecycle.py": "b4cc6df60863e12a16ac555ecb9818e318f6325cd989bc97d5bb6ed737857a73",
|
||||
"tests/test_native_infantry_cargo.py": "0d27423c6834503840813327d0d3ba0d675206ca759260ff79cb775adf83e314",
|
||||
"tests/test_native_infantry_setup.py": "8d935cd24ae49ca8a11f9a52fbd73146e567b8f36e0a7acc6565eb3e7d55f1fd",
|
||||
"tests/test_view_distance.py": "ddc6cbeb41485f14a4f2ce23a7a86f1708e7e335bbeecee6309be49fd72e00e7",
|
||||
"tests/test_optional_transport.py": "16c72845dd07f4ade7de361136c04bd264ff75634152e83a19d19a2fdf59bd0e",
|
||||
"tests/test_terrain_adapter.py": "bcc6f469a12399f833651c591b3e1ca9a8e7f49d8b98e223a271fddc245bc4c4",
|
||||
"tests/test_terrain_sites.py": "03ffa5812ae4fa282174f29b5fb3738fdeb261ea26ddbc44b9cd3b3d61c008c1",
|
||||
"tests/test_terrain_airfields.py": "3a6e7151902657a38d8bf0510fb5c1a8049408d4f84d51ca260a0b5d651431e4",
|
||||
"tests/test_vehicle_alarm.py": "1b58d6b35f080003ecab03b23c8da68e0ba2a6a82db4edce89e6031d83f26c82",
|
||||
"tests/test_hq_mounted_binding.py": "40b6af209178cab30b2d6b4420acabf2086532fe0d3f836caaba60e369bdb905",
|
||||
"tests/test_vehicle_alarm_perception.py": "6c8854088c28e2a7b4ee8d38a1f10f66eddfcf9d67afdd460b395d19877b7191",
|
||||
"tests/test_hq_vehicle_seats.py": "8f35fcbd30e51b5576349245a102105926c7df738f3f77abb6ffcd780c6cb62e",
|
||||
"tests/test_native_alarm_hearing.py": "7842886a177aa9fbb6b943b72703c4dafd18d162f26581ce30ce04289e7e9ab5",
|
||||
"tests/test_finite_magazine_reservation.py": "dce435b1dd2d9b1e0ae1f54b5f911ca4207227c4c23586b4f4701c6a6995fc8b",
|
||||
"tests/test_hq_memory.py": "839c7adc63dfab023fafe1547e29caf0ccef52882c5d2fad7b342691b562a41d",
|
||||
"tests/test_supply_budget_admission.py": "3d0e1372f89873470c336038c667df38d2e2e71b3dc2abfddd1215939c448fc5",
|
||||
"tests/test_native_mounted_swap_probe.py": "08674b3c0121756fe94762bd0acd5b6880ba5b234691557851eca5558718e2ea",
|
||||
"tests/test_mounted_initial_stock.py": "cf7d101d8bd31cbdaaa0e7d66f0d8b39aff9b3b70acf51641902c6d0de5be096",
|
||||
"tests/test_spawn_scene.py": "d89ef752906c1cf27c4cc7292997a4d793ddb3b56a6dfbdeff837978350167be",
|
||||
"tests/test_spawn_preload_timeout.py": "db3a3aa42c20f047da90cac6755cb283d15cb3de6a22a57a6e1f352e9b4511dc",
|
||||
"tests/test_hq_uplink_fairness.py": "fc4808a2045522ac6038eef9c37c75ef133bd8af7ffa02c6c1d105b3bfd6c555",
|
||||
"tests/test_hq_pursuit.py": "7ccbf51c7317097d9db6a66cc5be4bec67e542187f0fce5b4f3750bdc4311733",
|
||||
"tests/test_supply_convoys.py": "be4fc6376a77296c6721ce966ea33fd256fe9d76af8705a056a88b32c4f16710",
|
||||
"tests/test_native_mounted_courier_probe.py": "4dadd3157f7da193ce8996b5b08e802f891295ad82a4236366f3018c778c07e7",
|
||||
"tests/test_vehicle_maintenance_stock.py": "e09a6e98f314eef1cf487cbaf8b2185bda950c12329939214ed1ba95713a097a",
|
||||
"tests/test_hq_coverage.py": "d790ae0e5b533f702284e6deeb2c84f7ede5c89bb2b6a68847400e915d50c146",
|
||||
"tests/test_engineer_mine_site.py": "9c01b326f4d171041669f6aff25a1750ab80cec113be4481eeabc471c45ccd80",
|
||||
"tests/test_engineer_mine_hq.py": "822f50e363be3ba03bc5d12748cfc93d83e93552d66797b01afc6460e7ab7d5d",
|
||||
"tests/test_intel_discovery_feedback.py": "49b2fedd8704ec67af60a6bb3dd156ddca28eb334d18b20692ab1e4659f439c5",
|
||||
"tests/test_engineer_mine.py": "3ffd360652ba66d4bb96247496e9b34175cc35c4e5d665f6524cb1cbd563bb73",
|
||||
"tests/test_mine_intel_discovery_integration.py": "bac2c8ec269d6e51a46fc17893de4114a5613f37899a1e044a9132d97d44cc5a",
|
||||
"tests/test_hq_delivered_task.py": "076aecdb0ff0d614cadbffedf9d9f8cbb1383fb94a468bd92050b31251bcbce2",
|
||||
"tests/test_hq_assignment_lifecycle.py": "4cfd15d1655b7cfadcbc08104bd01cdfc9960dc88a95ebc5a9958f6f71ce4aea",
|
||||
"tests/test_hq_task_reservations.py": "aa6c5a38618d4bb469c84f18fb7d1748501b4608bd1929cf7e0a6600d248c0f8",
|
||||
"tests/test_terrain_takeover.py": "12365036f81e0874910f100626d2e26b4b2a83042327a927727fda87f05e80db",
|
||||
"tests/test_ai_stinger_admin.py": "d645813857f85854aeca60c9010689662d7cc43f298256d73ae37b05d74e94c1",
|
||||
"tests/test_hq_convoy_dispatch.py": "945a3cba06694608b4880408a13447ebbbf812d400fba3af5b7a57cb006a1f6d",
|
||||
"tests/test_native_maintenance_refuel.py": "15eb8c0ee75778fce2a1caace045940b63cd253f81d16a081f669643b9fefcae",
|
||||
"tests/test_mounted_ammo_pickup.py": "cc4f70870b10beb2595ac4fb030952cc25be1fe1d9506f54dc3901fd1baecda4",
|
||||
"tests/test_mounted_ammo_transfer.py": "cb354bfc71560f430101aae9421f04d601bc2cb0788500771d66ac750df8ae72",
|
||||
"tests/test_mounted_resupply.py": "c17525ffeca6791b4542ea0c7641e4b220b03bce506fc2fda924aac09e8b24ce",
|
||||
"tests/test_hq_mine_sampler.py": "67186d8f6609c3544a9c60f57af876b046618f9b586b2a56520d3a5c8a0e6e22",
|
||||
"tests/test_hq_status_sampler.py": "b8582fb71b7ac77a23158ccd6230e8124ef0432831204e4b8992dee3a9c10062",
|
||||
"tests/test_convoy_mine_stock.py": "e0073271428f00edf86251c9af5530f39094261b05474058df6b794bd958091f",
|
||||
"tests/test_infantry_supply_pool.py": "6fc0053337bbb889021c6eba608796ef946ff4d30a1b01cf678927293218d9d2",
|
||||
"tests/test_automatic_equipment.py": "f8cb478df7979ab9e35c2bcb084e75b2571d41e21e68c8ab28249e01f5708c92",
|
||||
"tests/test_admin_menu.py": "53286ed86862e4b71614cb3ed687aa68893fc024cce203368bf54589ab92703b",
|
||||
"tests/test_admin_providers.py": "4869c86d2cb8c17a383680d594131f4053a051eb4af369657523e20f68311de6",
|
||||
"tests/test_native_mine_stock_runner.py": "51b30d4d7d7543b1674285448d382dcbca55272a0403ea977d2cf08db6a5290c",
|
||||
"tests/test_native_convoy_mine_stock.py": "f069900030cb644ba9e04adb01a6d847a1afa6248a6c458f547aa4fcbd4b5a40",
|
||||
"tests/test_native_mounted_ammo_probe.py": "dd7169d8127fef52a4815fb4ad8227307df557170da6a22723c765bcd74ba340",
|
||||
"tests/test_hq_mounted_ammo_sampler.py": "b1c630909e5227d0f7bd0d69d7927403fcb0f33e7297d5fa52f96001720f8442",
|
||||
"tests/test_native_player_death_fixture.py": "0d0825ebb948a68ff8b8f5cbdcc7e27bdf940e5af1e277ba1780d03e12f2ba33",
|
||||
"tests/test_enemy_loadout.py": "0cdfd28ce4d9c89223a7f439eaec1cad762cfc6c9e1cc4c3213d4600a3501b37",
|
||||
"tests/test_enemy_equipment.py": "bbc753525bdfe05ea9ee7fcf45f3158307ed56428518b08845e097fbcdf299c0",
|
||||
"tests/test_infantry_mine_transfer.py": "55caf2735f2d86aa4cc474ef2d3998f847935e0a0b0d881d5390b2039a055704",
|
||||
"tests/test_infantry_mine_coordinator.py": "2fdb2138685a329cef11557973450bd5189d3d7d657dac5cc51b3df2530fab6b",
|
||||
"tests/test_enemy_radio.py": "698c3ea5e1a3680d5b0438c50a341e53dd09f08d2f1f935e59667bc72ae83765",
|
||||
"tests/test_hq_planner_budget.py": "c9e6b85a3d25267c187f503f903136a16aee382e9d842f61ff88c550b5bb4e51",
|
||||
"tests/test_ground_depot_lifecycle.py": "927a6f15560e177c838322e18ef555ce7db3acde0651d1a43353a2c6e805f3a8",
|
||||
"tests/test_ground_depot_loading.py": "61b31dc3c2a6cf6bf0aef67fe3ec92b4b2d51ee93fe47e7cc8dc77f2235747af",
|
||||
"tests/test_ground_depot_transfer.py": "fc37626765919dd449432d445a17ec25a390ed687807571a1c5e2570c4ecddb7",
|
||||
"tests/test_ground_depot_provenance.py": "33880dbf308dd4f338cc4e4bb04449d662cc2fe0941721a819528b7f7a6decd7",
|
||||
"tests/test_native_observer_selector.py": "bafdca52e25cc45d94f2fc306c078e5e59d48f2e2540248acc3822b42668e937",
|
||||
"tests/test_chat_client_ownership.py": "c559dbda1af996455038da9eeb5ffcfc6332da9b93082da8ad3071f5d72b8bdf",
|
||||
"tests/test_mine_intel.py": "db64129001c0010ad11389ff764356893ac7c4fbf45e7c82aa56bbe4c75f4929",
|
||||
"tests/test_objective_map_element.py": "b02e59f977aa81462649282f99e70b0b6320b31ba0ea46f586e9ac0c5d15b884",
|
||||
"tests/test_objective_map_presentation.py": "2418f4c0f370bd436b316e56d5ead97ff4b6aa7cdfb3777c9d2af22486148268",
|
||||
"tests/test_native_objective_map_probe.py": "ef0663aa7d620e06fda63fef578abb397a9dcd8fe5336af5445056e32e19aa28",
|
||||
"tests/test_hint_delivery.py": "6066e2b00e54553ac298a3f9b37060a23851f5ca5d4f98eb25e502b2c44240a6",
|
||||
"tests/test_observer_ownership.py": "8e485c3d2e6ae90df82cbc7829996138db8e9a5a4a42a5e2bcaa3bffa61368de",
|
||||
"tests/test_hq_ammo_reporting.py": "bdd4a30440501a2861aca80c951098c48cf999023eaaabd9a2eef82174f8173d",
|
||||
"tests/test_hq_ammo_sampler.py": "e9c2d147eb1547be471df7459f1be815cdc9ef0a64c9a4ae4ef58936424dbc55",
|
||||
"tests/test_hq_multiconvoy_supply.py": "e58d1d6eba48b2c5c6073d54c76302b2a7250ad22176fa94c386e1bc2ce65588",
|
||||
"tests/test_hq_watch.py": "51d5a728b0be83ff957c0df61289d14f90aba109b49365e457c9b200d5ede36a",
|
||||
"tests/test_infantry_ammo_transfer.py": "0d2ebe4d5c18a4187745784013d07df621b8495967d0c01f39bd5a68ae574d26",
|
||||
"tests/test_infantry_resupply_coordinator.py": "f9cf7d583a25a12cec9f5f80539b2590449560331f4ef34e33ea2db283c5d54a",
|
||||
"tests/test_infantry_resupply_planning.py": "9929ee0f5a3272c348ee329355b6ba67fdcb745fc9ce1f377f6c989eae91bf9f",
|
||||
"tests/test_mounted_supply_integration.py": "f52acf7a16c9d5273894eb5a4afe7657a7c97de2ed72ba06671d25e163eea5d7",
|
||||
"tests/test_hq_mounted_intercept.py": "444411a6a97ebaff69a056302f444f986daa5f6f7f792e622fdbcca731f939c8",
|
||||
"tests/test_hq_mounted_dispatch.py": "3eb793719b21d508ffc294f5a1bf5c5153f5717a1408a8f779f0a4c1ddefe2a6",
|
||||
"tests/test_terrain_presentation.py": "05f070c5730a9f7f0aae9487a313342785a42682c9beebc3b10cac270b514f18",
|
||||
"tests/test_terrain_selection.py": "80bd8eec34f58ca139efa298509f0fa9dc01b7d35e02d2d62cb593b131c19211",
|
||||
"tests/test_native_mounted_intercept.py": "24810c0a22b221604e1fd8e6596f2d409e558699c5070fef7aae1f74a4fc2f5d",
|
||||
"tests/test_server_item_catalog.py": "c5aae32a55552095e49d9e7884ef88e1cdb4bdb257f850af67d387793c327e74",
|
||||
"tests/test_item_preferences.py": "1c52bec3aeb47b224adb3ee4385b68d4ec00214bc9f5e08fffb932af07f785b0",
|
||||
"tests/test_mortar_transactions.py": "5db4ec737355a7ae995240dc93f1dce146e07c0110c2da2c23451f180b134d26",
|
||||
"tests/test_native_convoy_probe.py": "c81046b5b3f7734f2f53dead61b01a3d81c96e841b7f9a4478c05702b29bde07",
|
||||
"tests/test_convoy_lifecycle.py": "fc18679882f1666a38587d151aa368241c5af115f3a8269dca81eb2617eb8afc",
|
||||
"tests/test_hunter_lifecycle.py": "4d228ec10c3d5f02ecf0eb755ca2773e3496a2c25a1e1c355e6ec79ee2ef53b6",
|
||||
"tests/test_convoy_survivors.py": "415cf9f5d23cce363e483d4bfd0292b58ab1fb3d9e727f9581271aa65c1c3109",
|
||||
"tests/test_hunter_capture.py": "d88b3a830c350e27a08fe5977cf85247dff140b3dc9135062eb5e7774b400790",
|
||||
"tests/test_hq_mounted_intercept_pair.py": "a8d758b1ff20bb6d1a95a9ff1d38f687e43c819c279f5ea9692e84f766dc4fb9",
|
||||
"tests/test_convoy_intercept_pair.py": "52b55727b7c2441d43530efa701045b9439b9175c71fbd2ae7577f851d2c98ec",
|
||||
"tests/native/inventory_control/README.md": "29f7086ee223b1cfe657932c7f236a1640876503f69f6f401451c72a5452695d",
|
||||
"tests/native/inventory_control/addon.gproj": "18dd7db85574119fac5057091c4d9991b247855f67f06bb86cf440f429b5911f",
|
||||
"tests/native/inventory_control/Prefabs/ERRIC_InventoryMode.et.meta": "6b4f66f296d15e7e5f82928cf1bdfa58f085421ae9b6f24afc5ac922d2a8781e",
|
||||
"tests/native/inventory_control/Prefabs/ERRIC_InventoryMode.et": "98408ab80347058f5b79b1103b849b19beaeb97a9ff81662b59fe6100ddd6bf8",
|
||||
"tests/native/inventory_control/Worlds/ERRIC_Inventory.ent.meta": "ca0420504016fa2aa7220712d13b7bf7748832d8d58b085afc4373f4abeb1339",
|
||||
"tests/native/inventory_control/Worlds/ERRIC_Inventory.ent": "22324bc7eb0d80dec8a1f3bd61a9df9201b72f749f4e751766b0c9d0b7d46b87",
|
||||
"tests/native/inventory_control/Scripts/Game/ERRIC/ERRIC_InventoryControl.c": "8707f35ecf48813bc4e72c599fe2527c44807493496218d18e89aa8739eb59f9",
|
||||
"tests/native/inventory_control/Scripts/WorkbenchGame/ERRIC/ERRIC_InventoryControlPlugin.c": "000d8fa3aa6412a5adb1561333f840f27185dc42fa61569c311b0607df0f2420",
|
||||
"tests/native/inventory_control/Worlds/ERRIC_Inventory_Layers/default.layer": "80a5f0b6c1bc8761469ea69978967dffc070ba9f98c5125430a78a506684c39e",
|
||||
"tools/native_command.py": "883c5955018e43c9451e3e6cb56e9cc8491c77920be3264478612a82c248cbbe",
|
||||
"tools/release_check.py": "65df1bff33638a583905ba5f6c583a095b9f7e38186b6f29d70b72a61525589e",
|
||||
"tools/reference_guard.py": "545b67111927d73bf2020f291528ea6f2f687a994b3e37bf567f239c32c8cd78",
|
||||
"tools/native_report.py": "bb75ef5fe551f45776d441f15a49ccad4daf19abca505187f9b2d30102538658",
|
||||
"tools/archive_native.py": "445736e18e645f1d6fac0aaf837627e94179d295c21a6a67a8e84b5b468758f9",
|
||||
"tools/compare_performance.py": "568acfee2525b8924f3e7a39784e8641f76d63bb3703dcce8251ab9ac9bb1854",
|
||||
"tools/source_manifest.py": "c1a985348bad16f3b5b69ef7e545b6341eca298d8ae08111b8340b31535cc199",
|
||||
"tools/native_provenance.py": "d4777f1f6ffed6993f7009ee2af41b100e8e0138d60ebfdcc5404ff2128f8e2a",
|
||||
"tools/dedicated_preflight.py": "ee56e05a15ada3bda80fecd74a024d141dcc3df00416821ac2c1a29ff308123f",
|
||||
"tools/dedicated_native.py": "35e0c1402be1d54f1b4345de8ac4fa7fc73f8cdfd09c7cc6f832a39dfb33e621",
|
||||
"tools/source_bundle.py": "e8b06f9340a8d67da992373f4642385a028acebe9aa1b3752f62cab95c8fce98",
|
||||
"tools/native_package.py": "32195bf9b43024028e9931a99885d0a131b28ba1b77b32e9d6ed70b914625559",
|
||||
"tools/pak_inventory.py": "95b0b64ab1f89a2142385ca596016fa131b49d933e31aef42d9bf18e2ec01383",
|
||||
"tools/archive_packed.py": "99cec180db72dc93c1ea8256bc316d42c23ab02843d5331a9d228b1a06a71d57",
|
||||
"tools/parse_native_stats.py": "32aa0935d63e59ace29fcdb1a02eaa850898b21ee4cce011a21d364e3dd69c75",
|
||||
"tools/headless_display_probe.py": "4ec3851835474eb4d8b84fc74c20d08567d92906ce832643d911f1d062abf0bd",
|
||||
"tools/private_prefix_cleanup.py": "336889ddfbfe8032eef584d8e9e222d85e33a039441f8fe9c4904e1db8ee10e9",
|
||||
"tools/headless_input_proof.py": "1952fb2ba577b80420133b9698b6d718d25211c86f853de8d22e0acb15f9f9c3",
|
||||
"tools/headless_inventory_control.py": "194121b64ee39c31cc25935afea8ee9dddbce04cbf26aaf058e25d02ee9828fe",
|
||||
"tools/ordinary_look_evidence.py": "3b6a0415352295e15e988ff78659e81981001e1ee03430a5b4c2df1518dd9a31",
|
||||
"tools/ordinary_gate_evidence.py": "5f02596c92b945b28081d861e466f01897875489e673bf0feafa6e4ebdae1708",
|
||||
"tools/prison_approach_proof.py": "fcd2936fa5a67ba6a970c56e8e4d04cc76bff6ffc6087ac95085716fb5fa673f",
|
||||
"tools/prison_approach_evidence.py": "12f1618b16f845d987ba928c8d95f2d448bc6a600fb76f8870b2ae8fd4a660cb",
|
||||
"tools/prison_alignment_proof.py": "37bd011642ca96dd0279e90cce785fc689002011b3977055f15c0dfad4161bed",
|
||||
"tools/prison_alignment_evidence.py": "67d5675c13b2de75110c53b99d1bd88e0eb0c36673488312711b03fec32e4235",
|
||||
"tools/ordinary_gate_choices.py": "59a0055d44f2f5b27c530406e5cbd46847357285d2d260d08e5c34cc53b7a795",
|
||||
"tools/prison_action_selection_proof.py": "00d397bf9b13405a2612678ec866f09d452acb4079f2072eb97c79a7fdf650da",
|
||||
"tools/prison_action_selection_evidence.py": "668b1d6ff86903ddeb702fd0c49d8d8110cd58559062d6d650f2962e4e321ab6",
|
||||
"tools/prison_held_action_proof.py": "450f03c6dbb4960d9f15a0f9c43ae5a18ab00594181e23321a2eea9b59ed47fd",
|
||||
"tools/prison_held_action_evidence.py": "971a630d663291d50c407dbadf6005e17ea035db88b5358e5ec1f6b24e6824fb",
|
||||
"tools/project_schema.py": "cc1527c5f68fb75711208f293f4751c206fedc725934d9db7d4b7fe539f60cec",
|
||||
"tools/external_addons.py": "c389b76c4d5f0f763420cd7d564d3ff5ebb7350cac10d2a26164c17f59b72dde",
|
||||
"tools/headless_game.py": "e7cab472ee51497343174cb460022b3e4591f148d4acbc6e6f881fc68c0fda1b",
|
||||
"tools/packed_external.py": "46203c078a82e825ec2e82c2495e6e165aa7ddc92368d2fe7786d87b957d3096",
|
||||
"tools/import_settings.py": "8b41ea8fb45e5c3840dad67f9d2494cba5dfa1e0e67d41e334fdd04e515a68a1",
|
||||
"tools/prefab_identity.py": "26088826275e84dcac2dbac771faf0552545791d30e9110df1ce88ee0d5a81ad",
|
||||
"tools/terrain_adapter.py": "d5ca50a5dc3ab4ba8b7d8cf2c72a9c3579b6567efbad5572acab991267116417",
|
||||
"tools/check.py": "53727a193b89ec4682abfcc429fa73b49cda279510e4747bb91803684d66b342",
|
||||
"tools/headless_workbench.py": "57f472b7a7642f35e6c2f0e2546631ee941a6f1ea6a5f85365ef59042d674264",
|
||||
"tools/desktop/kwin-right-monitor.js": "ff2b6f7b9d1b1eb9f92b61b39df18f1ddc44be5540e18b1fc1829eb2fc1fb1a2",
|
||||
"terrain-profiles/nogova.json": "4456c27cd7a4b2fef329051beedf3ccd683c94a684580bb07a35f16a9c59e743",
|
||||
"terrain-adapters/nogova/addon.gproj": "08442b784c8ead8362c19f084a9812e70783069da7740cbff7eef21f9a07e084",
|
||||
"terrain-adapters/nogova/terrain-adapter.json": "f2bdbb3ee3e03300f03d8ab184dd59f1b016c2eacb70b8873ab6fe5dbf1137d3",
|
||||
"terrain-adapters/nogova/resourceDatabase.rdb": "b12cdbacbd4fed63f460a19697b2ae8d1cbde7f9e725b73264ba3acd3584b2dc",
|
||||
"terrain-adapters/nogova/Worlds/ERR_nogova.ent": "6a2d45449ceedd13632a53c2ad612d155d3fc76b029febff7e3a5350d66952a2",
|
||||
"terrain-adapters/nogova/Worlds/ERR_nogova.ent.meta": "d6c41b4e1251829bbbb079a699a94a318b1a94e9eeade6e3fd4a019e0967c118",
|
||||
"terrain-adapters/nogova/Missions/ERR_nogova.conf": "60d542d81e48c15c838fe267e43e75273c7ec805d3a6031162dbb26fdab62889",
|
||||
"terrain-adapters/nogova/Missions/ERR_nogova.conf.meta": "cb6b7feabed57784ff41ac6d82f37df05f0e831b3f87bd18b1612332c61e21c5",
|
||||
"terrain-adapters/nogova/Worlds/ERR_nogova_Layers/default.layer": "2e9f780b1c2cacf5e34de4eaa7aa5010c0b4c81232e54246f8f466b32f909f5e",
|
||||
"terrain-integrations/nogova/terrain-adapter.json": "c6cb3d243ba68946add810256501322026e757027ff7e83cda40a02471dfb1a1",
|
||||
"terrain-integrations/nogova/Worlds/ERR_nogova.ent": "6a2d45449ceedd13632a53c2ad612d155d3fc76b029febff7e3a5350d66952a2",
|
||||
"terrain-integrations/nogova/Worlds/ERR_nogova.ent.meta": "d6c41b4e1251829bbbb079a699a94a318b1a94e9eeade6e3fd4a019e0967c118",
|
||||
"terrain-integrations/nogova/Missions/ERR_nogova.conf": "60d542d81e48c15c838fe267e43e75273c7ec805d3a6031162dbb26fdab62889",
|
||||
"terrain-integrations/nogova/Missions/ERR_nogova.conf.meta": "cb6b7feabed57784ff41ac6d82f37df05f0e831b3f87bd18b1612332c61e21c5",
|
||||
"terrain-integrations/nogova/Worlds/ERR_nogova_Layers/default.layer": "2e9f780b1c2cacf5e34de4eaa7aa5010c0b4c81232e54246f8f466b32f909f5e",
|
||||
"docs/addons.json": "ccda7c1aa6bf60524cd983ff863e178270445ef193e7418967b11cc49eb366d4",
|
||||
"docs/migration-provenance.json": "e671a9ec4dd69097a7a7f974c8a1ab0985b8070724847cfafe2ede0af88e7bee",
|
||||
"docs/dependency-audit.json": "d5e6cbcdc59c0a25e5748c590d6a195da9c97e8296119e07185f4c360bdfc4ae",
|
||||
"docs/architecture.md": "4dc8f223a793b36c92c5a70de9d2114d0e70fe36ddef873dbaef96ffa0bfc8eb",
|
||||
"docs/migration.md": "04387d3dad83ebdba4e4076fcc8ddc903a16836a3d0bf79d504d9006e6e7a17d",
|
||||
"docs/runtime-testing.md": "82a255aeb0c4cafa34b9fce9839147739c72e2e163dad18658c01cc0ee199faa",
|
||||
"docs/validation.md": "2810c18d2445c27c50f94ef92a781f9281cb3bd88ab9235834194abce3266262",
|
||||
"docs/acceptance.json": "b58e56be792c7ac2cb792a70e0b560989e59b1607fe9533f6f852e2171df9408",
|
||||
"docs/native-initial-snapshot.json": "94ca2c1fcb003823b7edc719d923df1d2387543f975fbca7ad1fb5ad3628889e",
|
||||
"docs/native-followup-snapshot.json": "156e5de03b28b0cb717e2d832afbc6dd9bfa966c28d2f9b9416415e7643ecfc3",
|
||||
"docs/original-resource-inventory.json": "bc52a981c42f933134e637a027d08a0924abb19784782731590b7ef0b0e2e73f",
|
||||
"docs/base-replacement.md": "b8af82e651366198a93824b7569430faed4f0e04018a5438a92942b98bfece25",
|
||||
"docs/site-authoring.md": "7a2e66e75ca3fdab43bd730f16e864c31909ab42fe060b00715caa13b8352ce7",
|
||||
"docs/original-feature-coverage.json": "fa260dc8f7ef94e327c9d0f6a888070c608a19f75384bbefd335915383cbfaa4",
|
||||
"docs/original-feature-coverage.md": "8386a0f56e11041915ec023a078dce627be5bb3732bf148b7d9955f19342cacc",
|
||||
"docs/configuration.md": "c89d23793e2cf8d8a9611efcc7dd584a80b568b30d9919ae8f9c6a96a6077069",
|
||||
"docs/extraction.md": "215c96cbc004b3817a70ee40f30b181a72e78123e69e16a3585ed4d8d566837d",
|
||||
"docs/mission-time.md": "29d9a18c7acef0587764becbd04c2c63684644a05c7b3b2fe77defe1881ae7a0",
|
||||
"docs/native-errors.md": "f0cbc674803f5254db0c823bce2df437cad121756e19f6c658087d5d2fddb349",
|
||||
"docs/ground-transport.md": "febce039866e290f1cdadf53ae2f6422387b1a4778264649c04727ee954c2ab6",
|
||||
"docs/radio-defence.md": "82c9c30ac75688b17fd7a5bc52e3e0b9843f2711442d4b8e5274687382c8bf06",
|
||||
"docs/performance-comparison.md": "063d46961611efb49478e462f44443b45eafa28b8ff3ca08f799b1652ba1a91f",
|
||||
"docs/release-qualification.md": "7bdf42f51fe3a4c1d6187d0e6f5248ac9a68ce16873281909bd96e1cea6c406b",
|
||||
"docs/player-lifecycle.md": "35fd066a50ddb2bcc78d4a448811e0763263cb0605161ccf43713b5e4a5f9cf0",
|
||||
"docs/base-gameplay-gaps.md": "8fb9ce4bf3d36507fdcd03af9681657c473bdf1f1d188380e7e4a83d47b10625",
|
||||
"docs/objective-widget-lifecycle.md": "3ec0ee8c74b2a3117356477a16125a077dfbf010050e53280a00769b710958a0",
|
||||
"docs/prisoner-loadout.md": "0b27f5fce4af457bea7133e461dbc37d8fa73fe126c8c3de90d723b358af389a",
|
||||
"docs/native-medical-fixture.md": "3c915a74afbcac1dfa45f0c1e9d8d4801f33704cac9ce1b66355121805ecfbb1",
|
||||
"docs/recovery.md": "026b513982530b8d65a6a1459e8707cd4abaf56450abc00f8449cf01fc660d0d",
|
||||
"docs/admin-discovery-setting.md": "a140f09cd1f27635824a9fa27e88e99259fc0030ff3228dd1376e2e949edbc1c",
|
||||
"docs/environment.md": "2a58f8a969348e42e09e45b906a3d61fb65d887ae2814547605a01987747ab6c",
|
||||
"docs/faction-catalog-source-hashes.json": "46060bf2bfe6c39517e3140acb004f9b129c26695a7ee890b4aae571b3f0da76",
|
||||
"docs/mission-faction-catalogs.md": "2ce3e49404d103a4c372ab0386de3bcf831bee87d95b1092372345a3d32ef8d2",
|
||||
"docs/medical-kit-teardown-ownership.md": "9f5c6636bfef46802800a4cfce27833fd1251067be892d95f4ce4f07469fef1a",
|
||||
"docs/site-patrols.md": "5956f70a5ebea4167819a43c2a15f8b6d6460bc724b000f989d5ad5265d48d56",
|
||||
"docs/environment-restart-fixture.md": "1c1bd3872a67c5a59c12c1293d075d52fe0b563fcd5e9e10a1ce1850c7efcea9",
|
||||
"docs/inventory-identity.md": "02092960cf937b175c5a190b426b84e0383104fa71f324c8b3a0ceb4fbe01164",
|
||||
"docs/radio-document-loss.md": "64e6dbc3a62ffd164321f7b3c1a80de05a74c26b1541bb67f6fd3513983d5fdb",
|
||||
"docs/ordinary-game-startup.md": "e979300482ad716c7a8cee532dbe3ca73b7d265b9e94fe02bd3729359551c7b2",
|
||||
"docs/guard-source-hashes.json": "874845def1458901d61adeb090a464bbee7c7b18816406ae3780ff48fe855266",
|
||||
"docs/initial-guard-readiness.md": "5f2b45f3744f1f0316f198225a34e25d93eb4795bb05d6cfded8de1f1a116c18",
|
||||
"docs/casualty-loading.md": "cddb8b1aece65dd0e3366a493e22ece02f49b1ae5d626cb9931a188551f007a4",
|
||||
"docs/native-guard-cancellation.md": "880b545b8451a86f16ef47e0a378d081c8fecb1175bf9a8a122bf853ff9b816c",
|
||||
"docs/casualty-ground-support.md": "9ffa8df2c88e51d77c29d12a23df8032d8858b0c82ff079d476f773cb6b5825e",
|
||||
"docs/casualty-ground-source-sha256.json": "90ffa7d0c61e3823922ca9d4773e3d12fd5e9a1760a35c3b29fd12a53642f83a",
|
||||
"docs/guard-cancellation-fix.md": "41f8bd169de714df4df4a61189d47a37f9be2f1b1554edc99c29d8a1fa4aaf19",
|
||||
"docs/guard-cancellation-source-hashes.json": "a1bf169c021931cdcba835d52e420df44e5dd2ef351257f0be10f63c8727afd4",
|
||||
"docs/radio-feedback.md": "3aaaecb318e2f737321bd08f9deb9323af153f2ae9ed196b55104c0616dd2be8",
|
||||
"docs/current-status.md": "027a565076981f9985019baab93413ef127d7fbb5cd336b55e33a402f4e59af2",
|
||||
"docs/initial-extraction-placement.md": "643b50d231dd9ceb49b81fd8f061cd114a9618fd1ad5a6f88724c0199a5a304c",
|
||||
"docs/initial-extraction-placement-source-sha256.json": "a44a32cedc54a70f635bf5840e7b29d35850a2212fcf64b1fd36ca096f10fffa",
|
||||
"docs/native-casualty-remove.md": "a91a9a638b19d4e8759a7168e196bf1a50bbfb328e7812d88e329166c06e69db",
|
||||
"docs/casualty-remove-source-sha256.json": "4799a5843315fe5b5b2e933e8068a8b4bae6d924a49438eccb7f68ffc8e12be9",
|
||||
"docs/ordinary-launch-callback-fix.md": "b74b8d0d011f25c367ea07b37e1a5ef488a2b378542ddca63ff9e981e649b874",
|
||||
"docs/casualty-incapacitation.md": "2811fa294de0348fbdf8e78b0c66bc31a59dd8f1e43fe16af8da87cb7e789820",
|
||||
"docs/ordinary-diagnostic-splash.md": "6e07a87a03df81dd5c9d6c0f9602b26e08ec7fd7cf941b91f2b8007bd9d33e6a",
|
||||
"docs/ordinary-splash-source-hashes.json": "b2e277e3487c2b790bce741527555bb74f6b9820b9e5626950ab709533d9fa97",
|
||||
"docs/ordinary-identity-diagnostics.md": "62c34ac6c10331025792d1785272a1605e655ca3bcbcec9ffb4852968925251e",
|
||||
"docs/native-mortar-firing.md": "0a340bb6f074531633e8b1b1189082346f4149db7f8484c08f16207767994be9",
|
||||
"docs/finite-mortar-operations.md": "d277ae850de9c33b410343b1dd498fecbe9f5c87f7c4b74829f07ca894603224",
|
||||
"docs/mortar-transactions-source-sha256.json": "dabad1cbc6eb3dd6f66b3e9ece574edcabff94adceeb2494b1adb2147608dbc0",
|
||||
"docs/local-singleplayer-admission.md": "f7fec9a8ab119d006818b518684315e28c96c7f63a1d075922a5c8af2864a5ae",
|
||||
"docs/dedicated-preparation.md": "7462af696bdb65d32e764cd98a354c9059772252bfa22d3c8aff23bd2e636c67",
|
||||
"docs/mortar-aim-renewal.md": "c26041505ea6af56146dfc9f52aa2b0cc3955ded565fdcb76892a83d1fe2e56d",
|
||||
"docs/mortar-aim-renewal-sources.json": "ceb9162fc1a4fdabf44ce7aeeda5f6d91bfbab6c6d1eb1666ce4661f1a540513",
|
||||
"docs/native-mortar-transfer.md": "7d5086b8268c213375d56ed2dc3994daf7065b2076742d0af588c8ffce5f753c",
|
||||
"docs/dedicated-native-diagnostic.md": "37d3ccf17776f5c2c2f2c8bdb87165451cc4c14eef4173162b89378f67693cd6",
|
||||
"docs/native-local-admission-probe.md": "c2794b2475d94f2756921d98f676aa8224f6fa3bc95bb14ce45c2bb7648e1566",
|
||||
"docs/dedicated-resource-audit.md": "92dbd16d7cd1b018abc5e5bd0c12bf0859f5c7af8194477eaab835e2f3fe4256",
|
||||
"docs/mortar-report-deduplication.md": "cdeeee20885c947e10507b30a2dd19b086f7aa863730c266b574cc756d466bcb",
|
||||
"docs/native-powered-flight.md": "049186db16d1b18f5d51e6eaee3e5e4c9efad9e578774501f14288781b82b972",
|
||||
"docs/mortar-expiry-evidence.md": "fb980772ef0a04568ab98457b889d6af4f3ec216382b1452e6d66eb1165345ef",
|
||||
"docs/discovery-map-usability.md": "b70b210884b642db9e28c4eefb4bde4449858089c2d8b47c3dd3209d9ba2296a",
|
||||
"docs/native-hq-response-proof.md": "9d3e43088e4874a1f6d2edc853bc2f9dfdddbfd9df3c54802dbf499a74f9910c",
|
||||
"docs/prison-breach-progression.md": "31797bfa36556cb744390ec0c7c8d7a404fe91f18a2c20ec1c0e4f122648e184",
|
||||
"docs/powered-flight-pad-survey.md": "0347bf1cfa94855ee26f22c4fa10339baa566ea10d0dee86f9190ac8a33383d2",
|
||||
"docs/item-station-teardown.md": "7a2eb028f767ec52214f375f79ed162f9ee7432a08f4229afdebe90ed338e37e",
|
||||
"docs/development-source-bundle.md": "7cd8dbc04a9cfe87ff7479283477e788552484ea1b83ffdee4893620f1df31ec",
|
||||
"docs/native-flight-footprint.md": "ef178fb48a0fce61d4eb6fa1f5533e0107f9ee6b24a26655fe17ba7449f4c776",
|
||||
"docs/native-flight-input-diagnostics.md": "4dffc58b20b2e05f3fc3d700f3feec79cad2ceacbd5fcaf9bdaabf6574de23d3",
|
||||
"docs/finite-mortar-courier.md": "96306ab4f0313b9b403f7a37b9007bf83b237de433e0cbdf1b1de361d0b2e738",
|
||||
"docs/finite-mortar-courier-source-evidence.json": "970ed87a005fe31382ffda5da77b3f18c944a57d6d1477bf9289403b39d75054",
|
||||
"docs/progression-identity.md": "be443ecba1d0c7f104dae20886c1622286f768d182d69907d4859429fd07fc84",
|
||||
"docs/vanilla-game-startup-control.md": "ff0f24a4921dd3d7c52e2895c1905e107e28824d628924125ba7e6b6b97a3ab6",
|
||||
"docs/normal-splash-audit.md": "f377cfa81ccd49c49f5f63a9d15194b063aea3554afddb934ad3679707ff6874",
|
||||
"docs/native-packaging.md": "6f83aa58d8692f0d84bb8384d3fcf812f8f1f3ef899b20aff839db0a4e2e91e8",
|
||||
"docs/spectator-guidance.md": "ad805edd230933a80950f8702b1a65a177a4f80d525f8c38d96bb74a89f38dad",
|
||||
"docs/native-mortar-salvo.md": "4358b187a9e2f518259535ce23a2f601b2242982ef0d2ad6cbe4528d1edd9819",
|
||||
"docs/pak-inventory.md": "b50157dc0dc5e4630a1626c94abc0037e8fa5db6a0973a06f3f815100b141a18",
|
||||
"docs/packed-core-startup.md": "4c9e8c6d47bb1a0ce6146070ddf4a40e1e7ad0dc710ea60f03a45b2641a85040",
|
||||
"docs/mortar-report-refresh.md": "80d7709a20930e78d138cdf792de9ead5368040ef03a94975c0222d182a6dfac",
|
||||
"docs/native-packaging-control.md": "5e1df445af29ff9d161e2a385ca90cecf283f668ee3806fc0207b55c8774cb1f",
|
||||
"docs/packed-evidence.md": "cf67a65201cc34c7316e3b677c29f301f757a81ebdb0eff83e4f705739c8a691",
|
||||
"docs/native-companion-packaging.md": "e9fc608d900c3849958eb6224f46f5619a5cd5c2e78d47536a5e3bb8d3edc31f",
|
||||
"docs/mortar-activity-completion.md": "388756d09696d1b2bf5277a945a57edd07b792b40be8ebadb709fa8d2f62ea86",
|
||||
"docs/shared-map-ownership.md": "43d8134ce6d1e6f6561634a5b18053f6295f7d7d5a575b4831995ca254f283db",
|
||||
"docs/intel-registration.md": "0568ca6f283532d2ebfc28bbcb11202b72a7ed5ac011ea66ed772272e5e87e70",
|
||||
"docs/native-shared-map-ownership.md": "c3119a4742ec35f914de6f25751316fcbfdd794dd8b395285b8b22a5fb1ecb9c",
|
||||
"docs/native-powered-translation.md": "b1efa32020ca713fe95f20a714853a67bb5b64a09781ba5d1d7cadefd06b7a93",
|
||||
"docs/aircraft-flight-controller.md": "3df786d7ce4ec6d6b52804b2a2ca86a9980fdfd203aac7eadb204458b54d88b3",
|
||||
"docs/prison-yard-surface.md": "4e881eb8eababe03c4bcb86e46fab7bca47ddd1d3ed4cd570e2bdb2e0ceadd41",
|
||||
"docs/native-aircraft-control.md": "eed101ab213e180822fc6967264efefaa75c99ee97f433fc019da45d47e33cba",
|
||||
"docs/native-server-statistics.md": "c5256d62bc6632788057df57fe5daa1fe738904bf1042e0e1bb7785aae66f4b6",
|
||||
"docs/native-end-lifecycle.md": "84bacfc43eb6011b565ca1da1db325d96098be1d4434d24b3c8a3736a0098924",
|
||||
"docs/hint-delivery.md": "35c5d8a2d1e9eda99c2f82315171fd95ca0faabdf7c1b342a45fca7db6e22755",
|
||||
"docs/preparation-status.md": "bca0997a65e5f85076d6c516be22130e5207371f8b06e74a88a140e2f1bfc837",
|
||||
"docs/headless-display-probe.md": "08a5627abe39206610d0641323947367e6631e1f87e4826789f56eae2e6b8719",
|
||||
"docs/aircraft-route.md": "c23d28a742546cc774df045c3668406b77c573bb4183aa3eef333dc6b26821f7",
|
||||
"docs/ordinary-input-observer.md": "a068702b345fd9645367606a766d57912fb5d7c6dbf615d4ba5b731b4b817d90",
|
||||
"docs/headless-input-proof.md": "da0053ff71264178de63417b28a5e2fd53d641d0188b83e5bb7c75a95773a408",
|
||||
"docs/inventory-slot-diagnosis.md": "d661908156717cd8f3545a721374a6d82c3755859267f951cd3a7625e891bd80",
|
||||
"docs/inventory-layout.md": "f22bf592a0f560ac3db2de5526d8937f899af28006b873ada4d42acd7920f088",
|
||||
"docs/prison-input-proof-plan.md": "5003c8cc190ebf9bb3c0e6b53138480b91c38c4bea2d063e01b55dc934ce7cfd",
|
||||
"docs/ordinary-look-observer.md": "20a17b23bd1c057f2c6af96d24078362c132689e87ea41d331a44f0dde54345f",
|
||||
"docs/prison-approach-design.md": "2cc363678200287d9bc13a25f5b3f8fdd749d266b1734640079cd11c29e97084",
|
||||
"docs/ordinary-gate-observer.md": "75c2ad994dbdcd8cfed1c29c83d6a73de677edc98700327e36868ae8169731c1",
|
||||
"docs/private-pointer-edge-audit.md": "29eef2bbb8ff932523f0ff501a6b3609c171224544a6b7e02461aa1f519a8cc0",
|
||||
"docs/prison-lock-alignment-plan.md": "c857bb12700a5a6be0b63735d38efe6a9c03062ab10a450bac09fed66192434b",
|
||||
"docs/prison-held-action-design.md": "89af762f9d33e8a0cc4f461ff6cf0b55036df84a05ca26fda8292d9d9a7647d7",
|
||||
"docs/prison-action-selection-design.md": "9505c865248904c21a8096a53a18baaf11799a615968c6047dcc1ab2773637b1",
|
||||
"docs/prison-approach-reliability.md": "2d6e1e243788beb27cdb6bfe1afa5a15384931625ccc075f8ebd25e9f5135596",
|
||||
"docs/native-packaging-next-audit.md": "9746148ebc0ccb5c49ccbe7aa55ed6f8a509b95ba39c83bcaca706ff88e4a454",
|
||||
"docs/aircraft-service.md": "601bd40cdce1de39cbef3cff373b17cb77371493370bf57fa20de937c50c7b1f",
|
||||
"docs/aircraft-hq-integration-plan.md": "c6d2e5dfea4379c0b2b57cc028b7555f62d47661eb9afbf6611a3dd509179238",
|
||||
"docs/aircraft-field-route-plan.md": "a44e8f670363b85543d19fb508adc6840fc9f02b3cdd80737d36872ed6e3732c",
|
||||
"docs/prison-held-action.md": "3d1360d19d73d13189b4351e2c2fdb50da1e97a393f9f17191d4531921f09d7c",
|
||||
"docs/playtest-build.md": "f9f4d763268f24b3acd80c58b8e24f2a3ae30fc7730ee6ee32a1e762a523ea82",
|
||||
"docs/owned-ai-intel-migration.md": "be048099213bbf2daca4d38a9126f2f4d68e267894163f2ac641209a92f2c7e2",
|
||||
"docs/chat-integration.md": "28a94422f4339ef47a549540518c14afc419add48bd25b9aa98cdb9e3297e181",
|
||||
"docs/external-addons.json": "c340fe315efa9bad37ec9f249a259f51b833e66ea6f38bd774f1573753216ece",
|
||||
"docs/external-addons.md": "8deff9567694245390cccd8a22176c6d7791ab56686d93ee732841f111f6d3b8",
|
||||
"docs/ai-equipment-migration.md": "819ed66d7f68a83ca979ec0d78dc8dc8b9538e44317d5c5e70904a15cf488be6",
|
||||
"docs/convoy-bmp-seat-audit.json": "54811861f71c320c026b4d870cc4773a1afd16f9d7b894d709fff99db7959b21",
|
||||
"docs/ai-feature-migration.md": "3f5d301e48d9dac57be8ea0bb1e67a897c90462a9289d9227f02e58bb9648f07",
|
||||
"docs/ai-feature-migration.json": "68dfb0b523b650fc82648953d3ed390dc68617d0ed9cc5bce86145b3e7b99eea",
|
||||
"docs/convoy-migration.md": "1cf205d1ea3579a8300f75fb31b260f73f3267269c67925196d13ee548e1e461",
|
||||
"docs/hq-ammunition.md": "4e9e78855e33d6381ebf89a5586062d264d5a2c6a862faa7f9b58f1f1e10480f",
|
||||
"docs/ai-migration-progress.md": "3af61e888d2de139fac21b9dc9163ecd8b83bebd29921906559687d938dc0986",
|
||||
"docs/vehicle-security.md": "3ba235ec441d4215925b275a7a4f261d8851285deb7d0e4474958d3a78501193",
|
||||
"docs/vehicle-security-vanilla-sha256.json": "16ee4be00be331023668238f5f0272050f83031ee627f8c0fa234f88552aec5a",
|
||||
"docs/vanilla-resources.json": "d80edf67d429b578051cad1630d3c44fae70714d14a277e40294242f9b439783",
|
||||
"docs/vehicle-maintenance.md": "94362113ce91d248485b2ce25f9f99e60e55d1ac50d6d6dd728cba1fc797043c",
|
||||
"docs/vehicle-maintenance-source-evidence.json": "c84283c91ef554d8c6ded79ae9aecdfe00ad6922a150d4733260dc7a5a3d2fa2",
|
||||
"docs/radio-equipment-provenance.json": "ec67aeec4ec116dda41aba4f3f2448e739859b8f4082d548a5deb7992371f021",
|
||||
"docs/hq-originating-call.md": "ca1bbaa5f511b65a392d6dd6e361b6898a85b201f219f8e0bce25c30cef27a77",
|
||||
"docs/packed-external-development.md": "6e7cd3e3efc143de42ba44febe9b03811f7c263d4000593dfacaf28fcc3ef287",
|
||||
"docs/convoy-startup.md": "754b2ef659749d3307809f314eb2fcb4b16506c03e019f191ee5cf4e68a630aa",
|
||||
"docs/infantry-resupply-access.md": "8eb812945ba95660501ec3fd6626a354eadf9e68eddffb34bc66071e250e5fca",
|
||||
"docs/hq-local-handoff.md": "8225ba1ea2cef47930a4e04fea82a87dab92a8761349c3b7da5c918079ef675b",
|
||||
"docs/road-interception.md": "521b0cc50b29c8d3f60dacc184c2fe6554d83779367492e176ee3f60d82801be",
|
||||
"docs/single-mod.md": "31c628fb5bcf00486bddba527a8955e3f996cf31f9342b6e0bb2bdb314e38c19",
|
||||
"docs/multiplayer-prefab-ids.md": "b9dc13f6d5448871959d2f7872847af8824f21ec773f2683ed117d43c1a2a77d",
|
||||
"docs/prison-opening.md": "be60fee1542185bfae38cba9bacf1f181acf2a032af33d5eb853468a036cc54a",
|
||||
"docs/content-packs.md": "c543cbec225d6feb722c5f0a7bc848e3bda265bf89092bf5629b71d1ee4bf0ed",
|
||||
"docs/supply-convoys.md": "b1fcfc475da1e295af3f9129472fde5ea4003f9590b598fdacba01969b41ae62",
|
||||
"docs/generated-sites.md": "0caf082c45e4ac84af5749ff6554312f6db66bfcb7e3485611c0040bb9d2196d",
|
||||
"docs/mortar-stations.md": "eda8ce9201fc1fda4c777174dd83aec93e6b5aad35c3632aef7494969ca539df",
|
||||
"docs/prison-patrol-and-debug-intel.md": "c02f57e65981eb74773fc4db2a9a82ac80b5b45276735ad5cf2af4a767caf172",
|
||||
"docs/airfield-extraction-and-map-icons.md": "5f21746c0b685811992765e6909f83d4a47eb6ff11b17974405136bb5d858350",
|
||||
"docs/mortar-intel-and-prison-map.md": "116f87df517804919363836ea577ac032e9bdcebf33da83c19ca1323e1731076",
|
||||
"docs/radio-camp-security.md": "ffd6f794601dcfe46e3c373aa7a868782366fa6e234b189d81af78a40d84fc81",
|
||||
"docs/hill-scouts.md": "8d6ff1a57334fd915925396558c9f8fb915fbb1f8689fb72e7bad3ddc7367a9f",
|
||||
"docs/search-helicopter.md": "6220eb11e64245a580d85bad683baa07f0ae499b5d7673e973bc401e87907482",
|
||||
"docs/hq-air-support.md": "b090de3a96b93f67987c27de182d5c05ee1890e98816c6a5441a7e54d626614d",
|
||||
"docs/aggression-ai.md": "62c2c8aeb23eaf085b54e2e480ce066588fd2a4df2e1a5180333f7107bc2ebdd",
|
||||
"docs/enemy-loadouts.md": "3d00ed31e6a169658a5d950c12b278cccdd7501c791b18d6ab8b2ffb90d06cef",
|
||||
"docs/gameplay-recovery.md": "ae1ffa270ab27afe9e2542be2f0b96b6a8527f4ad243ba4247e1f772b242f9f1",
|
||||
"docs/field-sites.md": "9c3928a04b4b242dcf806d7b20ecf2f4cc636aff9a31ed90d9fd75cfaf776aa0",
|
||||
"docs/town-garrisons.md": "c58e6b669997f8e1763a3da289e6ac6b777b15f0852eb2cd81b4c83fe2783eb4",
|
||||
"docs/ai-local-awareness.md": "9583de1d5233dde3cd259d464e0d5a859de9a69b24725acc2d141822fd3f9908",
|
||||
"docs/response-medics-combat.md": "17192d50d1a406de0547ce30a19eb8d9b216730d6c07ba2a89939687096faf1e",
|
||||
"docs/infantry-combat-priority.md": "a2609f8596d58d320f088b0f5c2e5de8ced6d94365ae1a8407fe763ea2da5db7",
|
||||
"docs/gameplay-review-2026-09-27.md": "ee66710a5fdefc1d1ced769e15119f6f1449fb9268b9a62733a8b2f9b733bc71",
|
||||
"docs/helicopter-standoff.md": "d804e148383f71d4457e773f62664b99d3e28401ba756f3fe6e447419aaa4350",
|
||||
"docs/airborne-validation-2026-09-27.md": "54b4655df8a449da5abf6ff04097119f8b6ae2ce69613b57e9dca3cd6f821000",
|
||||
"docs/mortar-rapid-fire.md": "412ef3c1cda90f774f9134db1ab150cb1e1d09189598e1beaecf5d1395b04a98",
|
||||
"docs/convoy-coordination.md": "095fa1f425688ad920e86363c88490aac6bd623f0dbe8dd2f039674cdc5908ff",
|
||||
"docs/server-log-fixes-2026-09-29.md": "44f69289ae12f1f38b71045d613c2500da6b7b3adbbdfec7166d7b216bfbf576",
|
||||
"docs/body-recovery-fixes-2026-09-29.md": "ce9f2978e3f2da13fab0bbbf88150b1f6dc7400a74c6ba7418f8a8b704c6f82b",
|
||||
"docs/infantry-resupply.md": "2fb21a42c768319368d28781b04c3fa4945f22277f0b76be1ed34a74b77b85b2",
|
||||
"docs/resupply-fixes-2026-09-30.md": "9e21d5b29f20350a2ee8c7b1fdc839c199e5c98777e88b310f14f7a583d70cdd",
|
||||
"docs/mission-checkpoint-2026-09-30.md": "48104b9c2a089404ed93da20d15a0e57283004bc8dfe0cea0b7477909791c7d0",
|
||||
"docs/combined-candidate-2026-09-30.md": "c53428454b2a6ea16f5e691fd4333f6f1bf2a6ad882f0fd05504f8e71b414d92",
|
||||
"docs/multiconvoy-resupply.md": "90e699e15914461392808802ec6029382dab1a2fc1a63eb0ab534557565661a4",
|
||||
"docs/hq-chronology-and-admission-2026-09-30.md": "1254daf6a44f27df8917805ce506d2005379641d9e4c10d9b396f5f0464fa1c9",
|
||||
"docs/empty-magazine-return.md": "d08e227c4b7d273687e9aca6645cdcdef8d6a88843ba00fb938fecfd4c8df154",
|
||||
"docs/next-gameplay-increments.md": "ee1a22dd3a79acf95fd3e59aad4599788a0f10f22eb4dbe1577875a651a18430",
|
||||
"docs/terrain-adapters.md": "70cfd9f50ef5e339de7ae98f48bf501eb4feb018d6a5739b465cf7407fcfcd95",
|
||||
"docs/nogova-publishing.md": "1a80072599a3e9bd460091c4b3ea39ff78324304730494641c94deb2b00adab7",
|
||||
"docs/mission-hq-fixes-2026-10-01.md": "5379ddb403f7c3611b2e92e0a9b4c043278fe136e28f93293dae1027ddc30ab9",
|
||||
"docs/player-route.md": "57165a47ef27c2fbeada41c2a68e3567f1e2ceef99fd1ac1ca89277fdc0041d5",
|
||||
"docs/terrain-takeover.md": "3504c5bcf820adcdfc4640f90ec2ea4347ac96b4b425d11658b2868c21bd5db1",
|
||||
"docs/ground-depot-loading.md": "6913d5e05d779a1c6c3149ad5eea46555c685ce4e35490f05bd1f1ef89ce1ded",
|
||||
"docs/objective-map-presentation.md": "eccf546db122634ad3528207070b5aaa4005e72bd615f7ddd477d08481875305",
|
||||
"docs/mounted-interception.md": "20f80f34919c32535c1d57da7894ecadee6699f7f17b1993a32bed5837342bec",
|
||||
"README.md": "6f1b1fdc61fd7ba17861a284c5ebec05be642c0e116ba28306d8e16b8760af4e",
|
||||
".gitignore": "b965eff620081f103bc18f8e6a37c670629e1275261448ea3af22688695edc2b"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user