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:
@@ -0,0 +1,484 @@
|
||||
{
|
||||
"authored": {
|
||||
"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": "d39dc876487b445efc5c3fadc19e1e06c6900c5cd57c93a01f35c32aa27d6039",
|
||||
"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": "af8bb43d207d6360ae4dae06c7283a81f89d57a66b644f3f730496b0d47d4a55",
|
||||
"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": "ea6e80333cc8b6561fc4b76dd7811a7a539007cf60b4d85dfe4943ff7994f317",
|
||||
"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_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": "4acbd2b23803f1fde3701fbddffd56edd83693196f0be2fe99f3be9b79a12b3d",
|
||||
"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_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": "9bd0824995d3e08cdb2745f1cb539ddd27e422509814188ea6ab2ba56631e7cd",
|
||||
"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_TerrainSites.c": "2e1f2102e4f30535178ec3dd168d833a0996de87bc3d3b17e059d44ad3129cbd",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeover.c": "770291e7616daac6dcb0276e234cd1611aa3ecef8d82589c4feb1752fdd2fee4",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverClient.c": "694505e69789c7267f70fd0925c5c999f08cf9546e05d5d6ed394cb4eef23964",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverManagers.c": "5b7661a5617b7124a10d3c18e087a4e2363a967eecc5c87ecc94573f2abc07f2",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverSettings.c": "cc4a68484995ad663b112d92b20dbfcae2f549a586ef713fb3ea13353291d1b7",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_TimeService.c": "63867760876d523a1794f619118a52c5cd387d5d92756cd286b2c16a348a6c7b",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_UnconsciousCameraProbe.c": "7d5ff3389ee806ac213db5be3a68288b3399142ccc29806df0d65b34a0b2fc3c",
|
||||
"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": "0b4faaf3d4ee506730de48554100ea3d9443e9cbdfdf354f56dd8b121507b324",
|
||||
"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": "7ed538302204b54630fd07b91fe8b73fa36c11df3c402b31d23fb570fbe021a9",
|
||||
"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": "e1d5dff5584f1bf12c146df510c860c3e0e046c989ff758def88ecf6b49c6392",
|
||||
"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": "1f129b979abcb86ee10622e2866aa987807f070f2ab2d818af015e12a8bcfd8c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQAdminProvider.c": "c480e3cb22129d46babae961f0e8ac3ac562c3c360b33b3c58a7dfe97f2f07bf",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQAmmoReporting.c": "95c024cbdd35c02d9b0c3d389d29bb042e5dda6ffd2f5f0d896078274b3adb35",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQAmmoSampler.c": "258ad63c9dbf221f2d8402e93feb7b4d51897417954abf477004b5302e819bc3",
|
||||
"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_HQNetwork.c": "85e4d2cb27426c07b8631ca411f4b8f5c821f9f331c8c2719cbfa71b3687f08e",
|
||||
"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": "0769d4e7327439ce91b8f9ed0b47c4d92d382e97a1bbf62da8e6002ae4041bf0",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoAccess.c": "ab4262f78d54782c5ecbf5fff4ebe3da16d72329658d7e18c0c35387dc8b3c7d",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoTransfer.c": "401a29ec31712d113c203d6975e44aed7bb3166025454f4950a4c7cb6f5ad6eb",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryCombat.c": "5684fcbee128a35107312452297f7ae2bec28aaa1b63f619fa649f36df8ea476",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryMineTransfer.c": "b1648598070def582edb93e8160c3aabc62bd18777551ba34d9dca641c5f485b",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c": "966068555a6724e620a2c377b98d8dc02f141466c5cdd44636f86cb1cfe9ac78",
|
||||
"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": "34f7eaa01dc68e97aae8c1177b7f8fd6c15d5a41c6dc86de377f00b3613a8e77",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MortarSystem.c": "bc290ee8bed299d869598cbf41ec281653e0cc7f3c64c7214613df8a0fc83829",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoPickup.c": "7993e38efb81412d3c8efac28f28c9277e96f2a48ebe6694815834896c482588",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoTransfer.c": "1a764a46bdc0b7e98032b5716a144a1f44343f769be6d2280b0987400227be1e",
|
||||
"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": "827550ab1b3fd749414f064d20849e23b4ff0ac2d39585cc8d4388df581ca6f4",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyService.c": "dca3fc09fc11cbc719eed38edaf9b141ba86f440b4c5c28579699fbb296d0b6c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoySystem.c": "f09aa817c27d6af0ccfb544f4ab1ae2afafb63d626641e03d87cfe159475d821",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyTactics.c": "78e6b60596189dfe1ee64c9471c62aae5c494ebd9942cb11520babab7c01873c",
|
||||
"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": "45a40e5cc877c4bbcd23ed3219b24a9a329cfed09688ec39fbe9ccdbed1046a4",
|
||||
"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_NativeMountedSwapProbe.c": "a6721fcaaccadf8eddb06687c7d587d0a3e377d5d1bb0b8e72e630fede8690ac",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeRadioProbe.c": "84524163c63402755b4f834f74dfe6d8e5c397dc26031f532593c6f79f5a93b1",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c": "ff651e27c4a981df66f4fc5b55aadb262df75820aef707bb68c369e548c0c3be",
|
||||
"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": "997e4f33660de5bb54c2939898cacc8857736a73db2c6919888ae0e762050e5c",
|
||||
"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/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"
|
||||
},
|
||||
"unstaged": {
|
||||
"README.md": "5e0a88356396480ec805d755a45fa37afc2d0142b2e804fc39d9ba474a3fddc1",
|
||||
"addons/core/Prefabs/ERR/Prison.et": "c0ca5219aa2d6322ba7d094b92000640b259112ff6611a745c0713c8875d09d4",
|
||||
"addons/core/Prefabs/ERR/PrisonWall.et": "9b410b7c85bb001be4601ad93f750e17b8318b850306d84f29c4dc1b02723d7c",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AdminMenu.c": "d39dc876487b445efc5c3fadc19e1e06c6900c5cd57c93a01f35c32aa27d6039",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AdminService.c": "c3e008c8bbf538c5bca3a82a975decc7a283a38543a9d2639d90a81ce592c51e",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_AirfieldPlacement.c": "0c2f4d31a2abc8f3f90bda20546543f2a1cf9306e9bf93351dda1883b990770e",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_DiscoveryMap.c": "dd350bdda1bdf427ab8cefab623fe60cce5332b062d1a29686fe52f73e9e83f1",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_EscapeGameOverScreenInfo.c": "6e3c65e55b05cf205040c310b9086faa41b88582c7f9faf49c010afd59da5b07",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_GameMode.c": "ea6e80333cc8b6561fc4b76dd7811a7a539007cf60b4d85dfe4943ff7994f317",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_GeneratedSites.c": "4a23058f27e3ab074df6f8f7bb6f08a8fd26fbadb8943c6fb8d64dcc9c221751",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_JournalText.c": "87a6c19429f12c5630ed73170ca9a8a6d2255d6cd38b130166a78aacd15f2de0",
|
||||
"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_NativeBaseSmoke.c": "e811e985048e07365569acde6b5f045d57e9bbf13927f85b74e6e347fd2ef571",
|
||||
"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_NativeMedicalProbe.c": "4b73e7cf35e9c8fddb089678a320ea58272e1b6611318d0a762754c171eff785",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeOptionalSiteProbe.c": "19fc19e22348e6213827f620f4fdb5dc26144ea2ce0032de773e79c1fb155a26",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativeResultProbe.c": "c9a89cae067f221b9f33965b4d3ab311b78ba79ea383ca65032c4001df44b472",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ObjectiveText.c": "8d75b35ab977be0cb1a2e3d3d1cb8ef875cdbfba69c35784c3d195451ef944c6",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c": "4acbd2b23803f1fde3701fbddffd56edd83693196f0be2fe99f3be9b79a12b3d",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerMedical.c": "270454d50235bc28df9bba4fde2689934578874c4eca9560afb9f0f6324d7215",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c": "81bf30d358c7b100248be17cbc2e6beb2f107300c939aede8a92d273eb7dae1f",
|
||||
"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_UnconsciousCameraProbe.c": "7d5ff3389ee806ac213db5be3a68288b3399142ccc29806df0d65b34a0b2fc3c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_BodyRecovery.c": "47d9374095d04f5f1339c220caf518a40938ffc20f891b1b51ae38579e7e1c25",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyEquipment.c": "c52d4a3cc0e71eb136487c42df2fcd5bd6da1c33d31dbd3d6429c237dcf4a8d9",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyLoadout.c": "a48f32dbd7f209bf08fce2840aaffc1781c571fc679763be58987f6b082e776c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyMedicine.c": "05d341a9a000bfa2084057bd67d476d6e35adca6c8518d74c27cd31b551bbb95",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQAdminProvider.c": "c480e3cb22129d46babae961f0e8ac3ac562c3c360b33b3c58a7dfe97f2f07bf",
|
||||
"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_NativeAirLogisticsProbe.c": "58fe863775d0ad575cb566c78cef696c9826691cf49ef9adde76376be7316db1",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativeMortarSalvoProbe.c": "fb9ccc6b36247ab3ad828ffbb2556abf5c2838d93ea6cd1ad259c0a015142f5e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ReconAirports.c": "7551347d776d6e0eec818d6468cacbca7ac53daee7317e0a1618ca3d39ebc762",
|
||||
"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/20_Intel/ERR_MineIntel.c": "3ce16cebaa9703b2f9803df7c11614f94de6460a9136dab8f80dcad41ae2b1c2",
|
||||
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeHQMemoryProbe.c": "d8fec6c138bb0f5bdbf99ee82ffc3ac9b44d1045d8833da63f8aa5f39beeb7e5",
|
||||
"addons/core/UI/layouts/ERR/AdminMenu.layout": "997e4f33660de5bb54c2939898cacc8857736a73db2c6919888ae0e762050e5c",
|
||||
"addons/core/UI/layouts/ERRMap/SharedMapRenderTarget.layout": "6e83a9c70282aff43b23a08b732739e7a241a31e8abf200498c90a745d3d60dd",
|
||||
"docs/configuration.md": "c89d23793e2cf8d8a9611efcc7dd584a80b568b30d9919ae8f9c6a96a6077069",
|
||||
"docs/current-status.md": "6f137ff3264af12e32a568567f8d724326760aa8a8fbfe63c4240e75af042dd6",
|
||||
"docs/dedicated-native-diagnostic.md": "37d3ccf17776f5c2c2f2c8bdb87165451cc4c14eef4173162b89378f67693cd6",
|
||||
"docs/enemy-loadouts.md": "3d00ed31e6a169658a5d950c12b278cccdd7501c791b18d6ab8b2ffb90d06cef",
|
||||
"docs/environment.md": "2a58f8a969348e42e09e45b906a3d61fb65d887ae2814547605a01987747ab6c",
|
||||
"docs/field-sites.md": "9c3928a04b4b242dcf806d7b20ecf2f4cc636aff9a31ed90d9fd75cfaf776aa0",
|
||||
"docs/next-gameplay-increments.md": "a0cac3e0a1fa54aa87aad286316f42e2aaada158410cec2bbe1cd41a142ccc58",
|
||||
"docs/original-feature-coverage.md": "4923ddabf3b6f6d1f432712abfc04526434512091ac5ca79e9781a344d69f15d",
|
||||
"docs/playtest-build.md": "0443986b62fd090c3887648043f5198d639d1c95d9da5cb27c3c63db56df3721",
|
||||
"docs/prison-opening.md": "be60fee1542185bfae38cba9bacf1f181acf2a032af33d5eb853468a036cc54a",
|
||||
"docs/recovery.md": "026b513982530b8d65a6a1459e8707cd4abaf56450abc00f8449cf01fc660d0d",
|
||||
"docs/spectator-guidance.md": "bdf81a6c3495c64aac3a835caf412993f25b94d9487c84d43b0644f4a736d1ae",
|
||||
"docs/vanilla-resources.json": "d80edf67d429b578051cad1630d3c44fae70714d14a277e40294242f9b439783",
|
||||
"tests/test_admin_menu.py": "246ab269aabe8b743beefba8536d5b72a5c49db5ef6254fa7ee6684c6a82ad0a",
|
||||
"tests/test_admin_providers.py": "4869c86d2cb8c17a383680d594131f4053a051eb4af369657523e20f68311de6",
|
||||
"tests/test_command_maintenance.py": "be2c942aa11a5f874448e94f06925f2f1de2210a14881fb7e486842119cb673c",
|
||||
"tests/test_escape_result_wording.py": "7b3613cccd4f24d3ef4665dfc1854cfc958a69018a59f9b8090617f0185a82a8",
|
||||
"tests/test_field_sites.py": "fddb0e44e72fdfe32bb94347a14da64efbd2208e4f252754e80c620fbecda56c",
|
||||
"tests/test_headless_game_runner.py": "0d84160076ec9961e7315ccd50d6ce45ff971328c298649505fbba6305c22f31",
|
||||
"tests/test_journal_readability.py": "47764126522e7d7a90100cd9beff741bf49e4d8390111f977919d3e323a5c7df",
|
||||
"tests/test_mine_intel.py": "db64129001c0010ad11389ff764356893ac7c4fbf45e7c82aa56bbe4c75f4929",
|
||||
"tests/test_native_end_lifecycle.py": "8b187723c9039e68e3e590c812c91dcffe1e5917d4e450ed798dc177888fda7a",
|
||||
"tests/test_player_medical.py": "e4b3140a3afc0161db53f261e5480e51f1ca09f85657e25b0efb7dbba3336e3f",
|
||||
"tests/test_settings_migration.py": "5a82ba8672b72fb388a95e297d53452e45679ed3b94f5c7d4598455179038aef",
|
||||
"tests/test_spectator_recovery.py": "8bb59c51df003e24532ad9476af4c8ff3b7f4276dca25e8162b91eb77c7b67c2",
|
||||
"tools/check.py": "0e3f6f2ba5ffd3fdc35fcc3da24aaac9e5074213c39f643fa915c58b3c2fb813",
|
||||
"tools/headless_game.py": "e7cab472ee51497343174cb460022b3e4591f148d4acbc6e6f881fc68c0fda1b",
|
||||
"tools/headless_workbench.py": "02427c82e3d4f4ebbd46fe9a65e2076aa87c70a055ba7c535945abe10b29f514",
|
||||
"tools/import_settings.py": "8b41ea8fb45e5c3840dad67f9d2494cba5dfa1e0e67d41e334fdd04e515a68a1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
from pathlib import Path
|
||||
import hashlib,json
|
||||
R=Path('/home/jakub/devel/escapistsrereintroduced');E=R/'docs/evidence/recovery-launcher-2026-10-02';assert (E/'review.json').is_file()
|
||||
p=R/'docs/current-status.md';s=p.read_text();head,body=s.split('\n',1)
|
||||
new='''**2 October 2026 — recovery warnings and finite launcher resupply:** unconscious spectators now receive the already-replicated recovery, reconnection or thirty-second defeat warning. Hints refresh when that information changes and clear on awakening. HQ can also arrange supply for a reported empty, supported reloadable launcher using original compatible truck stock, after rifle and mounted demand. Disposable launchers remain excluded. [Implementation and review](evidence/recovery-launcher-2026-10-02/review.json).
|
||||
|
||||
All **283 registered regression programs pass**. The hidden/muted [observer fixture](evidence/native-2026-10-02-recovery-warning/report.json) passes **29 assertions**, including accepted recovery text and clearing on wake; the [final-source normal escape](evidence/native-2026-10-02-recovery-launcher-normal/report.json) passes **63 assertions and one round**. Both have zero script/runtime failures and **thirteen retained engine errors**, with confirmed cleanup. The observer run predates the four AI changes but uses exactly the final observer/probe code; the normal route binds all 407 final authored files.
|
||||
|
||||
Actual launcher transfer/reloading, full human play, multiplayer and comparative FPS remain unverified; no release qualification is claimed. The existing 416-file playtest package predates these changes and is unchanged. Original missions and inherited work are preserved; nothing was committed or published. A separate motorized-interception proposal remains design-only.
|
||||
|
||||
'''
|
||||
p.write_text(head+'\n\n'+new+body.lstrip('\n'))
|
||||
changes={
|
||||
'docs/spectator-guidance.md':'''\n## Recovery warnings while unconscious (2 October 2026)
|
||||
|
||||
The ordinary phase guide intentionally yields to spectator presentation. The observer now appends the existing replicated `RecoveryStatus`, so an unconscious player receives the same treatment/natural-recovery, possible returning rescuer, or uninterrupted thirty-second medical-defeat warning that conscious players can read in objective details. Medical policy and timing are unchanged.
|
||||
|
||||
The accepted status joins reason and survivor availability in hint deduplication. A missing hint manager or rejected delivery does not consume the message; an unchanged accepted message does not repeat. Waking, end-of-round cleanup and old-controller ownership checks preserve the existing camera behavior and clear only the owning controller's cache.
|
||||
|
||||
An actual-method regression fails on the earlier missing-warning behavior and passes with the fix, including status changes, delivery retries and round/controller replacement. The [native camera fixture](evidence/native-2026-10-02-recovery-warning/report.json) accepts the real ongoing-treatment status and clears it on awakening while retaining the earlier camera/recovery checks: 29 assertions, zero script failures and thirteen retained engine errors. This proves the accepted hint path, not full-resolution readability or remote multiplayer presentation. [Review and exact source scope](evidence/recovery-launcher-2026-10-02/review.json).
|
||||
''',
|
||||
'docs/infantry-resupply.md':'''\n## Supported carried launcher supply (2 October 2026)
|
||||
|
||||
HQ now considers supported empty launchers after existing rifle and mounted supply requests and before mine top-ups. Demand requires a fresh, complete delivered launcher snapshot for the entire known living formation, a positive count of reloadable magazine-well muzzles, and zero reported launcher rounds/chambers. The new `LauncherMuzzles` field survives detached status copying and validation. It does not assume an expended rocket/grenade leaves an empty magazine entity behind.
|
||||
|
||||
Before reserving the original infantry group and ammunition truck, the coordinator requires a compatible, unspent identity in its existing finite stock. The bounded scan covers all 96 stock entries and 32 original members; rifle-only prefixes cannot hide compatible cargo. Native muzzle disposal flags and scripted `CanBeReloaded` restrictions both refuse one-shot weapons. Exact supported wells, carried-weapon ownership and reciprocal loaded-item slots are checked. Installed RPG7/GP25/M203 round prefabs are projectile entities with magazine components; their entity type alone does not exclude them. This adds no stock, replacement actors or virtual ammunition.
|
||||
|
||||
The existing radio hold, reported rendezvous, physical access, shared spent ledger and native inventory proof govern transfer. Launcher trips do not return empty projectile rounds. Unknown delayed operations retain the original claims; a terminal negative callback releases them only after exact unchanged original source slot/storage, managers and exclusive custody are proved. Submitted stock remains spent even after refusal: no retry or refund. A failed move therefore cannot silently duplicate cargo or permanently pin forces when its unchanged source custody is confirmed.
|
||||
|
||||
Eleven focused programs pass independently, with failing mutation controls for copied muzzle presence, stock-prefix reachability and terminal refusal settlement. All 283 registered programs pass. The [final-source normal route](evidence/native-2026-10-02-recovery-launcher-normal/report.json) compiles the implementation and completes 63 assertions with zero script failures; thirteen retained engine errors preserve strict failure. Actual launcher walking delivery, subsequent AI reloading/firing, remote inventory behavior and performance remain unqualified. [Source, installed-API review and limits](evidence/recovery-launcher-2026-10-02/review.json). The existing packaged handoff predates this feature.
|
||||
''',
|
||||
'docs/next-gameplay-increments.md':'''\n## Latest source changes and next bounded AI feature (2 October 2026)
|
||||
|
||||
[Recovery warnings and supported launcher supply](evidence/recovery-launcher-2026-10-02/review.json) are now implemented and regression-tested; the latest packaged handoff above predates both. Physical launcher delivery/reloading still needs native qualification.
|
||||
|
||||
The next identified owned-AI gap is [motorized road interception from delivered travel history](evidence/recovery-launcher-2026-10-02/next-motorized-interception-proposal.json). Current remembered headings can place infantry ahead of travel, but convoy orders only hold/resume authored routes. A first implementation should assign at most one available original armed escort, deliver its order by radio, hold a suitable road and resume the preserved route. It must integrate with the existing convoy waypoint owner and exclude combat/logistics/depot reservations in both directions. This is a design proposal, not implemented behavior or a native driving result. Existing authorization covers later hidden/muted checks; no new permission gate is intended by the archived draft's wording.
|
||||
'''
|
||||
}
|
||||
for name,text in changes.items():
|
||||
p=R/name;s=p.read_text();assert text not in s;p.write_text(s+text)
|
||||
files=['docs/current-status.md',*changes]
|
||||
(E/'documentation-after.json').write_text(json.dumps({n:hashlib.sha256((R/n).read_bytes()).hexdigest() for n in files},indent=2)+'\n')
|
||||
print('Updated four current gameplay/status guides.')
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"docs/current-status.md": "d2bd9d4d9a1a15b91d680ef3dcf2601f3419993b8814f4bc6f0eea5dd2e49367",
|
||||
"docs/spectator-guidance.md": "ad805edd230933a80950f8702b1a65a177a4f80d525f8c38d96bb74a89f38dad",
|
||||
"docs/infantry-resupply.md": "6e8de63da90a84095d74e0fca1145f5d3301313a771ca839f2b85958b279628b",
|
||||
"docs/next-gameplay-increments.md": "cea3e979f4f738425cdc1c9b14314e11a272c7d87ef21b280f80ce16f56da344"
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
from pathlib import Path
|
||||
import sys,json,hashlib,shutil,subprocess
|
||||
R=Path('/home/jakub/devel/escapistsrereintroduced');B=Path('/tmp/erri-gameplay-1130');F=R/'.build/recovery-launcher-native-1130';E=R/'docs/evidence/recovery-launcher-2026-10-02';sys.path.insert(0,str(R/'tools'))
|
||||
from source_manifest import addon_manifest
|
||||
sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
def write(p,v):p.write_text(json.dumps(v,indent=2)+'\n')
|
||||
before=json.loads((B/'baseline.json').read_text());current=addon_manifest(R)
|
||||
assert current==json.loads((F/'frozen-source.json').read_text())
|
||||
suite=json.loads((B/'suite-result.json').read_text());assert suite['exit']==0 and suite['inputs_unchanged'] and suite['regression_programs']==283
|
||||
bindings=json.loads((B/'suite-inputs.json').read_text());assert all(sha(R/n)==h for n,h in bindings.items())
|
||||
assert hashlib.sha256(subprocess.check_output(['git','diff','--cached','--binary'],cwd=R)).hexdigest()==json.loads((B/'index-before-sha256.json').read_text())['sha256']
|
||||
obs=Path('/tmp/erri-observer-recovery-warning');launcher=Path('/tmp/erri-launcher-resupply-1140');reviewer=Path('/tmp/erri-launcher-review-1130')
|
||||
om=json.loads((obs/'source-manifest.json').read_text());lm=json.loads((launcher/'source-manifest.json').read_text());allowed=set(om)|set(lm)
|
||||
assert all(sha(R/n)==v['proposal_sha256'] for n,v in om.items())
|
||||
assert all(sha(R/n)==h for n,h in lm.items())
|
||||
lr=json.loads((reviewer/'review.json').read_text());assert lr['decision'].startswith('FINAL APPROVAL') and all(sha(R/n)==h for n,h in lr['source_sha256'].items())
|
||||
assert set(current)==set(before['authored'])
|
||||
changed=[n for n,h in before['authored'].items() if current[n]!=h];assert len(changed)==6 and set(changed)<=allowed
|
||||
assert all((sha(R/n) if (R/n).is_file() else None)==h for n,h in before['unstaged'].items() if n not in allowed)
|
||||
original=json.loads(Path('/tmp/erri-heartbeat-20260930-1542-reference.json').read_text());assert all(sha(Path('/home/jakub/devel/escapists')/n)==h for n,h in original.items())
|
||||
package=R/'.build/playtest-2026-10-02-objective-map';pm=json.loads((R/'docs/evidence/playtest-refresh-2026-10-02-objective-map/candidate-sha256.json').read_text());assert {str(p.relative_to(package)):sha(p) for p in package.rglob('*') if p.is_file()}==pm
|
||||
name='native-2026-10-02-recovery-launcher-normal';source=F/'docs/evidence'/name
|
||||
report=json.loads((source/'report.json').read_text());runner=json.loads((source/'runner.json.txt').read_text())
|
||||
assert report['smoke_completions']==1 and report['assertions_passed']==63 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']
|
||||
E.mkdir();shutil.copytree(source,R/'docs/evidence'/name)
|
||||
for p in B.iterdir():
|
||||
if p.is_file() and p.suffix in {'.json','.log','.py'}:shutil.copyfile(p,E/p.name)
|
||||
for folder,label in [(obs,'observer'),(launcher,'launcher'),(reviewer,'independent-launcher')]:
|
||||
out=E/label;out.mkdir()
|
||||
for p in folder.iterdir():
|
||||
if p.is_file() and p.suffix in {'.json','.log','.patch','.txt','.c','.py'}:shutil.copyfile(p,out/p.name)
|
||||
shutil.copyfile('/tmp/erri-hq-road-proposal-1130/proposal.json',E/'next-motorized-interception-proposal.json')
|
||||
write(E/'source-after.json',current)
|
||||
summary={'implemented':['Observer hints append current replicated recovery/defeat status, retry unaccepted delivery and clear on wake/end/ownership loss.','Delivered supported empty-launcher demand can reserve original infantry/truck and transfer compatible finite stock after existing rifle/mounted work. No new stock or disposable reload.'], 'authored_files':len(current),'changed_authored_files':changed,'registered_regression_programs_passed':283,'observer_native':{'assertions':29,'completions':1,'script_failures':0,'engine_errors':13,'source_scope':'Observer/probe exactly current; four AI files subsequently changed for launcher support.'},'normal_native':{'assertions':63,'completions':1,'script_failures':0,'engine_errors':13,'source_scope':'Exact final 407 authored files.'},'native_qualification':'failed; retained engine errors','launcher_physical_transfer_proven':False,'multiplayer_proven':False,'fps_improvement_proven':False,'package_refreshed':False,'existing_candidate_files_unchanged':len(pm),'original_reference_files_unchanged':len(original),'inherited_unstaged_paths':len(before['unstaged']),'inherited_overlap_paths':sorted(set(before['unstaged'])&allowed),'other_inherited_edits_unchanged':True,'cached_diff_unchanged':True,'committed':False,'published':False,'next_proposal':'Motorized interception from delivered travel history is design only; current authorization covers future hidden/muted validation, without a new user permission gate.'}
|
||||
write(E/'review.json',summary)
|
||||
print(json.dumps(summary,indent=2))
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"verdict": "PASS bounded independent integration review; concurrent parent documentation changes excluded",
|
||||
"checks": {
|
||||
"407_authored_final_manifest_match": true,
|
||||
"283_programs_all_exit_zero": true,
|
||||
"suite_input_bindings_unchanged": true,
|
||||
"observer_proposal_exact": true,
|
||||
"launcher_proposal_exact": true,
|
||||
"observer_and_probe_match_29_assertion_snapshot": true,
|
||||
"original533_preserved": true,
|
||||
"candidate416_preserved": true,
|
||||
"cached_diff_preserved": true,
|
||||
"normal63_one_completion_no_script_failures": true,
|
||||
"13_engine_errors_retained": true,
|
||||
"cleanup_and_inputs_confirmed": true,
|
||||
"console_matches_recorded_sha": true,
|
||||
"nonowned_inherited_source_unstaged_preserved": true
|
||||
},
|
||||
"suite_input_count": 741,
|
||||
"changed_authored": [
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c",
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_UnconsciousCameraProbe.c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQAmmoSampler.c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoTransfer.c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c"
|
||||
],
|
||||
"normal_evidence": "/home/jakub/devel/escapistsrereintroduced/.build/recovery-launcher-native-1130/docs/evidence/native-2026-10-02-recovery-launcher-normal",
|
||||
"retained_engine_errors": [
|
||||
"12:06:19.632 RESOURCES (E): Wrong GUID/name for resource @\"{0AFDD005CD669892}Language/rhs_localization.st\" in property \"StringTableSource\"",
|
||||
"12:06:19.632 RESOURCES (E): Wrong GUID/name for resource @\"{41FA2A08B6971D4C}Language/wcs_armament_localization.st\" in property \"StringTableSource\"",
|
||||
"12:06:19.633 RESOURCES (E): Wrong GUID/name for resource @\"{84270E09F7A4A7A0}Language/wcs_spacecore_localization.st\" in property \"StringTableSource\"",
|
||||
"12:06:19.633 RESOURCES (E): Wrong GUID/name for resource @\"{E50E557B468C7073}Language/wcs_bmp1_localization.st\" in property \"StringTableSource\"",
|
||||
"12:06:57.817 WORLD (E): Unknown keyword/data 'Parent' at offset 35955(0x8c73)",
|
||||
"12:06:57.817 WORLD (E): Unknown keyword/data 'Parent' at offset 36915(0x9033)",
|
||||
"12:06:57.818 WORLD (E): Unknown keyword/data 'Parent' at offset 42646(0xa696)",
|
||||
"12:06:57.818 WORLD (E): Unknown keyword/data 'Parent' at offset 43645(0xaa7d)",
|
||||
"12:06:57.818 WORLD (E): Unknown keyword/data 'Parent' at offset 44617(0xae49)",
|
||||
"12:06:57.818 WORLD (E): Unknown keyword/data 'Parent' at offset 45590(0xb216)",
|
||||
"12:06:57.917 WORLD (E): Unknown keyword/data 'SlidingTrackMaterial' at offset 19441(0x4bf1)",
|
||||
"12:07:04.286 WORLD (E): Unknown keyword/data 'm_HardLandingVelocity' at offset 133(0x85)",
|
||||
"12:07:04.286 WORLD (E): Unknown keyword/data 'm_DeathLandingVelocity' at offset 163(0xa3)"
|
||||
],
|
||||
"limitations": [
|
||||
"Native strict engine-error qualification remains failed despite scripted success.",
|
||||
"No real multiplayer, FPS improvement or physical launcher transfer qualification.",
|
||||
"Existing packaged candidate was preserved; these source changes have not refreshed that package.",
|
||||
"Observer native evidence uses earlier snapshot with identical Observer/probe; final normal route matches all 407 current authored files."
|
||||
],
|
||||
"concurrent_documentation_changes": [
|
||||
"docs/current-status.md",
|
||||
"docs/next-gameplay-increments.md",
|
||||
"docs/spectator-guidance.md"
|
||||
]
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"test_hq_ammo_sampler.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "e9c2d147eb1547be471df7459f1be815cdc9ef0a64c9a4ae4ef58936424dbc55",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_hq_ammo_sampler.py.log"
|
||||
},
|
||||
"test_infantry_ammo_transfer.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "7929033fe661de87f9ca72bc33d64fe996ac8b8e3630a17b4210a6f498b2930f",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_infantry_ammo_transfer.py.log"
|
||||
},
|
||||
"test_infantry_resupply_planning.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "9929ee0f5a3272c348ee329355b6ba67fdcb745fc9ce1f377f6c989eae91bf9f",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_infantry_resupply_planning.py.log"
|
||||
},
|
||||
"test_infantry_resupply_coordinator.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "f9cf7d583a25a12cec9f5f80539b2590449560331f4ef34e33ea2db283c5d54a",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_infantry_resupply_coordinator.py.log"
|
||||
},
|
||||
"test_mounted_supply_integration.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "f52acf7a16c9d5273894eb5a4afe7657a7c97de2ed72ba06671d25e163eea5d7",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_mounted_supply_integration.py.log"
|
||||
},
|
||||
"test_hq_multiconvoy_supply.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "e58d1d6eba48b2c5c6073d54c76302b2a7250ad22176fa94c386e1bc2ce65588",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_hq_multiconvoy_supply.py.log"
|
||||
},
|
||||
"test_hq_ammo_reporting.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "bdd4a30440501a2861aca80c951098c48cf999023eaaabd9a2eef82174f8173d",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_hq_ammo_reporting.py.log"
|
||||
},
|
||||
"test_hq_watch.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "51d5a728b0be83ff957c0df61289d14f90aba109b49365e457c9b200d5ede36a",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_hq_watch.py.log"
|
||||
},
|
||||
"test_infantry_mine_coordinator.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "2fdb2138685a329cef11557973450bd5189d3d7d657dac5cc51b3df2530fab6b",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_infantry_mine_coordinator.py.log"
|
||||
},
|
||||
"test_infantry_supply_pool.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "6fc0053337bbb889021c6eba608796ef946ff4d30a1b01cf678927293218d9d2",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_infantry_supply_pool.py.log"
|
||||
},
|
||||
"test_hq_status_sampler.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "b8582fb71b7ac77a23158ccd6230e8124ef0432831204e4b8992dee3a9c10062",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_hq_status_sampler.py.log"
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
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
|
||||
+1
@@ -0,0 +1 @@
|
||||
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
|
||||
+1
@@ -0,0 +1 @@
|
||||
PASS: actual HQ rotates finite donors, skips unavailable/contacted trucks, uses delivered reports and dispatches at most one trip per planning cadence
|
||||
+1
@@ -0,0 +1 @@
|
||||
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
|
||||
+1
@@ -0,0 +1 @@
|
||||
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
|
||||
+1
@@ -0,0 +1 @@
|
||||
PASS: actual bounded empty-return and finite delivery; phase-isolated callbacks, exact ownership proof, stop quarantine and original actor/vehicle boundaries
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
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
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
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
|
||||
+1
@@ -0,0 +1 @@
|
||||
PASS: actual convoy binding, finite identities, disjoint tokens, shared report replay/cooldown, original recipients and HQ cleanup
|
||||
+1
@@ -0,0 +1 @@
|
||||
PASS: actual finite convoy supply pool; immutable slots, shared demand state, sparse/capped discovery and permanent shutdown
|
||||
+1
@@ -0,0 +1 @@
|
||||
PASS: actual shared mounted stock/history ownership, atomic exact HQ pair claim, and bounded stopped-owner proof polling
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"decision": "Approved source proposal; no remaining concrete blocker found after two review corrections.",
|
||||
"producer": "/tmp/erri-launcher-resupply-1140",
|
||||
"source_sha256": {
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c": "52e601a0571b4942c9d4d0ce820727df8498febd6726efbd7da0c5eff7041b58",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c": "db2c0f1bfb0e0a8accabe45abd7eb93e6a13d6a8205ea427b12e28a7b4b28f40",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoTransfer.c": "e6f0896dd7bdfea89877cc7150928c94f36ac8aeb3af9a383bc665d5df801267"
|
||||
},
|
||||
"independent_tests": {
|
||||
"test_infantry_ammo_transfer.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "382b7a567d4a99fa6521160aac5d7eb332eeae20e14bf64eb603d59fdd0bb1a8",
|
||||
"log": "/tmp/erri-launcher-review-1130/test_infantry_ammo_transfer.py.log"
|
||||
},
|
||||
"test_infantry_resupply_planning.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "7bcb3461cc16b04a0247e928c1c831a3fb0944f48edcb9125cf7ef463a07168a",
|
||||
"log": "/tmp/erri-launcher-review-1130/test_infantry_resupply_planning.py.log"
|
||||
},
|
||||
"test_infantry_resupply_coordinator.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "f9cf7d583a25a12cec9f5f80539b2590449560331f4ef34e33ea2db283c5d54a",
|
||||
"log": "/tmp/erri-launcher-review-1130/test_infantry_resupply_coordinator.py.log"
|
||||
},
|
||||
"test_mounted_supply_integration.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "f52acf7a16c9d5273894eb5a4afe7657a7c97de2ed72ba06671d25e163eea5d7",
|
||||
"log": "/tmp/erri-launcher-review-1130/test_mounted_supply_integration.py.log"
|
||||
},
|
||||
"test_hq_multiconvoy_supply.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "e58d1d6eba48b2c5c6073d54c76302b2a7250ad22176fa94c386e1bc2ce65588",
|
||||
"log": "/tmp/erri-launcher-review-1130/test_hq_multiconvoy_supply.py.log"
|
||||
},
|
||||
"test_infantry_mine_coordinator.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "2fdb2138685a329cef11557973450bd5189d3d7d657dac5cc51b3df2530fab6b",
|
||||
"log": "/tmp/erri-launcher-review-1130/test_infantry_mine_coordinator.py.log"
|
||||
},
|
||||
"test_infantry_supply_pool.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "6fc0053337bbb889021c6eba608796ef946ff4d30a1b01cf678927293218d9d2",
|
||||
"log": "/tmp/erri-launcher-review-1130/test_infantry_supply_pool.py.log"
|
||||
}
|
||||
},
|
||||
"resolved_findings": [
|
||||
"LauncherStock now rejects rifle-only, spent, absent and incompatible original stock before claims, message consumption or cooldown. It checks only attributable own roster/weapon custody, capped at128 compatibility pairs.",
|
||||
"Loaded launcher magazine binding now requires exact current muzzle parent-container and attached entity identity, accepting a native loaded slot with no ordinary deposit storage. Incoming cargo storage validation remains unchanged."
|
||||
],
|
||||
"reviewed_constraints": [
|
||||
"Fresh delivered LauncherComplete with full reported member count, a supported magazine and zero rounds/chambers; no inference from unknown, missing-magazine or projectile-only launchers.",
|
||||
"HQ ordering remains rifle, mounted, launcher, mine; same stock/spent and report replay/cooldown state are reused. Launcher mode flows through trip, Compatible, transfer constructor and Context.",
|
||||
"Original stock reserved before native submission via shared spent array; any submission remains spent. Incoming IsUsed is refused. Exact physical proof and callback are both required.",
|
||||
"Uncertain submitted launcher operations retain donor/recipient claims in state7 through Stop; no native retry or stock refund. Terminal proof/deletion releases only owned claims."
|
||||
],
|
||||
"mutation": {
|
||||
"path": "/tmp/erri-launcher-review-1130/native-slot-mutation-red.log",
|
||||
"result": "Compiled then failed actual Compatible assertion when exact muzzle-slot proof was replaced by old StoredUnder loaded-magazine check.",
|
||||
"setup_failure_preserved": "/tmp/erri-launcher-review-1130/native-slot-mutation-compile-only.log",
|
||||
"setup_note": "First mutation left an unused local and failed compilation; declaration removed before the behavioral red run. Only final runtime assertion is counted as mutation evidence."
|
||||
},
|
||||
"limits": [
|
||||
"No native compile or inventory transfer was run by reviewer. Carried launcher catalogue coverage, animation/reload and actual engine movement remain unqualified.",
|
||||
"This slice delivers a compatible finite magazine to carried inventory; it does not guarantee an automatic reload or support projectile-only/disposable configurations.",
|
||||
"All seven focused tests execute modeled actual methods; they do not establish network/native callback behavior."
|
||||
],
|
||||
"writes": "Only review notes/logs and isolated mutation under /tmp; no producer, target or reference source changes."
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/tmp/erri-launcher-review-1130/scratch/err-policy-4s_8qyg4/check.cpp: In static member function ‘static bool ERR_InfantryAmmoTransfer::Compatible(IEntity, IEntity, bool)’:
|
||||
/tmp/erri-launcher-review-1130/scratch/err-policy-4s_8qyg4/check.cpp:184:27: error: unused variable ‘loadedSlot’ [-Werror=unused-variable]
|
||||
184 | InventoryStorageSlot loadedSlot = loadedItem->GetParentSlot();
|
||||
| ^~~~~~~~~~
|
||||
cc1plus: all warnings being treated as errors
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-launcher-resupply-1140/tests/test_infantry_ammo_transfer.py", line 323, in <module>
|
||||
run(pre+translated_reservation()+code+'\n'.join(callbacks)+main)
|
||||
~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
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/erri-launcher-review-1130/scratch/err-policy-4s_8qyg4/check.cpp', '-o', '/tmp/erri-launcher-review-1130/scratch/err-policy-4s_8qyg4/check']' returned non-zero exit status 1.
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
check: /tmp/erri-launcher-review-1130/scratch/err-policy-ses6lcfs/check.cpp:466: int main(): Assertion `ERR_InfantryAmmoTransfer::Compatible(&f.recipient,&f.magEntity,true)' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-launcher-resupply-1140/tests/test_infantry_ammo_transfer.py", line 323, in <module>
|
||||
run(pre+translated_reservation()+code+'\n'.join(callbacks)+main)
|
||||
~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
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/erri-launcher-review-1130/scratch/err-policy-ses6lcfs/check']' died with <Signals.SIGABRT: 6>.
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
enum ERR_InfantryAmmoTransferState { FAILED = -1, PENDING, SUCCEEDED }
|
||||
|
||||
class ERR_InfantryAmmoCallback : ScriptedInventoryOperationCallback
|
||||
{
|
||||
ERR_InfantryAmmoTransfer Transfer;
|
||||
bool Returning;
|
||||
protected override void OnComplete() { if (Transfer) Transfer.Acknowledge(true, Returning); }
|
||||
protected override void OnFailed() { if (Transfer) Transfer.Acknowledge(false, Returning); }
|
||||
protected override void OnAllComplete() { if (Transfer) Transfer.Acknowledge(true, Returning); }
|
||||
protected override void OnAllFailed() { if (Transfer) Transfer.Acknowledge(false, Returning); }
|
||||
}
|
||||
|
||||
// One exact physical magazine movement. The caller owns HQ authorization and
|
||||
// the spent-item reservation; this leaf never selects tasks or retries a move.
|
||||
class ERR_InfantryAmmoTransfer
|
||||
{
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected Vehicle m_Donor;
|
||||
protected SCR_AIGroup m_DonorGroup;
|
||||
protected IEntity m_Recipient;
|
||||
protected SCR_AIGroup m_RecipientGroup;
|
||||
protected IEntity m_Magazine;
|
||||
protected IEntity m_Driver;
|
||||
protected ERR_ForceRecord m_DonorRecord;
|
||||
protected ERR_ForceRecord m_RecipientRecord;
|
||||
protected InventoryStorageManagerComponent m_Source;
|
||||
protected InventoryStorageManagerComponent m_Destination;
|
||||
protected BaseInventoryStorageComponent m_Storage;
|
||||
protected ref ERR_InfantryAmmoCallback m_Callback;
|
||||
protected bool m_RecycleEmpty;
|
||||
protected bool m_LauncherMagazine;
|
||||
protected IEntity m_ReturnMagazine;
|
||||
protected BaseInventoryStorageComponent m_ReturnStorage;
|
||||
protected ref ERR_InfantryAmmoCallback m_ReturnCallback;
|
||||
protected bool m_ReturnSubmitted;
|
||||
protected bool m_ReturnAccepted;
|
||||
protected bool m_ReturnDone;
|
||||
protected int m_ReturnAcknowledgement;
|
||||
protected float m_PhaseStarted = -1;
|
||||
protected bool m_Submitted;
|
||||
protected bool m_Accepted;
|
||||
protected int m_Acknowledgement;
|
||||
protected float m_Started = -1;
|
||||
protected ERR_InfantryAmmoTransferState m_State = ERR_InfantryAmmoTransferState.PENDING;
|
||||
protected string m_Reason;
|
||||
void ERR_InfantryAmmoTransfer(ERR_GameMode mode, Vehicle donorVehicle, SCR_AIGroup donorGroup, IEntity recipientActor, SCR_AIGroup recipientGroup, IEntity exactMagazine, bool recycleEmpty = false, bool launcherMagazine = false)
|
||||
{
|
||||
m_RecycleEmpty = recycleEmpty; m_LauncherMagazine = launcherMagazine;
|
||||
m_Mode = mode; m_Donor = donorVehicle; m_DonorGroup = donorGroup;
|
||||
m_Recipient = recipientActor; m_RecipientGroup = recipientGroup; m_Magazine = exactMagazine;
|
||||
}
|
||||
static bool Compatible(IEntity actor, IEntity exactMagazine, bool launcherMagazine = false)
|
||||
{
|
||||
if (!actor || !exactMagazine) return false;
|
||||
BaseMagazineComponent magazine = BaseMagazineComponent.Cast(exactMagazine.FindComponent(BaseMagazineComponent));
|
||||
BaseWeaponManagerComponent manager = BaseWeaponManagerComponent.Cast(actor.FindComponent(BaseWeaponManagerComponent));
|
||||
if (!magazine || !manager || !magazine.GetMagazineWell() || magazine.GetAmmoCount() <= 0 || magazine.GetMaxAmmoCount() <= 0 || magazine.GetAmmoCount() > magazine.GetMaxAmmoCount()) return false;
|
||||
if (launcherMagazine && magazine.IsUsed()) return false;
|
||||
array<IEntity> weapons = {}; manager.GetWeaponsList(weapons);
|
||||
if (weapons.IsEmpty() || weapons.Count() > 8) return false;
|
||||
foreach (IEntity rifle : weapons)
|
||||
{
|
||||
if (!rifle) continue;
|
||||
BaseWeaponComponent weapon = BaseWeaponComponent.Cast(rifle.FindComponent(BaseWeaponComponent));
|
||||
if (!weapon || weapon.GetOwner() != rifle) continue;
|
||||
EWeaponType kind = weapon.GetWeaponType();
|
||||
bool smallWeapon = kind == EWeaponType.WT_RIFLE || kind == EWeaponType.WT_SNIPERRIFLE || kind == EWeaponType.WT_MACHINEGUN || kind == EWeaponType.WT_HANDGUN;
|
||||
if (!launcherMagazine && !smallWeapon) continue;
|
||||
if (launcherMagazine && !smallWeapon && kind != EWeaponType.WT_ROCKETLAUNCHER && kind != EWeaponType.WT_GRENADELAUNCHER) continue;
|
||||
if (launcherMagazine && !StoredUnder(rifle, actor)) continue;
|
||||
array<BaseMuzzleComponent> muzzles = {}; weapon.GetMuzzlesList(muzzles);
|
||||
if (muzzles.Count() > 4) continue;
|
||||
foreach (BaseMuzzleComponent muzzle : muzzles)
|
||||
{
|
||||
if (!muzzle || !muzzle.GetMagazineWell()) continue;
|
||||
EMuzzleType muzzleKind = muzzle.GetMuzzleType();
|
||||
if (!launcherMagazine && muzzleKind != EMuzzleType.MT_BaseMuzzle) continue;
|
||||
if (launcherMagazine)
|
||||
{
|
||||
if (muzzleKind != EMuzzleType.MT_RPGMuzzle && muzzleKind != EMuzzleType.MT_UGLMuzzle
|
||||
&& kind != EWeaponType.WT_ROCKETLAUNCHER && kind != EWeaponType.WT_GRENADELAUNCHER) continue;
|
||||
// Projectile-only/disposable and entirely missing magazine configurations
|
||||
// are outside this finite magazine slice; never synthesize a projectile.
|
||||
BaseMagazineComponent loaded = muzzle.GetMagazine();
|
||||
if (!loaded || !loaded.GetOwner() || !loaded.GetMagazineWell() || loaded.GetMaxAmmoCount() <= 0) continue;
|
||||
InventoryItemComponent loadedItem = InventoryItemComponent.Cast(loaded.GetOwner().FindComponent(InventoryItemComponent));
|
||||
if (!loadedItem || !loadedItem.GetParentSlot()) continue;
|
||||
if (!StoredUnder(loaded.GetOwner(), rifle)) continue;
|
||||
if (loaded.GetMagazineWell().Type() != muzzle.GetMagazineWell().Type()) continue;
|
||||
if (magazine.GetMagazineWell().Type() != muzzle.GetMagazineWell().Type()) continue;
|
||||
}
|
||||
// Same direction as the stock magazine inventory predicate: a magazine's
|
||||
// derived well must inherit the actual carried small-arms muzzle's well.
|
||||
if (magazine.GetMagazineWell().IsInherited(muzzle.GetMagazineWell().Type())) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static bool StoredUnder(IEntity entity, IEntity root)
|
||||
{
|
||||
for (int depth = 0; entity && depth < 16; depth++)
|
||||
{
|
||||
if (entity == root) return true;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent));
|
||||
if (item && item.GetParentSlot())
|
||||
{
|
||||
BaseInventoryStorageComponent storage = item.GetParentSlot().GetStorage();
|
||||
if (!storage) return false;
|
||||
entity = storage.GetOwner();
|
||||
}
|
||||
else entity = entity.GetParent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected bool ManagedStorage(InventoryStorageManagerComponent inventory, BaseInventoryStorageComponent storage, IEntity owner)
|
||||
{
|
||||
if (!inventory || !storage || inventory.GetOwner() != owner || !StoredUnder(storage.GetOwner(), owner)) return false;
|
||||
array<BaseInventoryStorageComponent> storages = {};
|
||||
inventory.GetStorages(storages, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
return storages.Count() <= 64 && storages.Contains(storage);
|
||||
}
|
||||
protected int Ownership(InventoryStorageManagerComponent inventory, IEntity exact = null)
|
||||
{
|
||||
if (!exact) exact = m_Magazine;
|
||||
if (!inventory || !exact) return -1;
|
||||
array<IEntity> items = {}; inventory.GetItems(items, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (items.Count() > 512) return -1;
|
||||
if (items.Contains(exact)) return 1;
|
||||
return 0;
|
||||
}
|
||||
protected bool Proof(IEntity exact, InventoryStorageManagerComponent source, InventoryStorageManagerComponent destination, BaseInventoryStorageComponent storage, IEntity owner, int acknowledgement)
|
||||
{
|
||||
if (!exact || acknowledgement != 1 || Ownership(source, exact) != 0 || Ownership(destination, exact) != 1 || !ManagedStorage(destination, storage, owner)) return false;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(exact.FindComponent(InventoryItemComponent));
|
||||
return item && item.GetParentSlot() && item.GetParentSlot().GetStorage() == storage;
|
||||
}
|
||||
protected bool PhysicalProof()
|
||||
{
|
||||
return Proof(m_Magazine, m_Source, m_Destination, m_Storage, m_Recipient, m_Acknowledgement);
|
||||
}
|
||||
protected bool ReturnProof()
|
||||
{
|
||||
return Proof(m_ReturnMagazine, m_Destination, m_Source, m_ReturnStorage, m_Donor, m_ReturnAcknowledgement);
|
||||
}
|
||||
void Acknowledge(bool success, bool returning = false)
|
||||
{
|
||||
if (returning)
|
||||
{
|
||||
if (m_ReturnAcknowledgement == 0)
|
||||
{
|
||||
if (success) m_ReturnAcknowledgement = 1;
|
||||
else m_ReturnAcknowledgement = -1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (m_Acknowledgement != 0) return;
|
||||
if (success) m_Acknowledgement = 1;
|
||||
else m_Acknowledgement = -1;
|
||||
}
|
||||
// Both native operations remain observable after Stop. Returning an empty
|
||||
// magazine never proves delivery or grants authority for a later full one.
|
||||
bool Unresolved()
|
||||
{
|
||||
if ((m_ReturnAccepted || (m_LauncherMagazine && m_ReturnSubmitted)) && !m_ReturnDone && m_ReturnMagazine && m_ReturnAcknowledgement >= 0 && !ReturnProof()) return true;
|
||||
if (m_LauncherMagazine && m_Submitted) return m_Magazine && !PhysicalProof();
|
||||
if (!m_Accepted || !m_Magazine || m_Acknowledgement < 0) return false;
|
||||
return !PhysicalProof();
|
||||
}
|
||||
// Any native submission consumes this incoming identity at the coordinator,
|
||||
// even when only the empty return was attempted and delivery never started.
|
||||
bool Submitted() { return m_Submitted || m_ReturnSubmitted; }
|
||||
string Reason() { return m_Reason; }
|
||||
protected ERR_InfantryAmmoTransferState Fail(string reason)
|
||||
{
|
||||
if (m_State == ERR_InfantryAmmoTransferState.PENDING) { m_State = ERR_InfantryAmmoTransferState.FAILED; m_Reason = reason; }
|
||||
return m_State;
|
||||
}
|
||||
void Stop() { Fail("HQ authorization stopped; an accepted native move cannot be cancelled"); }
|
||||
protected bool Context()
|
||||
{
|
||||
if (!Replication.IsServer() || SCR_Global.IsEditMode() || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active() || !m_Mode.Forces()) return false;
|
||||
if (!m_Donor || !ERR_Actors.Alive(m_Donor) || !m_DonorGroup || !m_RecipientGroup || m_DonorGroup == m_RecipientGroup) return false;
|
||||
if (!ERR_EnemyEquipmentRules.Eligible(m_Mode, m_RecipientGroup, m_Recipient) || CompartmentAccessComponent.GetVehicleIn(m_Recipient)) return false;
|
||||
if (m_DonorGroup.IsSlave() || m_RecipientGroup.IsSlave() || m_DonorGroup.GetPlayerCount(true) > 0 || m_DonorGroup.GetFaction() != m_Mode.EnemyFaction()) return false;
|
||||
if (SCR_FactionManager.SGetFaction(m_Donor) != m_Mode.EnemyFaction() || SCR_FactionManager.SGetFaction(m_Recipient) != m_Mode.EnemyFaction()) return false;
|
||||
ERR_ForceRecord donorRecord = m_Mode.Forces().FindGroup(m_DonorGroup);
|
||||
ERR_ForceRecord recipientRecord = m_Mode.Forces().FindGroup(m_RecipientGroup);
|
||||
if (!donorRecord || !recipientRecord || donorRecord.Group != m_DonorGroup || recipientRecord.Group != m_RecipientGroup || !donorRecord.Physical || !recipientRecord.Physical || donorRecord.Eliminated || recipientRecord.Eliminated) return false;
|
||||
if (m_DonorRecord && (m_DonorRecord != donorRecord || m_RecipientRecord != recipientRecord)) return false;
|
||||
SCR_AIGroupUtilityComponent utility = m_DonorGroup.GetGroupUtilityComponent();
|
||||
if (!utility || !utility.IsMilitary() || !utility.m_VehicleMgr || !utility.m_VehicleMgr.FindVehicle(m_Donor)) return false;
|
||||
if (ERR_ConvoyRules.Captured(m_Donor, m_DonorGroup)) return false;
|
||||
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(m_Donor.FindComponent(SCR_AIVehicleUsageComponent));
|
||||
if (!usage || !usage.GetPilotCompartmentSlot()) return false;
|
||||
IEntity driver = usage.GetPilotCompartmentSlot().GetOccupant();
|
||||
if (!ERR_EnemyEquipmentRules.Eligible(m_Mode, m_DonorGroup, driver) || !ERR_ConvoyRules.PartOf(CompartmentAccessComponent.GetVehicleIn(driver), m_Donor)) return false;
|
||||
if (SCR_FactionManager.SGetFaction(driver) != m_Mode.EnemyFaction() || (m_Driver && driver != m_Driver)) return false;
|
||||
Physics physics = m_Donor.GetPhysics();
|
||||
if (!physics) return false;
|
||||
float speed = physics.GetVelocity().Length();
|
||||
float angular = physics.GetAngularVelocity().Length();
|
||||
float distance = vector.Distance(m_Donor.GetOrigin(), m_Recipient.GetOrigin());
|
||||
if (!(speed >= 0 && speed <= 0.25 && angular >= 0 && angular <= 0.1 && distance >= 0 && distance <= 15)) return false;
|
||||
if (distance > 4 && !m_Donor.FindComponent(VehicleHelicopterSimulation)) return false;
|
||||
if (!Compatible(m_Recipient, m_Magazine, m_LauncherMagazine)) return false;
|
||||
m_Driver = driver; m_DonorRecord = donorRecord; m_RecipientRecord = recipientRecord;
|
||||
return true;
|
||||
}
|
||||
// One optional capacity recovery, using only an unused exact empty identity.
|
||||
protected bool ReturnEmpty()
|
||||
{
|
||||
if (!m_RecycleEmpty || m_ReturnSubmitted || m_ReturnMagazine) return false;
|
||||
BaseMagazineComponent incoming = BaseMagazineComponent.Cast(m_Magazine.FindComponent(BaseMagazineComponent));
|
||||
if (!incoming || !incoming.GetMagazineWell()) return false;
|
||||
array<IEntity> items = {}; m_Destination.GetItems(items, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (items.Count() > 128) return false;
|
||||
foreach (IEntity candidate : items)
|
||||
{
|
||||
if (!candidate || candidate == m_Magazine) continue;
|
||||
BaseMagazineComponent empty = BaseMagazineComponent.Cast(candidate.FindComponent(BaseMagazineComponent));
|
||||
if (!empty || empty.IsUsed() || empty.GetAmmoCount() != 0 || empty.GetMaxAmmoCount() <= 0
|
||||
|| !empty.GetMagazineWell() || empty.GetMagazineWell().Type() != incoming.GetMagazineWell().Type()) continue;
|
||||
if (Ownership(m_Destination, candidate) != 1 || Ownership(m_Source, candidate) != 0) continue;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(candidate.FindComponent(InventoryItemComponent));
|
||||
if (!item || !item.GetParentSlot() || !ManagedStorage(m_Destination, item.GetParentSlot().GetStorage(), m_Recipient)) continue;
|
||||
BaseInventoryStorageComponent storage = m_Source.FindStorageForItem(candidate, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (!ManagedStorage(m_Source, storage, m_Donor) || !m_Destination.CanMoveItemToStorage(candidate, storage, -1)) continue;
|
||||
m_ReturnMagazine = candidate; m_ReturnStorage = storage;
|
||||
m_ReturnCallback = new ERR_InfantryAmmoCallback(); m_ReturnCallback.Transfer = this; m_ReturnCallback.Returning = true;
|
||||
m_ReturnSubmitted = true;
|
||||
m_ReturnAccepted = m_Destination.TryMoveItemToStorage(candidate, storage, -1, m_ReturnCallback);
|
||||
if (m_ReturnAcknowledgement == 1) m_ReturnAccepted = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
ERR_InfantryAmmoTransferState Tick(float now)
|
||||
{
|
||||
if (m_State != ERR_InfantryAmmoTransferState.PENDING) return m_State;
|
||||
if (!ERR_ForceRules.Finite(now) || now < 0) return Fail("invalid transaction clock");
|
||||
if (m_Started < 0) { m_Started = now; m_PhaseStarted = now; }
|
||||
if (now < m_Started || now < m_PhaseStarted || now - m_Started >= 16000 || now - m_PhaseStarted >= 8000) return Fail("native magazine move deadline expired");
|
||||
if (!Context()) return Fail("original owned groups, vehicle, driver, recipient, proximity or compatible weapon unavailable");
|
||||
if (m_ReturnSubmitted && !m_ReturnDone)
|
||||
{
|
||||
if (!m_ReturnAccepted || m_ReturnAcknowledgement < 0) return Fail("native empty return rejected; neither identity will be retried");
|
||||
if (!ReturnProof()) return ERR_InfantryAmmoTransferState.PENDING;
|
||||
m_ReturnDone = true; m_PhaseStarted = now;
|
||||
// Separate slow tick allows loss of HQ authority to stop before delivery.
|
||||
return ERR_InfantryAmmoTransferState.PENDING;
|
||||
}
|
||||
if (!m_Submitted)
|
||||
{
|
||||
m_Source = InventoryStorageManagerComponent.Cast(m_Donor.FindComponent(InventoryStorageManagerComponent));
|
||||
m_Destination = InventoryStorageManagerComponent.Cast(m_Recipient.FindComponent(InventoryStorageManagerComponent));
|
||||
if (!m_Source || !m_Destination || m_Source == m_Destination || Ownership(m_Source) != 1 || Ownership(m_Destination) != 0) return Fail("exact magazine is not exclusive donor stock");
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(m_Magazine.FindComponent(InventoryItemComponent));
|
||||
if (!item || !item.GetParentSlot() || !ManagedStorage(m_Source, item.GetParentSlot().GetStorage(), m_Donor)) return Fail("exact source magazine storage does not belong to original donor");
|
||||
m_Storage = m_Destination.FindStorageForItem(m_Magazine, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (m_Storage && !ManagedStorage(m_Destination, m_Storage, m_Recipient)) return Fail("recipient storage ownership unavailable");
|
||||
bool capacity = m_Storage && m_Source.CanMoveItemToStorage(m_Magazine, m_Storage, -1);
|
||||
if (!ERR_InfantryAmmoAccess.Clear(m_Donor, m_Recipient)) return Fail("no unobstructed physical reach to an authored donor storage access point");
|
||||
if (!capacity)
|
||||
{
|
||||
if (!m_ReturnDone && ReturnEmpty()) return ERR_InfantryAmmoTransferState.PENDING;
|
||||
return Fail("recipient has no native compatible available storage after at most one empty return");
|
||||
}
|
||||
m_Callback = new ERR_InfantryAmmoCallback(); m_Callback.Transfer = this;
|
||||
m_Submitted = true;
|
||||
m_Accepted = m_Source.TryMoveItemToStorage(m_Magazine, m_Storage, -1, m_Callback);
|
||||
if (m_Acknowledgement == 1) m_Accepted = true;
|
||||
if (!m_Accepted) return Fail("native magazine move rejected; exact item will not be retried");
|
||||
}
|
||||
// Native callbacks may synchronously cancel this leaf or revoke ownership.
|
||||
if (m_LauncherMagazine && m_State != ERR_InfantryAmmoTransferState.PENDING) return m_State;
|
||||
if (m_LauncherMagazine && !Context()) return Fail("launcher custody authority changed during native submission");
|
||||
if (PhysicalProof())
|
||||
{
|
||||
m_State = ERR_InfantryAmmoTransferState.SUCCEEDED;
|
||||
m_Reason = "native callback and exact destination storage confirm one physical magazine transferred";
|
||||
return m_State;
|
||||
}
|
||||
if (m_Acknowledgement < 0) return Fail("native magazine move failed");
|
||||
return ERR_InfantryAmmoTransferState.PENDING;
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"decision": "FINAL APPROVAL: no remaining concrete source blocker found in frozen revised proposal. Supersedes intermediate loaded-empty-only review.",
|
||||
"producer": "/tmp/erri-launcher-resupply-1140",
|
||||
"patch_sha256": "cfc9de0cbda4f5fb151ac5ad3c7f2b04685feecb83805fc91a8ba32d6ab86d6e",
|
||||
"source_sha256": {
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c": "52e601a0571b4942c9d4d0ce820727df8498febd6726efbd7da0c5eff7041b58",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQAmmoSampler.c": "8bb0ec1422887feaa340307d3c60bae50fc82d424fce6390876e580ff8b75e8b",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c": "76f95e3526d971e123f546afbdcb5d225253954250d06bf48f997369d7c43fdc",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoTransfer.c": "72e7505fd5d303c77183a32da7fdfea68675860783219b5c7e7cc773ce0e8861"
|
||||
},
|
||||
"independent_tests": {
|
||||
"test_hq_ammo_sampler.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "e9c2d147eb1547be471df7459f1be815cdc9ef0a64c9a4ae4ef58936424dbc55",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_hq_ammo_sampler.py.log"
|
||||
},
|
||||
"test_infantry_ammo_transfer.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "8ec5687714c6f6a193e6ba3e764599a8dba1fc198d98b90ea2dc6bda1083b566",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_infantry_ammo_transfer.py.log"
|
||||
},
|
||||
"test_infantry_resupply_planning.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "9929ee0f5a3272c348ee329355b6ba67fdcb745fc9ce1f377f6c989eae91bf9f",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_infantry_resupply_planning.py.log"
|
||||
},
|
||||
"test_infantry_resupply_coordinator.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "f9cf7d583a25a12cec9f5f80539b2590449560331f4ef34e33ea2db283c5d54a",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_infantry_resupply_coordinator.py.log"
|
||||
},
|
||||
"test_mounted_supply_integration.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "f52acf7a16c9d5273894eb5a4afe7657a7c97de2ed72ba06671d25e163eea5d7",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_mounted_supply_integration.py.log"
|
||||
},
|
||||
"test_hq_multiconvoy_supply.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "e58d1d6eba48b2c5c6073d54c76302b2a7250ad22176fa94c386e1bc2ce65588",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_hq_multiconvoy_supply.py.log"
|
||||
},
|
||||
"test_hq_ammo_reporting.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "bdd4a30440501a2861aca80c951098c48cf999023eaaabd9a2eef82174f8173d",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_hq_ammo_reporting.py.log"
|
||||
},
|
||||
"test_hq_watch.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "51d5a728b0be83ff957c0df61289d14f90aba109b49365e457c9b200d5ede36a",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_hq_watch.py.log"
|
||||
},
|
||||
"test_infantry_mine_coordinator.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "2fdb2138685a329cef11557973450bd5189d3d7d657dac5cc51b3df2530fab6b",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_infantry_mine_coordinator.py.log"
|
||||
},
|
||||
"test_infantry_supply_pool.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "6fc0053337bbb889021c6eba608796ef946ff4d30a1b01cf678927293218d9d2",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_infantry_supply_pool.py.log"
|
||||
},
|
||||
"test_hq_status_sampler.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "b8582fb71b7ac77a23158ccd6230e8124ef0432831204e4b8992dee3a9c10062",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_hq_status_sampler.py.log"
|
||||
}
|
||||
},
|
||||
"reviewed_final_changes": [
|
||||
"LauncherMuzzles is detached in Snapshot and validated from0 through32 per sampled member. Inspect counts only non-disposable well-bearing supported launcher muzzles; SCR_MuzzleInMagComponent.CanBeReloaded refusal makes launcher state incomplete. Native APIs verified in installed generated BaseMuzzleComponent and SCR_MuzzleInMagComponent.",
|
||||
"Delivered demand requires fresh complete original living roster counts and at least one supported launcher muzzle with zero rounds and chambers. Empty GetMagazine is allowed, matching consumed one-round projectile-magazines; no absent-weapon inference.",
|
||||
"Optional existing loaded magazine retains exact current muzzle-container/attached-entity/well proof. Incoming positive unused finite compatible magazine and original carried weapon custody still required.",
|
||||
"LauncherStock scans the full bounded96x32 original stock/roster pair space, preventing permanent prefix starvation. No claims/replay/cooldown consumed for rifle-only or incompatible stock.",
|
||||
"Rifle/mounted requests keep priority; launcher precedes mines. Same spent ledger, original finite inventory, report replay and recipient cooldown remain.",
|
||||
"Mode reaches physical per-actor sampler, Compatible, transfer constructor and Context. Launcher empty-return is disabled; no speculative recycling of fired projectile identities.",
|
||||
"Native uncertainty retains exact donor/recipient claims, no retry/refund after submission; stopped polling continues only proof/cleanup."
|
||||
],
|
||||
"mutation": {
|
||||
"log": "/tmp/erri-launcher-review-1130/prefix-starvation-mutation-red.log",
|
||||
"result": "Restoring128 pair cap compiled then failed actual TryPlanLauncher late-compatible-stock assertion. Full96x32 frozen source passes."
|
||||
},
|
||||
"limitations": [
|
||||
"No native compile/run by reviewer. Actual RPG/VOG inventory transfer, reload, animations and multiplayer remain unqualified; tests use modeled native boundaries.",
|
||||
"Does not manufacture ammunition or guarantee automatic reload. Disposable/unsupported no-well configurations remain refused."
|
||||
],
|
||||
"source_changes_by_reviewer": "None; only isolated review artifacts/logs/mutation under /tmp."
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
check: /tmp/erri-launcher-review-1130/scratch/err-policy-74xaaa7p/check.cpp:378: int main(): Assertion `f.s.TryPlanLauncher(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0)' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-launcher-resupply-1140/tests/test_infantry_resupply_planning.py", line 34, in <module>
|
||||
run(r'''
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<202 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/erri-launcher-review-1130/scratch/err-policy-74xaaa7p/check']' died with <Signals.SIGABRT: 6>.
|
||||
+561
@@ -0,0 +1,561 @@
|
||||
// HQ-owned ground rendezvous using delivered positions and existing finite cargo.
|
||||
class ERR_InfantryResupplyRules
|
||||
{
|
||||
static bool Fresh(ERR_HQWatchEntry entry, float now)
|
||||
{
|
||||
return entry && !entry.Closed && entry.Count > 0 && entry.Fit > 0
|
||||
&& ERR_ForceRules.Finite(now) && entry.ObservedAt >= 0 && now >= entry.ObservedAt
|
||||
&& now - entry.ObservedAt <= 120000 && ERR_ForceRules.FiniteVector(entry.Position);
|
||||
}
|
||||
static bool Need(ERR_HQWatchEntry entry, float now)
|
||||
{
|
||||
return Fresh(entry, now) && entry.AmmoState && entry.AmmoState.Valid(entry.Count)
|
||||
&& entry.AmmoState.Complete && entry.AmmoState.Members == entry.Count
|
||||
&& entry.AmmoState.SmallArmsMuzzles > 0
|
||||
&& entry.AmmoState.SmallArmsRounds + entry.AmmoState.SmallArmsChambers <= entry.Count * 30;
|
||||
}
|
||||
// Only positively sampled reloadable magazine-well muzzles establish launcher demand.
|
||||
static bool LauncherNeed(ERR_HQWatchEntry entry, float now)
|
||||
{
|
||||
return Fresh(entry, now) && entry.AmmoState && entry.AmmoState.Valid(entry.Count)
|
||||
&& entry.AmmoState.LauncherComplete && entry.AmmoState.Members == entry.Count
|
||||
&& entry.AmmoState.LauncherMuzzles > 0
|
||||
&& entry.AmmoState.LauncherRounds == 0 && entry.AmmoState.LauncherChambers == 0;
|
||||
}
|
||||
static bool LauncherPair(ERR_HQWatchEntry recipient, ERR_HQWatchEntry donor, float now)
|
||||
{
|
||||
return LauncherNeed(recipient, now) && Fresh(donor, now) && recipient.SourceId != donor.SourceId
|
||||
&& vector.DistanceXZ(recipient.Position, donor.Position) <= 200;
|
||||
}
|
||||
static bool MinePair(ERR_HQWatchEntry recipient, ERR_HQWatchEntry donor, float now)
|
||||
{
|
||||
return Fresh(recipient, now) && Fresh(donor, now) && recipient.SourceId != donor.SourceId
|
||||
&& recipient.AmmoState && recipient.AmmoState.Valid(recipient.Count)
|
||||
&& recipient.AmmoState.MineComplete && recipient.AmmoState.MineMembers == recipient.Count
|
||||
&& recipient.AmmoState.ReadyMines <= 1 && vector.DistanceXZ(recipient.Position, donor.Position) <= 200;
|
||||
}
|
||||
static bool Pair(ERR_HQWatchEntry recipient, ERR_HQWatchEntry donor, float now)
|
||||
{
|
||||
return Need(recipient, now) && Fresh(donor, now) && recipient.SourceId != donor.SourceId
|
||||
&& vector.DistanceXZ(recipient.Position, donor.Position) <= 200;
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_InfantryResupply
|
||||
{
|
||||
protected ERR_HQ m_HQ;
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected ERR_ConvoyTask m_Convoy;
|
||||
protected int m_ConvoySlot = -1;
|
||||
protected ERR_ConvoyVehicle m_Truck;
|
||||
protected ERR_HQUnit m_Recipient;
|
||||
protected ERR_HQUnit m_Donor;
|
||||
protected ref array<IEntity> m_Stock = {};
|
||||
protected ref array<IEntity> m_Spent = {};
|
||||
protected ref array<IEntity> m_MineStock = {};
|
||||
protected ref ERR_ConvoyMineStock m_MineBatch;
|
||||
protected bool m_MineStockCaptured;
|
||||
protected bool m_MineTrip;
|
||||
protected bool m_LauncherTrip;
|
||||
protected int m_MineRecipientSlot = -1;
|
||||
protected bool m_EmptyMineReplacement;
|
||||
protected int m_MinesDelivered;
|
||||
protected ref array<IEntity> m_Members = {};
|
||||
protected ref array<IEntity> m_Served = {};
|
||||
protected ref map<string, int> m_Consumed = new map<string, int>();
|
||||
protected ref map<string, float> m_NextAttempt = new map<string, float>();
|
||||
protected ref ERR_HQRoute m_Order;
|
||||
protected ref ERR_InfantryAmmoTransfer m_Move;
|
||||
protected ref ERR_InfantryMineTransfer m_MineMove;
|
||||
protected ref ERR_MountedResupply m_Mounted;
|
||||
protected IEntity m_Receiving;
|
||||
protected AIWaypoint m_MoveWaypoint;
|
||||
protected AIWaypoint m_HoldWaypoint;
|
||||
protected vector m_Rendezvous;
|
||||
protected int m_State; // 0 stock startup, 1 idle, 2 truck downlink, 3 stopped status, 4 infantry downlink, 5 transfer, 7 unresolved mine custody
|
||||
protected int m_Trip;
|
||||
protected int m_Token;
|
||||
protected int m_ReportId;
|
||||
protected int m_Delivered;
|
||||
protected int m_TripDelivered;
|
||||
protected float m_Deadline;
|
||||
protected float m_StockDeadline = -1;
|
||||
protected float m_HeldAt;
|
||||
protected float m_NextTick;
|
||||
protected bool m_Stopped;
|
||||
protected string m_Reason;
|
||||
void ERR_InfantryResupply(ERR_HQ hq, ERR_GameMode mode) { m_HQ = hq; m_Mode = mode; }
|
||||
// Bind before startup; every coordinator retains its exact finite convoy identity.
|
||||
bool Bind(ERR_ConvoyTask task, int slot, map<string, int> consumed, map<string, float> nextAttempt)
|
||||
{
|
||||
if (!task || !consumed || !nextAttempt || !m_HQ || !m_HQ.Convoys() || slot < 0 || slot >= 20
|
||||
|| slot >= m_HQ.Convoys().Count() || m_HQ.Convoys().At(slot) != task) return false;
|
||||
if (m_Stopped || m_Convoy || m_ConvoySlot >= 0 || m_State != 0 || m_Trip != 0 || m_NextTick != 0
|
||||
|| m_StockDeadline >= 0 || !m_Stock.IsEmpty() || m_Move || m_Truck) return false;
|
||||
m_Convoy = task; m_ConvoySlot = slot; m_Consumed = consumed; m_NextAttempt = nextAttempt;
|
||||
return true;
|
||||
}
|
||||
ERR_ConvoyTask Task() { return m_Convoy; }
|
||||
int State()
|
||||
{
|
||||
if (m_Mounted && m_Mounted.Busy()) return 6;
|
||||
return m_State;
|
||||
}
|
||||
ERR_MountedResupply Mounted() { return m_Mounted; }
|
||||
bool Unresolved() { return (m_Move && m_Move.Unresolved()) || (m_MineMove && m_MineMove.Unresolved()) || (m_Mounted && m_Mounted.Unresolved()); }
|
||||
protected void PollTransfers(float now)
|
||||
{
|
||||
if (m_Mounted) m_Mounted.Tick(now);
|
||||
if (m_Move && !m_Move.Unresolved() && (m_State <= 1 || (m_State == 7 && m_LauncherTrip)))
|
||||
{
|
||||
m_Move = null;
|
||||
if (m_State == 7) Finish("launcher custody settled; original claims released without retry or refund");
|
||||
}
|
||||
if (m_MineMove && !m_MineMove.Unresolved() && (m_State <= 1 || m_State == 7))
|
||||
{
|
||||
m_MineMove = null;
|
||||
if (m_State == 7) Finish("mine custody settled; original claims released without retry or refund");
|
||||
}
|
||||
}
|
||||
int Delivered() { return m_Delivered; }
|
||||
int MinesDelivered() { return m_MinesDelivered; }
|
||||
int StockCount() { return m_Stock.Count(); }
|
||||
// Admission uses the existing array object so an already-created mounted
|
||||
// coordinator observes this exact finite addition without a replacement ledger.
|
||||
protected int m_DepotImports;
|
||||
bool DepotCurrent()
|
||||
{
|
||||
return Current() && m_State == 1 && !m_Move && !m_MineMove && !Unresolved()
|
||||
&& (!m_Mounted || !m_Mounted.Busy()) && TruckValid(true);
|
||||
}
|
||||
bool DepotAvailable() { return DepotCurrent() && m_Stock.Count() < 96 && m_Spent.Count() < 96; }
|
||||
bool DepotNeeded()
|
||||
{
|
||||
if (!DepotAvailable()) return false;
|
||||
int committed;
|
||||
foreach (IEntity item : m_Spent)
|
||||
if (item && m_Stock.Contains(item)) committed++;
|
||||
return committed > m_DepotImports;
|
||||
}
|
||||
ERR_ConvoyVehicle DepotTruck()
|
||||
{
|
||||
if (!DepotCurrent()) return null;
|
||||
return m_Truck;
|
||||
}
|
||||
bool AdmitDepot(ERR_GroundDepotLoading job, IEntity item)
|
||||
{
|
||||
if (!job || !DepotAvailable() || !item || m_Stock.Contains(item) || m_Spent.Contains(item)) return false;
|
||||
if (!job.DeliveredTo(this, item) || !Contains(m_Truck.Entity, item)) return false;
|
||||
m_Stock.Insert(item); m_DepotImports++; Audit("one exact finite camp magazine admitted after proved physical courier delivery");
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void Audit(string reason)
|
||||
{
|
||||
if (m_MineTrip) reason = "mine top-up: " + reason;
|
||||
if (m_LauncherTrip) reason = "depleted launcher magazine: " + reason;
|
||||
m_Reason = reason;
|
||||
m_HQ.Journal().Write("INFANTRY SUPPLY", string.Format("convoy-slot=%1 trip=%2 state=%3 moved=%4 spent=%5/%6; %7", m_ConvoySlot, m_Trip, m_State, m_Delivered, m_Spent.Count(), m_Stock.Count(), reason), m_ReportId);
|
||||
}
|
||||
protected bool Current()
|
||||
{
|
||||
return !m_Stopped && Replication.IsServer() && m_Mode && m_Mode == ERR_GameMode.Get() && m_Mode.Active();
|
||||
}
|
||||
protected bool Contains(IEntity owner, IEntity item)
|
||||
{
|
||||
if (!owner || !item) return false;
|
||||
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(owner.FindComponent(InventoryStorageManagerComponent));
|
||||
if (!inventory) return false;
|
||||
array<IEntity> items = {}; ERR_Inventory.Items(inventory, items);
|
||||
return items.Count() <= 128 && items.Contains(item);
|
||||
}
|
||||
protected bool ConvoyCurrent()
|
||||
{
|
||||
if (!m_HQ || !m_Convoy) return false;
|
||||
if (m_ConvoySlot < 0) return m_HQ.ConvoyTask() == m_Convoy;
|
||||
return m_HQ.Convoys() && m_ConvoySlot < m_HQ.Convoys().Count() && m_HQ.Convoys().At(m_ConvoySlot) == m_Convoy;
|
||||
}
|
||||
protected bool TruckValid(bool allowArrived = false)
|
||||
{
|
||||
if (!m_Convoy || m_Convoy.Stopped || !m_Convoy.Ready || !m_Convoy.Receipt || !m_Convoy.Receipt.Consumed
|
||||
|| !ConvoyCurrent() || m_Convoy.Vehicles.Count() != 6 || m_Convoy.Vehicles[1] != m_Truck) return false;
|
||||
if (!m_Truck || m_Truck.Index != 1 || !m_Truck.Registered || m_Truck.Released || (m_Truck.Arrived && !allowArrived)
|
||||
|| !m_Truck.CrewValid(m_Mode) || ERR_ConvoyRules.Captured(m_Truck.Entity, m_Truck.Group)) return false;
|
||||
ERR_ForceRecord record = m_Mode.Forces().FindGroup(m_Truck.Group);
|
||||
return record && record.Physical && !record.Eliminated && record.Kind == ERR_ForceKind.CONVOY
|
||||
&& m_Convoy.Receipt.Records.Count() >= 6 && record.Id == m_Convoy.Receipt.Records[1];
|
||||
}
|
||||
protected bool Quiet(ERR_HQUnit unit)
|
||||
{
|
||||
return unit && unit.Group && unit.Utility && !unit.Group.IsSlave() && unit.Group.GetPlayerCount(true) == 0
|
||||
&& unit.Group.GetFaction() == m_Mode.EnemyFaction()
|
||||
&& (!unit.Utility.m_Perception || unit.Utility.m_Perception.m_aTargets.IsEmpty());
|
||||
}
|
||||
protected bool Roster()
|
||||
{
|
||||
if (!m_Recipient || !Quiet(m_Recipient) || !m_Recipient.StatusSampler.OwnsRoster(m_Recipient.Group)) return false;
|
||||
int strength = m_Recipient.StatusSampler.InitialStrength();
|
||||
if (strength < 1 || strength > 32) return false;
|
||||
array<AIAgent> agents = {}; m_Recipient.Group.GetAgents(agents);
|
||||
if (agents.IsEmpty() || agents.Count() > 32) return false;
|
||||
foreach (AIAgent agent : agents)
|
||||
if (!agent || !m_Members.Contains(agent.GetControlledEntity())) return false;
|
||||
bool living;
|
||||
foreach (IEntity actor : m_Members)
|
||||
{
|
||||
if (!actor || !ERR_Actors.Alive(actor)) continue;
|
||||
// Returning to the group cannot undo a sampler's permanent release of an
|
||||
// original slot after transfer or player control during this trip.
|
||||
bool attributed = false;
|
||||
for (int slot = 0; slot < strength; slot++)
|
||||
{
|
||||
if (m_Recipient.StatusSampler.InitialMember(slot) != actor) continue;
|
||||
attributed = m_Recipient.StatusSampler.CanAttribute(slot, actor);
|
||||
break;
|
||||
}
|
||||
if (!attributed) return false;
|
||||
if (ERR_Actors.Player(actor) || ERR_Actors.Group(actor) != m_Recipient.Group
|
||||
|| SCR_FactionManager.SGetFaction(actor) != m_Mode.EnemyFaction() || CompartmentAccessComponent.GetVehicleIn(actor)) return false;
|
||||
if (ERR_Actors.CanFight(actor)) living = true;
|
||||
}
|
||||
return living;
|
||||
}
|
||||
protected void Stock(float now)
|
||||
{
|
||||
if (!m_Convoy) m_Convoy = m_HQ.ConvoyTask();
|
||||
if (m_Convoy && !ConvoyCurrent()) { Stop(); return; }
|
||||
if (!m_Convoy || !m_Convoy.Ready || m_Convoy.Vehicles.Count() != 6) return;
|
||||
m_Truck = m_Convoy.Vehicles[1];
|
||||
if (!TruckValid()) { Stop(); return; }
|
||||
// Cargo has a later, separately bounded lifecycle than native crew admission.
|
||||
// Supply convoys first request faction weapons, then their matching magazines.
|
||||
if (m_StockDeadline < 0) m_StockDeadline = now + 90000;
|
||||
if (now >= m_StockDeadline) { Stop(); return; }
|
||||
if (!m_Convoy.CargoIssued) return;
|
||||
ERR_SupplyConvoyTask supply = ERR_SupplyConvoyTask.Cast(m_Convoy);
|
||||
if (supply && !supply.MagazinesIssued) return;
|
||||
if (supply) m_MineBatch = supply.MineStock;
|
||||
foreach (ERR_ConvoyCargoCallback operation : m_Convoy.Cargo)
|
||||
if (!operation.Finished) return;
|
||||
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(m_Truck.Entity.FindComponent(InventoryStorageManagerComponent));
|
||||
if (!inventory) { Stop(); return; }
|
||||
array<IEntity> items = {}; ERR_Inventory.Items(inventory, items);
|
||||
if (items.Count() > 128) { Stop(); return; }
|
||||
foreach (IEntity item : items)
|
||||
{
|
||||
if (!item || !item.FindComponent(BaseMagazineComponent)) continue;
|
||||
if (m_Stock.Count() >= 96) { Stop(); return; }
|
||||
m_Stock.Insert(item);
|
||||
}
|
||||
m_State = 1; Audit("truck magazine identities captured once after startup; absent/looted stock is never replaced");
|
||||
}
|
||||
bool TryPlan(ERR_HQUnit recipient, ERR_HQUnit donor, ERR_HQWatchEntry demand, ERR_HQWatchEntry donorStatus, float now, bool mineTrip = false, bool launcherTrip = false)
|
||||
{
|
||||
if (!Current() || m_State != 1 || m_Move || m_MineMove || (m_Mounted && m_Mounted.Busy()) || m_Trip >= 128 || !TruckValid()) return false;
|
||||
if ((mineTrip && launcherTrip) || m_Truck.Combat.Held || m_Truck.SupplyToken > 0 || !recipient || !donor) return false;
|
||||
if (donor.Group != m_Truck.Group || recipient.HomeGuard || !Quiet(recipient) || !Quiet(donor)) return false;
|
||||
if (donor.Downlink || donor.PendingOrder || donor.PendingConvoyOrder) return false;
|
||||
if (mineTrip)
|
||||
{
|
||||
if (!ERR_InfantryResupplyRules.MinePair(demand, donorStatus, now)) return false;
|
||||
// Empty reports authorize only an original startup-designated engineer.
|
||||
// This reads retained entitlement, never live positions/inventory for planning.
|
||||
if (demand.AmmoState.ReadyMines == 0)
|
||||
{
|
||||
bool designated;
|
||||
for (int member = 0; member < recipient.StatusSampler.InitialStrength() && member < 32; member++)
|
||||
{
|
||||
IEntity original = recipient.StatusSampler.InitialMember(member);
|
||||
if (original && recipient.StatusSampler.CanAttribute(member, original)
|
||||
&& m_HQ.MineSupplyReady(recipient.Group, original, true)) { designated = true; break; }
|
||||
}
|
||||
if (!designated) return false;
|
||||
}
|
||||
}
|
||||
else if (launcherTrip)
|
||||
{
|
||||
if (!ERR_InfantryResupplyRules.LauncherPair(demand, donorStatus, now)) return false;
|
||||
}
|
||||
else if (!ERR_InfantryResupplyRules.Pair(demand, donorStatus, now)) return false;
|
||||
if (demand.SourceId != recipient.ForceSourceId || donorStatus.SourceId != donor.ForceSourceId) return false;
|
||||
if (demand.MessageId <= m_Consumed.Get(demand.SourceId) || now < m_NextAttempt.Get(demand.SourceId)) return false;
|
||||
bool available;
|
||||
if (mineTrip)
|
||||
{
|
||||
if (!CaptureMines()) return false;
|
||||
foreach (IEntity mine : m_MineStock)
|
||||
if (ERR_InfantryMineTransfer.ReadyMine(mine) && !m_Spent.Contains(mine) && Contains(m_Truck.Entity, mine)) { available = true; break; }
|
||||
}
|
||||
else if (launcherTrip) available = LauncherStock(recipient);
|
||||
else foreach (IEntity magazine : m_Stock) if (magazine && !m_Spent.Contains(magazine) && Contains(m_Truck.Entity, magazine)) { available = true; break; }
|
||||
if (m_Spent.Count() >= 96) return false;
|
||||
if (!available) return false;
|
||||
m_Members.Clear();
|
||||
for (int i = 0; i < recipient.StatusSampler.InitialStrength(); i++)
|
||||
{
|
||||
IEntity actor = recipient.StatusSampler.InitialMember(i);
|
||||
if (actor && recipient.StatusSampler.CanAttribute(i, actor)) m_Members.Insert(actor);
|
||||
}
|
||||
m_Recipient = recipient; m_Donor = donor;
|
||||
if (!Roster()) { m_Recipient = null; m_Donor = null; return false; }
|
||||
// Disjoint bounded token range from the existing mortar courier's trip IDs.
|
||||
int slot = m_ConvoySlot;
|
||||
if (slot < 0) slot = 0; // Isolated legacy fixture uses the first convoy's range.
|
||||
int token = 100000 + slot * 128 + m_Trip + 1;
|
||||
if (!m_HQ.ReserveLogistics(recipient.Group, token)) { m_Recipient = null; m_Donor = null; return false; }
|
||||
m_MineTrip = mineTrip; m_LauncherTrip = launcherTrip; m_MineRecipientSlot = -1; m_Receiving = null; m_EmptyMineReplacement = false;
|
||||
m_Trip++; m_Token = token; m_Truck.SupplyToken = token; m_ReportId = demand.MessageId; m_TripDelivered = 0; m_Served.Clear();
|
||||
m_Consumed.Set(demand.SourceId, demand.MessageId); m_NextAttempt.Set(demand.SourceId, now + 180000);
|
||||
m_Deadline = Math.Min(now + 120000, demand.ObservedAt + 180000); m_State = 2;
|
||||
string purpose = "stop existing ammo truck and report position";
|
||||
if (mineTrip) purpose += "; one finite PMN4 top-up for an existing carrier";
|
||||
if (launcherTrip) purpose += "; finite compatible magazine for a reported depleted launcher";
|
||||
m_Order = m_HQ.Downlink(donor.Group, "INFANTRY SUPPLY HOLD", purpose, m_ReportId);
|
||||
if (!m_Order) Finish("truck radio downlink unavailable");
|
||||
else Audit("existing patrol reserved; truck hold awaits radio delivery");
|
||||
return true;
|
||||
}
|
||||
// Optional mine startup must never delay normal magazine readiness. Pin the
|
||||
// original batch during Stock(), then copy its closed exact identities once.
|
||||
protected bool CaptureMines()
|
||||
{
|
||||
ERR_SupplyConvoyTask supply = ERR_SupplyConvoyTask.Cast(m_Convoy);
|
||||
if (!supply || !m_MineBatch || supply.MineStock != m_MineBatch || !m_MineBatch.Complete()) return false;
|
||||
if (m_MineStockCaptured) return !m_MineStock.IsEmpty();
|
||||
m_MineStockCaptured = true;
|
||||
int count = m_MineBatch.Count();
|
||||
if (count < 0 || count > 2) return false;
|
||||
for (int index = 0; index < count; index++)
|
||||
{
|
||||
IEntity item = m_MineBatch.At(index);
|
||||
if (item && !m_MineStock.Contains(item)) m_MineStock.Insert(item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Compatibility-only preflight of original own carriers; HQ still uses the
|
||||
// delivered report for demand and positions. No item is reserved or moved here.
|
||||
protected bool LauncherStock(ERR_HQUnit recipient)
|
||||
{
|
||||
if (!recipient || !recipient.StatusSampler || m_Stock.Count() > 96) return false;
|
||||
int strength = recipient.StatusSampler.InitialStrength();
|
||||
if (strength < 1 || strength > 32) return false;
|
||||
int checks;
|
||||
foreach (IEntity magazine : m_Stock)
|
||||
{
|
||||
if (!magazine || m_Spent.Contains(magazine) || !Contains(m_Truck.Entity, magazine)) continue;
|
||||
for (int slot = 0; slot < strength; slot++)
|
||||
{
|
||||
if (++checks > 128) return false;
|
||||
IEntity actor = recipient.StatusSampler.InitialMember(slot);
|
||||
if (!actor || !recipient.StatusSampler.CanAttribute(slot, actor) || !ERR_Actors.CanFight(actor)) continue;
|
||||
if (ERR_InfantryAmmoTransfer.Compatible(actor, magazine, true)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool TryPlanLauncher(ERR_HQUnit recipient, ERR_HQUnit donor, ERR_HQWatchEntry demand, ERR_HQWatchEntry donorStatus, float now)
|
||||
{
|
||||
return TryPlan(recipient, donor, demand, donorStatus, now, false, true);
|
||||
}
|
||||
bool TryPlanMines(ERR_HQUnit recipient, ERR_HQUnit donor, ERR_HQWatchEntry demand, ERR_HQWatchEntry donorStatus, float now)
|
||||
{
|
||||
return TryPlan(recipient, donor, demand, donorStatus, now, true);
|
||||
}
|
||||
// Share exact frozen inventory identities and global radio replay state.
|
||||
bool TryPlanMounted(ERR_HQUnit recipient, ERR_HQUnit donor, ERR_HQWatchEntry demand, ERR_HQWatchEntry donorStatus, float now)
|
||||
{
|
||||
if (!Current() || m_State != 1 || m_Move || m_MineMove || m_ConvoySlot < 0 || !TruckValid(true)) return false;
|
||||
if (m_Mounted && m_Mounted.Busy()) return false;
|
||||
if (!m_Mounted) m_Mounted = new ERR_MountedResupply(m_HQ, m_Mode, m_Convoy, m_ConvoySlot, m_Stock, m_Spent, m_Consumed, m_NextAttempt);
|
||||
return m_Mounted.Begin(recipient, donor, demand, donorStatus, now);
|
||||
}
|
||||
protected bool Waypoints()
|
||||
{
|
||||
m_MoveWaypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), ERR_Actors.Ground(m_Rendezvous)));
|
||||
m_HoldWaypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.DefendWaypoint(), ERR_Actors.Ground(m_Rendezvous)));
|
||||
if (!m_MoveWaypoint || !m_HoldWaypoint) return false;
|
||||
// The reported rendezvous is 3 m from the donor report. A 2 m move
|
||||
// radius allowed arrival 5 m away, beyond the transfer's physical reach.
|
||||
m_MoveWaypoint.SetCompletionRadius(0.5); m_HoldWaypoint.SetCompletionRadius(0.5);
|
||||
m_Recipient.Group.AddWaypointAt(m_HoldWaypoint, 0); m_Recipient.Group.AddWaypointAt(m_MoveWaypoint, 0);
|
||||
m_Recipient.Group.ActivateAI(); return true;
|
||||
}
|
||||
protected void ClearWaypoints()
|
||||
{
|
||||
if (m_MoveWaypoint) { if (m_Recipient && m_Recipient.Group) m_Recipient.Group.RemoveWaypoint(m_MoveWaypoint); ERR_Actors.DeleteSpawned(m_MoveWaypoint); }
|
||||
if (m_HoldWaypoint) { if (m_Recipient && m_Recipient.Group) m_Recipient.Group.RemoveWaypoint(m_HoldWaypoint); ERR_Actors.DeleteSpawned(m_HoldWaypoint); }
|
||||
m_MoveWaypoint = null; m_HoldWaypoint = null;
|
||||
}
|
||||
protected void Finish(string reason)
|
||||
{
|
||||
Audit(reason);
|
||||
if (m_Move) m_Move.Stop();
|
||||
if (m_MineMove) m_MineMove.Stop();
|
||||
ClearWaypoints();
|
||||
m_Order = null;
|
||||
// A late native move must not race another truck's newer report. Retain both
|
||||
// exact claims until this one submitted identity proves custody or deletion.
|
||||
if ((m_MineMove && m_MineMove.Unresolved()) || (m_LauncherTrip && m_Move && m_Move.Unresolved())) { m_State = 7; return; }
|
||||
if (m_Recipient) m_HQ.ReleaseLogistics(m_Recipient.Group, m_Token);
|
||||
if (m_Truck && m_Truck.SupplyToken == m_Token) { m_Truck.SupplyToken = 0; m_Truck.SupplyHeld = false; }
|
||||
m_Recipient = null; m_Donor = null; m_Order = null; m_Receiving = null; m_State = 1; m_MineTrip = false; m_LauncherTrip = false; m_MineRecipientSlot = -1;
|
||||
}
|
||||
protected void TransferMine(float now)
|
||||
{
|
||||
if (m_MineMove)
|
||||
{
|
||||
int result = m_MineMove.Tick(now);
|
||||
if (result == 0) return;
|
||||
if (result < 0) { Finish("native mine transfer failed or expired; exact submitted identity stays spent"); return; }
|
||||
m_MineMove = null; m_MinesDelivered++;
|
||||
Finish("one exact finite PMN4 confirmed in the original carrier inventory"); return;
|
||||
}
|
||||
if (!m_Receiving)
|
||||
{
|
||||
int strength = m_Recipient.StatusSampler.InitialStrength();
|
||||
for (int slot = 0; slot < strength && slot < 32; slot++)
|
||||
{
|
||||
IEntity actor = m_Recipient.StatusSampler.InitialMember(slot);
|
||||
if (!actor || !m_Members.Contains(actor) || !m_Recipient.StatusSampler.CanAttribute(slot, actor, false)) continue;
|
||||
if (!ERR_Actors.CanFight(actor) || vector.DistanceXZ(actor.GetOrigin(), m_Truck.Entity.GetOrigin()) > 4) continue;
|
||||
if (!m_HQ.MineSupplyReady(m_Recipient.Group, actor)) continue;
|
||||
IEntity carried;
|
||||
int count = ERR_InfantryMineTransfer.CarriedCount(actor, m_Recipient.Group, carried);
|
||||
if (count != 1 && (count != 0 || !m_HQ.MineSupplyReady(m_Recipient.Group, actor, true))) continue;
|
||||
m_EmptyMineReplacement = count == 0;
|
||||
m_Receiving = actor; m_MineRecipientSlot = slot; break;
|
||||
}
|
||||
if (!m_Receiving) return;
|
||||
}
|
||||
if (!m_HQ.MineSupplyReady(m_Recipient.Group, m_Receiving, m_EmptyMineReplacement)) return;
|
||||
IEntity carried;
|
||||
int count = ERR_InfantryMineTransfer.CarriedCount(m_Receiving, m_Recipient.Group, carried);
|
||||
if (!m_Recipient.StatusSampler.CanAttribute(m_MineRecipientSlot, m_Receiving, false)
|
||||
|| (m_EmptyMineReplacement && count != 0) || (!m_EmptyMineReplacement && count != 1))
|
||||
{ Finish("original mine recipient lost or its pinned zero/one stock changed"); return; }
|
||||
foreach (IEntity mine : m_MineStock)
|
||||
{
|
||||
if (!ERR_InfantryMineTransfer.ReadyMine(mine) || m_Spent.Contains(mine) || !Contains(m_Truck.Entity, mine)) continue;
|
||||
ERR_FiniteMagazineReservation reservation = new ERR_FiniteMagazineReservation(m_MineStock, m_Spent, mine);
|
||||
if (!reservation.Reserve()) continue;
|
||||
m_MineMove = new ERR_InfantryMineTransfer(m_Mode, m_Truck.Entity, m_Truck.Group, m_Receiving, m_Recipient.Group,
|
||||
mine, reservation, m_Recipient.StatusSampler, m_MineRecipientSlot, m_EmptyMineReplacement);
|
||||
m_MineMove.Tick(now);
|
||||
// The leaf commits before the one native move. Only an unsubmitted refusal
|
||||
// may release its tentative claim while the same carrier approaches/retries.
|
||||
if (m_MineMove.Submitted()) return;
|
||||
reservation.ReleaseUnsubmitted(); m_MineMove = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
protected void Transfer(float now)
|
||||
{
|
||||
if (m_Move)
|
||||
{
|
||||
int result = m_Move.Tick(now);
|
||||
if (result == 0) return;
|
||||
if (result < 0) { Finish("native transfer rejected, expired or lost authority; no item retry"); return; }
|
||||
m_Move = null; m_Delivered++; m_TripDelivered++; m_Served.Insert(m_Receiving); m_Receiving = null;
|
||||
Audit("exact finite magazine confirmed in original soldier inventory");
|
||||
if (m_TripDelivered >= 6) { Finish("six-magazine trip allowance reached"); return; }
|
||||
}
|
||||
foreach (IEntity actor : m_Members)
|
||||
{
|
||||
if (!ERR_Actors.CanFight(actor) || m_Served.Contains(actor) || vector.DistanceXZ(actor.GetOrigin(), m_Truck.Entity.GetOrigin()) > 4) continue;
|
||||
ERR_HQAmmoSnapshot local = new ERR_HQAmmoSnapshot(); local.Complete = true; local.LauncherComplete = true;
|
||||
ERR_HQAmmoSampler.Inspect(actor, m_Recipient.Group, local);
|
||||
if (m_LauncherTrip)
|
||||
{
|
||||
if (!local.Valid(1) || !local.LauncherComplete || local.Members != 1 || local.LauncherMuzzles <= 0
|
||||
|| local.LauncherRounds != 0 || local.LauncherChambers != 0) continue;
|
||||
}
|
||||
else if (!local.Complete || local.SmallArmsMuzzles <= 0 || local.SmallArmsRounds + local.SmallArmsChambers > 30) continue;
|
||||
foreach (IEntity magazine : m_Stock)
|
||||
{
|
||||
if (!magazine || m_Spent.Contains(magazine) || !Contains(m_Truck.Entity, magazine) || !ERR_InfantryAmmoTransfer.Compatible(actor, magazine, m_LauncherTrip)) continue;
|
||||
ERR_FiniteMagazineReservation reservation = new ERR_FiniteMagazineReservation(m_Stock, m_Spent, magazine);
|
||||
if (!reservation.Reserve()) continue;
|
||||
m_Receiving = actor;
|
||||
m_Move = new ERR_InfantryAmmoTransfer(m_Mode, m_Truck.Entity, m_Truck.Group, actor, m_Recipient.Group, magazine, !m_LauncherTrip, m_LauncherTrip);
|
||||
m_Move.Tick(now);
|
||||
if (m_Move.Submitted()) { reservation.Commit(); return; }
|
||||
// No native operation exists yet: approach geometry, stopped state or
|
||||
// capacity may improve while the same bounded rendezvous remains valid.
|
||||
// Keep every submitted identity spent, including a synchronous rejection.
|
||||
reservation.ReleaseUnsubmitted(); m_Move = null; m_Receiving = null;
|
||||
break; // At most one unsubmitted preflight per member on this slow tick.
|
||||
}
|
||||
}
|
||||
}
|
||||
void Tick(float now)
|
||||
{
|
||||
if (!ERR_ForceRules.Finite(now) || now < 0) return;
|
||||
// Stopped services retain uncertain leaf refs until exact late proof settles.
|
||||
if (m_Stopped) { PollTransfers(now); return; }
|
||||
if (!Current() || now < m_NextTick) return;
|
||||
m_NextTick = now + 1000;
|
||||
PollTransfers(now);
|
||||
if (m_Mounted && m_Mounted.Busy()) return;
|
||||
if (m_State == 7) return;
|
||||
if (m_State == 0) { Stock(now); return; }
|
||||
if (m_State == 1)
|
||||
{
|
||||
if (m_Move && !m_Move.Unresolved()) m_Move = null;
|
||||
return;
|
||||
}
|
||||
if (now >= m_Deadline || !TruckValid() || !Quiet(m_Donor) || !Roster() || m_Truck.Combat.Held
|
||||
|| m_HQ.LogisticsToken(m_Recipient.Group) != m_Token)
|
||||
{ Finish("rendezvous expired, contact appeared or original ownership changed"); return; }
|
||||
if (m_Truck.SupplyToken != m_Token) { Finish("truck hold authority lost"); return; }
|
||||
if (m_State == 2)
|
||||
{
|
||||
int delivered = m_Order.Advance(now, m_HQ.Journal());
|
||||
if (delivered == 0) return;
|
||||
if (delivered < 0 || m_Truck.SupplyToken != m_Token) { Finish("truck hold downlink interrupted or reservation lost"); return; }
|
||||
m_Truck.SupplyHeld = true; m_Truck.ClearOrder(); m_HeldAt = -1;
|
||||
m_Order = null; m_State = 3; Audit("truck received stop; waiting for delivered stopped-position report"); return;
|
||||
}
|
||||
if (m_State == 3)
|
||||
{
|
||||
// The delivered hold asks the original crew to report only after physically stopping.
|
||||
if (m_HeldAt < 0)
|
||||
{
|
||||
Physics physics = m_Truck.Entity.GetPhysics();
|
||||
if (!physics) return;
|
||||
float speed = physics.GetVelocity().Length();
|
||||
float angular = physics.GetAngularVelocity().Length();
|
||||
if (!(speed >= 0 && speed <= 0.25 && angular >= 0 && angular <= 0.1)) return;
|
||||
m_HeldAt = now; m_Donor.NextStatus = now; return;
|
||||
}
|
||||
ERR_HQWatchEntry stopped = m_HQ.Watch().Find(m_Donor.ForceSourceId);
|
||||
if (!ERR_InfantryResupplyRules.Fresh(stopped, now) || stopped.ObservedAt < m_HeldAt) return;
|
||||
m_Rendezvous = stopped.Position + "3 0 0";
|
||||
m_Order = m_HQ.Downlink(m_Recipient.Group, "INFANTRY SUPPLY RENDEZVOUS", string.Format("walk to reported stopped ammo truck at %1", m_Rendezvous), m_ReportId);
|
||||
if (!m_Order) { Finish("infantry radio downlink unavailable"); return; }
|
||||
m_State = 4; return;
|
||||
}
|
||||
if (m_State == 4)
|
||||
{
|
||||
int delivered = m_Order.Advance(now, m_HQ.Journal());
|
||||
if (delivered == 0) return;
|
||||
if (delivered < 0 || !Waypoints()) { Finish("infantry order interrupted or native waypoint creation failed"); return; }
|
||||
m_Order = null; m_State = 5; Audit("infantry received reported rendezvous; native movement and physical transfer required");
|
||||
}
|
||||
if (m_State == 5)
|
||||
{
|
||||
if (m_MineTrip) TransferMine(now);
|
||||
else Transfer(now);
|
||||
}
|
||||
}
|
||||
string Status()
|
||||
{
|
||||
string detail = string.Format("Ground supply: state=%1 trips=%2 infantry moved=%3 stock=%4 spent=%5", State(), m_Trip, m_Delivered, m_Stock.Count(), m_Spent.Count());
|
||||
return detail + string.Format("; mines moved=%1 initial=%2; %3", m_MinesDelivered, m_MineStock.Count(), m_Reason);
|
||||
}
|
||||
void Stop()
|
||||
{
|
||||
if (m_Stopped) return;
|
||||
if (m_Mounted) m_Mounted.Stop();
|
||||
Finish("service stopped; original forces and all remaining cargo retained"); m_Stopped = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"decision": "FINAL APPROVAL: no remaining concrete source blocker after LauncherMuzzles, full bounded stock scan and settled terminal-refusal corrections.",
|
||||
"producer": "/tmp/erri-launcher-resupply-1140",
|
||||
"patch_sha256": "31d28c0f618a72bdb5230612bff9f823e2ad981961ba0c57dfbfd4bb03078252",
|
||||
"source_sha256": {
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c": "52e601a0571b4942c9d4d0ce820727df8498febd6726efbd7da0c5eff7041b58",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQAmmoSampler.c": "8bb0ec1422887feaa340307d3c60bae50fc82d424fce6390876e580ff8b75e8b",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c": "b01fa948f204d49acbfbdfc5fa641b1d914e0eb91081e92a93816ae52ae88b1e",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoTransfer.c": "463f8a355aeac40e5ee80981b09e8561fae1b44e179facf911a0fecbfc7722fe"
|
||||
},
|
||||
"independent_tests": {
|
||||
"test_hq_ammo_sampler.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "e9c2d147eb1547be471df7459f1be815cdc9ef0a64c9a4ae4ef58936424dbc55",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_hq_ammo_sampler.py.log"
|
||||
},
|
||||
"test_infantry_ammo_transfer.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "7929033fe661de87f9ca72bc33d64fe996ac8b8e3630a17b4210a6f498b2930f",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_infantry_ammo_transfer.py.log"
|
||||
},
|
||||
"test_infantry_resupply_planning.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "9929ee0f5a3272c348ee329355b6ba67fdcb745fc9ce1f377f6c989eae91bf9f",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_infantry_resupply_planning.py.log"
|
||||
},
|
||||
"test_infantry_resupply_coordinator.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "f9cf7d583a25a12cec9f5f80539b2590449560331f4ef34e33ea2db283c5d54a",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_infantry_resupply_coordinator.py.log"
|
||||
},
|
||||
"test_mounted_supply_integration.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "f52acf7a16c9d5273894eb5a4afe7657a7c97de2ed72ba06671d25e163eea5d7",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_mounted_supply_integration.py.log"
|
||||
},
|
||||
"test_hq_multiconvoy_supply.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "e58d1d6eba48b2c5c6073d54c76302b2a7250ad22176fa94c386e1bc2ce65588",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_hq_multiconvoy_supply.py.log"
|
||||
},
|
||||
"test_hq_ammo_reporting.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "bdd4a30440501a2861aca80c951098c48cf999023eaaabd9a2eef82174f8173d",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_hq_ammo_reporting.py.log"
|
||||
},
|
||||
"test_hq_watch.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "51d5a728b0be83ff957c0df61289d14f90aba109b49365e457c9b200d5ede36a",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_hq_watch.py.log"
|
||||
},
|
||||
"test_infantry_mine_coordinator.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "2fdb2138685a329cef11557973450bd5189d3d7d657dac5cc51b3df2530fab6b",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_infantry_mine_coordinator.py.log"
|
||||
},
|
||||
"test_infantry_supply_pool.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "6fc0053337bbb889021c6eba608796ef946ff4d30a1b01cf678927293218d9d2",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_infantry_supply_pool.py.log"
|
||||
},
|
||||
"test_hq_status_sampler.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "b8582fb71b7ac77a23158ccd6230e8124ef0432831204e4b8992dee3a9c10062",
|
||||
"log": "/tmp/erri-launcher-review-1130/final-test_hq_status_sampler.py.log"
|
||||
}
|
||||
},
|
||||
"reviewed_final_changes": [
|
||||
"LauncherMuzzles is detached in Snapshot and validated from0 through32 per sampled member. Inspect counts only non-disposable well-bearing supported launcher muzzles; SCR_MuzzleInMagComponent.CanBeReloaded refusal makes launcher state incomplete. Native APIs verified in installed generated BaseMuzzleComponent and SCR_MuzzleInMagComponent.",
|
||||
"Delivered demand requires fresh complete original living roster counts and at least one supported launcher muzzle with zero rounds and chambers. Empty GetMagazine is allowed, matching consumed one-round projectile-magazines; no absent-weapon inference.",
|
||||
"Optional existing loaded magazine retains exact current muzzle-container/attached-entity/well proof. Incoming positive unused finite compatible magazine and original carried weapon custody still required.",
|
||||
"LauncherStock scans the full bounded96x32 original stock/roster pair space, preventing permanent prefix starvation. No claims/replay/cooldown consumed for rifle-only or incompatible stock.",
|
||||
"Rifle/mounted requests keep priority; launcher precedes mines. Same spent ledger, original finite inventory, report replay and recipient cooldown remain.",
|
||||
"Mode reaches physical per-actor sampler, Compatible, transfer constructor and Context. Launcher empty-return is disabled; no speculative recycling of fired projectile identities.",
|
||||
"Native uncertainty retains exact donor/recipient claims, no retry/refund after submission; stopped polling continues only proof/cleanup.",
|
||||
"Terminal negative callback settles only with pinned original reciprocal slot/storage, unchanged original managers, exclusive donor inventory, no recipient copy and managed original donor storage. Unknown callback/native false, detached/foreign/replaced custody remain quarantined; spent identity is never refunded or retried."
|
||||
],
|
||||
"limitations": [
|
||||
"No native compile/run by reviewer. Actual RPG/VOG inventory transfer, reload, animations and multiplayer remain unqualified; tests use modeled native boundaries.",
|
||||
"Does not manufacture ammunition or guarantee automatic reload. Disposable/unsupported no-well configurations remain refused."
|
||||
],
|
||||
"source_changes_by_reviewer": "None; only isolated review artifacts/logs/mutation under /tmp.",
|
||||
"mutations": [
|
||||
{
|
||||
"log": "/tmp/erri-launcher-review-1130/prefix-starvation-mutation-red.log",
|
||||
"result": "128-pair cap restoration compiled then failed actual late-stock admission assertion."
|
||||
},
|
||||
{
|
||||
"log": "/tmp/erri-launcher-review-1130/snapshot-mutation-red.log",
|
||||
"result": "Removing LauncherMuzzles Snapshot copy compiled then failed detached-count assertion."
|
||||
},
|
||||
{
|
||||
"log": "/tmp/erri-launcher-review-1130/terminal-refusal-mutation-red.log",
|
||||
"result": "Removing only RefusedAtSource settlement compiled then failed terminal-negative-source-custody assertion; spent remained1."
|
||||
}
|
||||
]
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
check: /tmp/erri-launcher-review-1130/scratch/err-policy-smw7jj3i/check.cpp:278: int main(): Assertion `launcherWire->LauncherMuzzles==1' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-launcher-resupply-1140/tests/test_hq_ammo_sampler.py", line 24, in <module>
|
||||
run(r'''
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<142 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/erri-launcher-review-1130/scratch/err-policy-smw7jj3i/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1,209 @@
|
||||
// Detached counts of compatible small-arms magazines/chambers and separately
|
||||
// supported launcher magazines. Unknown inventories are never reported empty.
|
||||
class ERR_HQAmmoSnapshot
|
||||
{
|
||||
bool Complete;
|
||||
bool LauncherComplete;
|
||||
bool MineComplete;
|
||||
int MineMembers;
|
||||
int ReadyMines;
|
||||
int Members;
|
||||
int SmallArmsMuzzles;
|
||||
int SmallArmsRounds;
|
||||
int SmallArmsMagazines;
|
||||
int SmallArmsChambers;
|
||||
int LauncherMuzzles;
|
||||
int LauncherRounds;
|
||||
int LauncherMagazines;
|
||||
int LauncherChambers;
|
||||
bool KnownSmallArmsEmpty()
|
||||
{
|
||||
return Complete && Members > 0 && SmallArmsMuzzles > 0 && SmallArmsRounds == 0 && SmallArmsChambers == 0;
|
||||
}
|
||||
ERR_HQAmmoSnapshot Snapshot()
|
||||
{
|
||||
ERR_HQAmmoSnapshot copy = new ERR_HQAmmoSnapshot();
|
||||
copy.Complete = Complete; copy.LauncherComplete = LauncherComplete; copy.Members = Members;
|
||||
copy.SmallArmsMuzzles = SmallArmsMuzzles; copy.SmallArmsRounds = SmallArmsRounds;
|
||||
copy.SmallArmsMagazines = SmallArmsMagazines; copy.SmallArmsChambers = SmallArmsChambers;
|
||||
copy.LauncherRounds = LauncherRounds; copy.LauncherMagazines = LauncherMagazines;
|
||||
copy.LauncherChambers = LauncherChambers;
|
||||
copy.MineComplete = MineComplete; copy.MineMembers = MineMembers; copy.ReadyMines = ReadyMines;
|
||||
return copy;
|
||||
}
|
||||
bool Valid(int living)
|
||||
{
|
||||
if (MineMembers < 0 || MineMembers > living || living > 32 || (MineComplete && MineMembers != living)) return false;
|
||||
if (ReadyMines < 0 || ReadyMines > MineMembers * 128) return false;
|
||||
if (Members < 0 || Members > living || living > 32 || (Complete && Members != living)) return false;
|
||||
if (SmallArmsMuzzles < 0 || SmallArmsMuzzles > 1024) return false;
|
||||
if (SmallArmsRounds < 0 || SmallArmsRounds > 4096000) return false;
|
||||
if (SmallArmsMagazines < 0 || SmallArmsMagazines > 4096) return false;
|
||||
if (SmallArmsChambers < 0 || SmallArmsChambers > 4096) return false;
|
||||
if (LauncherMuzzles < 0 || LauncherMuzzles > Members * 32) return false;
|
||||
if (LauncherRounds < 0 || LauncherRounds > 4096000) return false;
|
||||
if (LauncherMagazines < 0 || LauncherMagazines > 4096) return false;
|
||||
return LauncherChambers >= 0 && LauncherChambers <= 4096;
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_HQAmmoSampler
|
||||
{
|
||||
protected static int Category(BaseWeaponComponent weapon, BaseMuzzleComponent muzzle)
|
||||
{
|
||||
if (!weapon || !muzzle) return 0;
|
||||
EWeaponType kind = weapon.GetWeaponType();
|
||||
EMuzzleType muzzleKind = muzzle.GetMuzzleType();
|
||||
if (muzzleKind == EMuzzleType.MT_RPGMuzzle || muzzleKind == EMuzzleType.MT_UGLMuzzle
|
||||
|| kind == EWeaponType.WT_ROCKETLAUNCHER || kind == EWeaponType.WT_GRENADELAUNCHER) return 2;
|
||||
if (kind == EWeaponType.WT_RIFLE || kind == EWeaponType.WT_SNIPERRIFLE
|
||||
|| kind == EWeaponType.WT_MACHINEGUN || kind == EWeaponType.WT_HANDGUN) return 1;
|
||||
return 0;
|
||||
}
|
||||
protected static bool Compatible(BaseMagazineComponent magazine, array<BaseMuzzleComponent> muzzles)
|
||||
{
|
||||
if (!magazine || !magazine.GetMagazineWell()) return false;
|
||||
foreach (BaseMuzzleComponent muzzle : muzzles)
|
||||
if (muzzle.GetMagazineWell() && muzzle.GetMagazineWell().Type() == magazine.GetMagazineWell().Type()) return true;
|
||||
return false;
|
||||
}
|
||||
// Native magazine search permits derived wells, while the reload UI uses exact
|
||||
// types. Keep exact counts, but never infer empty from uncertain derived stock.
|
||||
protected static bool InheritedCandidate(BaseMagazineComponent magazine, array<BaseMuzzleComponent> muzzles)
|
||||
{
|
||||
if (!magazine || !magazine.GetMagazineWell()) return false;
|
||||
foreach (BaseMuzzleComponent muzzle : muzzles)
|
||||
{
|
||||
if (!muzzle.GetMagazineWell()) continue;
|
||||
if (magazine.GetMagazineWell().Type() == muzzle.GetMagazineWell().Type()) continue;
|
||||
if (magazine.GetMagazineWell().IsInherited(muzzle.GetMagazineWell().Type())) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Caller must enumerate the immutable own roster. No group discovery, position,
|
||||
// spawning, ammunition mutation, vehicle weapons or virtual inventory work.
|
||||
static void Inspect(IEntity actor, SCR_AIGroup group, ERR_HQAmmoSnapshot result)
|
||||
{
|
||||
if (!result) return;
|
||||
if (!Replication.IsServer() || !group || group.IsSlave() || group.GetPlayerCount(true) > 0
|
||||
|| !ERR_Actors.Alive(actor) || ERR_Actors.Player(actor) || ERR_Actors.Group(actor) != group)
|
||||
{ result.Complete = false; result.LauncherComplete = false; return; }
|
||||
if (result.Members >= 32) { result.Complete = false; result.LauncherComplete = false; return; }
|
||||
result.Members++;
|
||||
InventoryStorageManagerComponent inventory;
|
||||
inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent));
|
||||
BaseWeaponManagerComponent manager;
|
||||
manager = BaseWeaponManagerComponent.Cast(actor.FindComponent(BaseWeaponManagerComponent));
|
||||
if (!inventory || !manager) { result.Complete = false; result.LauncherComplete = false; return; }
|
||||
array<IEntity> raw = {}; inventory.GetItems(raw);
|
||||
array<IEntity> weapons = {}; manager.GetWeaponsList(weapons);
|
||||
if (raw.Count() > 128 || weapons.Count() > 8)
|
||||
{ result.Complete = false; result.LauncherComplete = false; return; }
|
||||
array<IEntity> items = {};
|
||||
foreach (IEntity item : raw)
|
||||
{
|
||||
if (!item) { result.Complete = false; result.LauncherComplete = false; continue; }
|
||||
if (!items.Contains(item)) items.Insert(item);
|
||||
}
|
||||
array<BaseMuzzleComponent> small = {};
|
||||
array<BaseMuzzleComponent> launchers = {};
|
||||
array<IEntity> seenWeapons = {};
|
||||
foreach (IEntity weaponEntity : weapons)
|
||||
{
|
||||
if (!weaponEntity) { result.Complete = false; result.LauncherComplete = false; continue; }
|
||||
if (seenWeapons.Contains(weaponEntity)) continue;
|
||||
seenWeapons.Insert(weaponEntity);
|
||||
BaseWeaponComponent weapon = BaseWeaponComponent.Cast(weaponEntity.FindComponent(BaseWeaponComponent));
|
||||
if (!weapon) { result.Complete = false; result.LauncherComplete = false; continue; }
|
||||
// Throwables have no relevant magazine well; smoke/grenades do not pretend to be rifle ammunition.
|
||||
if (weapon.GetWeaponType() == EWeaponType.WT_FRAGGRENADE
|
||||
|| weapon.GetWeaponType() == EWeaponType.WT_SMOKEGRENADE) continue;
|
||||
bool launcherWeapon = weapon.GetWeaponType() == EWeaponType.WT_ROCKETLAUNCHER
|
||||
|| weapon.GetWeaponType() == EWeaponType.WT_GRENADELAUNCHER;
|
||||
array<BaseMuzzleComponent> muzzles = {}; weapon.GetMuzzlesList(muzzles);
|
||||
if (muzzles.IsEmpty() || muzzles.Count() > 4)
|
||||
{
|
||||
if (!launcherWeapon) result.Complete = false;
|
||||
result.LauncherComplete = false;
|
||||
continue;
|
||||
}
|
||||
foreach (BaseMuzzleComponent muzzle : muzzles)
|
||||
{
|
||||
if (!muzzle)
|
||||
{
|
||||
if (!launcherWeapon) result.Complete = false;
|
||||
result.LauncherComplete = false;
|
||||
continue;
|
||||
}
|
||||
if (small.Contains(muzzle) || launchers.Contains(muzzle)) continue;
|
||||
int category = Category(weapon, muzzle);
|
||||
if (category == 0) { result.Complete = false; result.LauncherComplete = false; continue; }
|
||||
if (category == 1) { small.Insert(muzzle); result.SmallArmsMuzzles++; }
|
||||
else
|
||||
{
|
||||
launchers.Insert(muzzle);
|
||||
SCR_MuzzleInMagComponent reloadable = SCR_MuzzleInMagComponent.Cast(muzzle);
|
||||
bool supported = muzzle.GetMagazineWell() && !muzzle.IsDisposable();
|
||||
if (reloadable && !reloadable.CanBeReloaded()) supported = false;
|
||||
if (supported) result.LauncherMuzzles++;
|
||||
else result.LauncherComplete = false;
|
||||
}
|
||||
if (!muzzle.GetMagazineWell())
|
||||
{
|
||||
if (category == 1) result.Complete = false;
|
||||
else result.LauncherComplete = false; // loose launcher projectiles are not compatible-magazine counts
|
||||
}
|
||||
int barrels = muzzle.GetBarrelsCount();
|
||||
if (barrels < 1 || barrels > 4)
|
||||
{
|
||||
if (category == 1) result.Complete = false;
|
||||
else result.LauncherComplete = false;
|
||||
continue;
|
||||
}
|
||||
for (int barrel = 0; barrel < barrels; barrel++)
|
||||
{
|
||||
if (!muzzle.IsBarrelChambered(barrel)) continue;
|
||||
if (category == 1) result.SmallArmsChambers++;
|
||||
else result.LauncherChambers++;
|
||||
}
|
||||
BaseMagazineComponent loaded = muzzle.GetMagazine();
|
||||
if (loaded && (!loaded.GetOwner() || !loaded.GetMagazineWell()))
|
||||
{
|
||||
if (category == 1) result.Complete = false;
|
||||
else result.LauncherComplete = false;
|
||||
}
|
||||
if (loaded && loaded.GetOwner() && !items.Contains(loaded.GetOwner())) items.Insert(loaded.GetOwner());
|
||||
}
|
||||
}
|
||||
// Physical identity deduplicates loaded/carried enumeration and shared compatible weapon wells.
|
||||
if (items.Count() > 128) { result.Complete = false; result.LauncherComplete = false; return; }
|
||||
foreach (IEntity item : items)
|
||||
{
|
||||
BaseMagazineComponent magazine = BaseMagazineComponent.Cast(item.FindComponent(BaseMagazineComponent));
|
||||
if (!magazine) continue;
|
||||
if (!magazine.GetMagazineWell())
|
||||
{
|
||||
// Stock reload and magazine-search predicates reject a candidate with no well.
|
||||
// It cannot replenish our fully inspected well-bearing small arms. A loaded
|
||||
// no-well small-arms magazine was already rejected in its muzzle above.
|
||||
result.LauncherComplete = false;
|
||||
continue;
|
||||
}
|
||||
if (InheritedCandidate(magazine, small)) result.Complete = false;
|
||||
if (InheritedCandidate(magazine, launchers)) result.LauncherComplete = false;
|
||||
bool rifle = Compatible(magazine, small);
|
||||
bool launcher = Compatible(magazine, launchers);
|
||||
if (!rifle && !launcher) continue;
|
||||
int rounds = magazine.GetAmmoCount();
|
||||
int capacity = magazine.GetMaxAmmoCount();
|
||||
if ((rifle && launcher) || rounds < 0 || capacity < 1 || capacity > 1000 || rounds > capacity)
|
||||
{
|
||||
if (rifle) result.Complete = false;
|
||||
if (launcher) result.LauncherComplete = false;
|
||||
continue;
|
||||
}
|
||||
if (rifle) { result.SmallArmsRounds += rounds; result.SmallArmsMagazines++; }
|
||||
else { result.LauncherRounds += rounds; result.LauncherMagazines++; }
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
check: /tmp/erri-launcher-review-1130/scratch/err-policy-dq0m6hc6/check.cpp:495: int main(): Assertion `f.c.m_Move->Submitted()&&!f.c.m_Move->Unresolved()&&f.c.m_Spent.Count()==1' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-launcher-resupply-1140/tests/test_infantry_ammo_transfer.py", line 338, in <module>
|
||||
run(pre+translated_reservation()+code+'\n'.join(callbacks)+main)
|
||||
~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
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/erri-launcher-review-1130/scratch/err-policy-dq0m6hc6/check']' died with <Signals.SIGABRT: 6>.
|
||||
+314
@@ -0,0 +1,314 @@
|
||||
enum ERR_InfantryAmmoTransferState { FAILED = -1, PENDING, SUCCEEDED }
|
||||
|
||||
class ERR_InfantryAmmoCallback : ScriptedInventoryOperationCallback
|
||||
{
|
||||
ERR_InfantryAmmoTransfer Transfer;
|
||||
bool Returning;
|
||||
protected override void OnComplete() { if (Transfer) Transfer.Acknowledge(true, Returning); }
|
||||
protected override void OnFailed() { if (Transfer) Transfer.Acknowledge(false, Returning); }
|
||||
protected override void OnAllComplete() { if (Transfer) Transfer.Acknowledge(true, Returning); }
|
||||
protected override void OnAllFailed() { if (Transfer) Transfer.Acknowledge(false, Returning); }
|
||||
}
|
||||
|
||||
// One exact physical magazine movement. The caller owns HQ authorization and
|
||||
// the spent-item reservation; this leaf never selects tasks or retries a move.
|
||||
class ERR_InfantryAmmoTransfer
|
||||
{
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected Vehicle m_Donor;
|
||||
protected SCR_AIGroup m_DonorGroup;
|
||||
protected IEntity m_Recipient;
|
||||
protected SCR_AIGroup m_RecipientGroup;
|
||||
protected IEntity m_Magazine;
|
||||
protected IEntity m_Driver;
|
||||
protected ERR_ForceRecord m_DonorRecord;
|
||||
protected ERR_ForceRecord m_RecipientRecord;
|
||||
protected InventoryStorageManagerComponent m_Source;
|
||||
protected InventoryStorageManagerComponent m_Destination;
|
||||
protected BaseInventoryStorageComponent m_Storage;
|
||||
protected InventoryStorageSlot m_OriginalSlot;
|
||||
protected BaseInventoryStorageComponent m_OriginalStorage;
|
||||
protected ref ERR_InfantryAmmoCallback m_Callback;
|
||||
protected bool m_RecycleEmpty;
|
||||
protected bool m_LauncherMagazine;
|
||||
protected IEntity m_ReturnMagazine;
|
||||
protected BaseInventoryStorageComponent m_ReturnStorage;
|
||||
protected ref ERR_InfantryAmmoCallback m_ReturnCallback;
|
||||
protected bool m_ReturnSubmitted;
|
||||
protected bool m_ReturnAccepted;
|
||||
protected bool m_ReturnDone;
|
||||
protected int m_ReturnAcknowledgement;
|
||||
protected float m_PhaseStarted = -1;
|
||||
protected bool m_Submitted;
|
||||
protected bool m_Accepted;
|
||||
protected int m_Acknowledgement;
|
||||
protected float m_Started = -1;
|
||||
protected ERR_InfantryAmmoTransferState m_State = ERR_InfantryAmmoTransferState.PENDING;
|
||||
protected string m_Reason;
|
||||
void ERR_InfantryAmmoTransfer(ERR_GameMode mode, Vehicle donorVehicle, SCR_AIGroup donorGroup, IEntity recipientActor, SCR_AIGroup recipientGroup, IEntity exactMagazine, bool recycleEmpty = false, bool launcherMagazine = false)
|
||||
{
|
||||
m_RecycleEmpty = recycleEmpty; m_LauncherMagazine = launcherMagazine;
|
||||
m_Mode = mode; m_Donor = donorVehicle; m_DonorGroup = donorGroup;
|
||||
m_Recipient = recipientActor; m_RecipientGroup = recipientGroup; m_Magazine = exactMagazine;
|
||||
}
|
||||
static bool Compatible(IEntity actor, IEntity exactMagazine, bool launcherMagazine = false)
|
||||
{
|
||||
if (!actor || !exactMagazine) return false;
|
||||
BaseMagazineComponent magazine = BaseMagazineComponent.Cast(exactMagazine.FindComponent(BaseMagazineComponent));
|
||||
BaseWeaponManagerComponent manager = BaseWeaponManagerComponent.Cast(actor.FindComponent(BaseWeaponManagerComponent));
|
||||
if (!magazine || !manager || !magazine.GetMagazineWell() || magazine.GetAmmoCount() <= 0 || magazine.GetMaxAmmoCount() <= 0 || magazine.GetAmmoCount() > magazine.GetMaxAmmoCount()) return false;
|
||||
if (launcherMagazine && magazine.IsUsed()) return false;
|
||||
array<IEntity> weapons = {}; manager.GetWeaponsList(weapons);
|
||||
if (weapons.IsEmpty() || weapons.Count() > 8) return false;
|
||||
foreach (IEntity rifle : weapons)
|
||||
{
|
||||
if (!rifle) continue;
|
||||
BaseWeaponComponent weapon = BaseWeaponComponent.Cast(rifle.FindComponent(BaseWeaponComponent));
|
||||
if (!weapon || weapon.GetOwner() != rifle) continue;
|
||||
EWeaponType kind = weapon.GetWeaponType();
|
||||
bool smallWeapon = kind == EWeaponType.WT_RIFLE || kind == EWeaponType.WT_SNIPERRIFLE || kind == EWeaponType.WT_MACHINEGUN || kind == EWeaponType.WT_HANDGUN;
|
||||
if (!launcherMagazine && !smallWeapon) continue;
|
||||
if (launcherMagazine && !smallWeapon && kind != EWeaponType.WT_ROCKETLAUNCHER && kind != EWeaponType.WT_GRENADELAUNCHER) continue;
|
||||
if (launcherMagazine && !StoredUnder(rifle, actor)) continue;
|
||||
array<BaseMuzzleComponent> muzzles = {}; weapon.GetMuzzlesList(muzzles);
|
||||
if (muzzles.Count() > 4) continue;
|
||||
foreach (BaseMuzzleComponent muzzle : muzzles)
|
||||
{
|
||||
if (!muzzle || !muzzle.GetMagazineWell()) continue;
|
||||
EMuzzleType muzzleKind = muzzle.GetMuzzleType();
|
||||
if (!launcherMagazine && muzzleKind != EMuzzleType.MT_BaseMuzzle) continue;
|
||||
if (launcherMagazine)
|
||||
{
|
||||
if (muzzleKind != EMuzzleType.MT_RPGMuzzle && muzzleKind != EMuzzleType.MT_UGLMuzzle
|
||||
&& kind != EWeaponType.WT_ROCKETLAUNCHER && kind != EWeaponType.WT_GRENADELAUNCHER) continue;
|
||||
// An expended single-round item may no longer exist. The real reloadable
|
||||
// muzzle/well proves capability; existing loaded items need exact custody.
|
||||
if (muzzle.IsDisposable()) continue;
|
||||
SCR_MuzzleInMagComponent reloadable = SCR_MuzzleInMagComponent.Cast(muzzle);
|
||||
if (reloadable && !reloadable.CanBeReloaded()) continue;
|
||||
BaseMagazineComponent loaded = muzzle.GetMagazine();
|
||||
if (loaded)
|
||||
{
|
||||
if (!loaded.GetOwner() || !loaded.GetMagazineWell() || loaded.GetMaxAmmoCount() <= 0) continue;
|
||||
InventoryItemComponent loadedItem = InventoryItemComponent.Cast(loaded.GetOwner().FindComponent(InventoryItemComponent));
|
||||
if (!loadedItem || !loadedItem.GetParentSlot()) continue;
|
||||
InventoryStorageSlot loadedSlot = loadedItem.GetParentSlot();
|
||||
if (loadedSlot.GetParentContainer() != muzzle || loadedSlot.GetAttachedEntity() != loaded.GetOwner()) continue;
|
||||
if (loaded.GetMagazineWell().Type() != muzzle.GetMagazineWell().Type()) continue;
|
||||
}
|
||||
if (magazine.GetMagazineWell().Type() != muzzle.GetMagazineWell().Type()) continue;
|
||||
}
|
||||
// Same direction as the stock magazine inventory predicate: a magazine's
|
||||
// derived well must inherit the actual carried small-arms muzzle's well.
|
||||
if (magazine.GetMagazineWell().IsInherited(muzzle.GetMagazineWell().Type())) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static bool StoredUnder(IEntity entity, IEntity root)
|
||||
{
|
||||
for (int depth = 0; entity && depth < 16; depth++)
|
||||
{
|
||||
if (entity == root) return true;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent));
|
||||
if (item && item.GetParentSlot())
|
||||
{
|
||||
BaseInventoryStorageComponent storage = item.GetParentSlot().GetStorage();
|
||||
if (!storage) return false;
|
||||
entity = storage.GetOwner();
|
||||
}
|
||||
else entity = entity.GetParent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected bool ManagedStorage(InventoryStorageManagerComponent inventory, BaseInventoryStorageComponent storage, IEntity owner)
|
||||
{
|
||||
if (!inventory || !storage || inventory.GetOwner() != owner || !StoredUnder(storage.GetOwner(), owner)) return false;
|
||||
array<BaseInventoryStorageComponent> storages = {};
|
||||
inventory.GetStorages(storages, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
return storages.Count() <= 64 && storages.Contains(storage);
|
||||
}
|
||||
protected int Ownership(InventoryStorageManagerComponent inventory, IEntity exact = null)
|
||||
{
|
||||
if (!exact) exact = m_Magazine;
|
||||
if (!inventory || !exact) return -1;
|
||||
array<IEntity> items = {}; inventory.GetItems(items, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (items.Count() > 512) return -1;
|
||||
if (items.Contains(exact)) return 1;
|
||||
return 0;
|
||||
}
|
||||
protected bool Proof(IEntity exact, InventoryStorageManagerComponent source, InventoryStorageManagerComponent destination, BaseInventoryStorageComponent storage, IEntity owner, int acknowledgement)
|
||||
{
|
||||
if (!exact || acknowledgement != 1 || Ownership(source, exact) != 0 || Ownership(destination, exact) != 1 || !ManagedStorage(destination, storage, owner)) return false;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(exact.FindComponent(InventoryItemComponent));
|
||||
return item && item.GetParentSlot() && item.GetParentSlot().GetStorage() == storage;
|
||||
}
|
||||
protected bool PhysicalProof()
|
||||
{
|
||||
return Proof(m_Magazine, m_Source, m_Destination, m_Storage, m_Recipient, m_Acknowledgement);
|
||||
}
|
||||
// Terminal negative callback is settled only with exact unchanged source
|
||||
// custody. Native false without a callback still retains all uncertainty.
|
||||
protected bool RefusedAtSource()
|
||||
{
|
||||
if (!m_Submitted || m_Acknowledgement != -1 || !m_Magazine || !m_Donor || !m_Recipient) return false;
|
||||
if (!m_OriginalSlot || !m_OriginalStorage) return false;
|
||||
if (InventoryStorageManagerComponent.Cast(m_Donor.FindComponent(InventoryStorageManagerComponent)) != m_Source) return false;
|
||||
if (InventoryStorageManagerComponent.Cast(m_Recipient.FindComponent(InventoryStorageManagerComponent)) != m_Destination) return false;
|
||||
if (Ownership(m_Source) != 1 || Ownership(m_Destination) != 0) return false;
|
||||
if (!ManagedStorage(m_Source, m_OriginalStorage, m_Donor)) return false;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(m_Magazine.FindComponent(InventoryItemComponent));
|
||||
if (!item || item.GetParentSlot() != m_OriginalSlot) return false;
|
||||
return m_OriginalSlot.GetStorage() == m_OriginalStorage && m_OriginalSlot.GetAttachedEntity() == m_Magazine;
|
||||
}
|
||||
protected bool ReturnProof()
|
||||
{
|
||||
return Proof(m_ReturnMagazine, m_Destination, m_Source, m_ReturnStorage, m_Donor, m_ReturnAcknowledgement);
|
||||
}
|
||||
void Acknowledge(bool success, bool returning = false)
|
||||
{
|
||||
if (returning)
|
||||
{
|
||||
if (m_ReturnAcknowledgement == 0)
|
||||
{
|
||||
if (success) m_ReturnAcknowledgement = 1;
|
||||
else m_ReturnAcknowledgement = -1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (m_Acknowledgement != 0) return;
|
||||
if (success) m_Acknowledgement = 1;
|
||||
else m_Acknowledgement = -1;
|
||||
}
|
||||
// Both native operations remain observable after Stop. Returning an empty
|
||||
// magazine never proves delivery or grants authority for a later full one.
|
||||
bool Unresolved()
|
||||
{
|
||||
if ((m_ReturnAccepted || (m_LauncherMagazine && m_ReturnSubmitted)) && !m_ReturnDone && m_ReturnMagazine && m_ReturnAcknowledgement >= 0 && !ReturnProof()) return true;
|
||||
if (m_LauncherMagazine && m_Submitted) return m_Magazine && !PhysicalProof();
|
||||
if (!m_Accepted || !m_Magazine || m_Acknowledgement < 0) return false;
|
||||
return !PhysicalProof();
|
||||
}
|
||||
// Any native submission consumes this incoming identity at the coordinator,
|
||||
// even when only the empty return was attempted and delivery never started.
|
||||
bool Submitted() { return m_Submitted || m_ReturnSubmitted; }
|
||||
string Reason() { return m_Reason; }
|
||||
protected ERR_InfantryAmmoTransferState Fail(string reason)
|
||||
{
|
||||
if (m_State == ERR_InfantryAmmoTransferState.PENDING) { m_State = ERR_InfantryAmmoTransferState.FAILED; m_Reason = reason; }
|
||||
return m_State;
|
||||
}
|
||||
void Stop() { Fail("HQ authorization stopped; an accepted native move cannot be cancelled"); }
|
||||
protected bool Context()
|
||||
{
|
||||
if (!Replication.IsServer() || SCR_Global.IsEditMode() || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active() || !m_Mode.Forces()) return false;
|
||||
if (!m_Donor || !ERR_Actors.Alive(m_Donor) || !m_DonorGroup || !m_RecipientGroup || m_DonorGroup == m_RecipientGroup) return false;
|
||||
if (!ERR_EnemyEquipmentRules.Eligible(m_Mode, m_RecipientGroup, m_Recipient) || CompartmentAccessComponent.GetVehicleIn(m_Recipient)) return false;
|
||||
if (m_DonorGroup.IsSlave() || m_RecipientGroup.IsSlave() || m_DonorGroup.GetPlayerCount(true) > 0 || m_DonorGroup.GetFaction() != m_Mode.EnemyFaction()) return false;
|
||||
if (SCR_FactionManager.SGetFaction(m_Donor) != m_Mode.EnemyFaction() || SCR_FactionManager.SGetFaction(m_Recipient) != m_Mode.EnemyFaction()) return false;
|
||||
ERR_ForceRecord donorRecord = m_Mode.Forces().FindGroup(m_DonorGroup);
|
||||
ERR_ForceRecord recipientRecord = m_Mode.Forces().FindGroup(m_RecipientGroup);
|
||||
if (!donorRecord || !recipientRecord || donorRecord.Group != m_DonorGroup || recipientRecord.Group != m_RecipientGroup || !donorRecord.Physical || !recipientRecord.Physical || donorRecord.Eliminated || recipientRecord.Eliminated) return false;
|
||||
if (m_DonorRecord && (m_DonorRecord != donorRecord || m_RecipientRecord != recipientRecord)) return false;
|
||||
SCR_AIGroupUtilityComponent utility = m_DonorGroup.GetGroupUtilityComponent();
|
||||
if (!utility || !utility.IsMilitary() || !utility.m_VehicleMgr || !utility.m_VehicleMgr.FindVehicle(m_Donor)) return false;
|
||||
if (ERR_ConvoyRules.Captured(m_Donor, m_DonorGroup)) return false;
|
||||
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(m_Donor.FindComponent(SCR_AIVehicleUsageComponent));
|
||||
if (!usage || !usage.GetPilotCompartmentSlot()) return false;
|
||||
IEntity driver = usage.GetPilotCompartmentSlot().GetOccupant();
|
||||
if (!ERR_EnemyEquipmentRules.Eligible(m_Mode, m_DonorGroup, driver) || !ERR_ConvoyRules.PartOf(CompartmentAccessComponent.GetVehicleIn(driver), m_Donor)) return false;
|
||||
if (SCR_FactionManager.SGetFaction(driver) != m_Mode.EnemyFaction() || (m_Driver && driver != m_Driver)) return false;
|
||||
Physics physics = m_Donor.GetPhysics();
|
||||
if (!physics) return false;
|
||||
float speed = physics.GetVelocity().Length();
|
||||
float angular = physics.GetAngularVelocity().Length();
|
||||
float distance = vector.Distance(m_Donor.GetOrigin(), m_Recipient.GetOrigin());
|
||||
if (!(speed >= 0 && speed <= 0.25 && angular >= 0 && angular <= 0.1 && distance >= 0 && distance <= 15)) return false;
|
||||
if (distance > 4 && !m_Donor.FindComponent(VehicleHelicopterSimulation)) return false;
|
||||
if (!Compatible(m_Recipient, m_Magazine, m_LauncherMagazine)) return false;
|
||||
m_Driver = driver; m_DonorRecord = donorRecord; m_RecipientRecord = recipientRecord;
|
||||
return true;
|
||||
}
|
||||
// One optional capacity recovery, using only an unused exact empty identity.
|
||||
protected bool ReturnEmpty()
|
||||
{
|
||||
if (!m_RecycleEmpty || m_ReturnSubmitted || m_ReturnMagazine) return false;
|
||||
BaseMagazineComponent incoming = BaseMagazineComponent.Cast(m_Magazine.FindComponent(BaseMagazineComponent));
|
||||
if (!incoming || !incoming.GetMagazineWell()) return false;
|
||||
array<IEntity> items = {}; m_Destination.GetItems(items, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (items.Count() > 128) return false;
|
||||
foreach (IEntity candidate : items)
|
||||
{
|
||||
if (!candidate || candidate == m_Magazine) continue;
|
||||
BaseMagazineComponent empty = BaseMagazineComponent.Cast(candidate.FindComponent(BaseMagazineComponent));
|
||||
if (!empty || empty.IsUsed() || empty.GetAmmoCount() != 0 || empty.GetMaxAmmoCount() <= 0
|
||||
|| !empty.GetMagazineWell() || empty.GetMagazineWell().Type() != incoming.GetMagazineWell().Type()) continue;
|
||||
if (Ownership(m_Destination, candidate) != 1 || Ownership(m_Source, candidate) != 0) continue;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(candidate.FindComponent(InventoryItemComponent));
|
||||
if (!item || !item.GetParentSlot() || !ManagedStorage(m_Destination, item.GetParentSlot().GetStorage(), m_Recipient)) continue;
|
||||
BaseInventoryStorageComponent storage = m_Source.FindStorageForItem(candidate, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (!ManagedStorage(m_Source, storage, m_Donor) || !m_Destination.CanMoveItemToStorage(candidate, storage, -1)) continue;
|
||||
m_ReturnMagazine = candidate; m_ReturnStorage = storage;
|
||||
m_ReturnCallback = new ERR_InfantryAmmoCallback(); m_ReturnCallback.Transfer = this; m_ReturnCallback.Returning = true;
|
||||
m_ReturnSubmitted = true;
|
||||
m_ReturnAccepted = m_Destination.TryMoveItemToStorage(candidate, storage, -1, m_ReturnCallback);
|
||||
if (m_ReturnAcknowledgement == 1) m_ReturnAccepted = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
ERR_InfantryAmmoTransferState Tick(float now)
|
||||
{
|
||||
if (m_State != ERR_InfantryAmmoTransferState.PENDING) return m_State;
|
||||
if (!ERR_ForceRules.Finite(now) || now < 0) return Fail("invalid transaction clock");
|
||||
if (m_Started < 0) { m_Started = now; m_PhaseStarted = now; }
|
||||
if (now < m_Started || now < m_PhaseStarted || now - m_Started >= 16000 || now - m_PhaseStarted >= 8000) return Fail("native magazine move deadline expired");
|
||||
if (!Context()) return Fail("original owned groups, vehicle, driver, recipient, proximity or compatible weapon unavailable");
|
||||
if (m_ReturnSubmitted && !m_ReturnDone)
|
||||
{
|
||||
if (!m_ReturnAccepted || m_ReturnAcknowledgement < 0) return Fail("native empty return rejected; neither identity will be retried");
|
||||
if (!ReturnProof()) return ERR_InfantryAmmoTransferState.PENDING;
|
||||
m_ReturnDone = true; m_PhaseStarted = now;
|
||||
// Separate slow tick allows loss of HQ authority to stop before delivery.
|
||||
return ERR_InfantryAmmoTransferState.PENDING;
|
||||
}
|
||||
if (!m_Submitted)
|
||||
{
|
||||
m_Source = InventoryStorageManagerComponent.Cast(m_Donor.FindComponent(InventoryStorageManagerComponent));
|
||||
m_Destination = InventoryStorageManagerComponent.Cast(m_Recipient.FindComponent(InventoryStorageManagerComponent));
|
||||
if (!m_Source || !m_Destination || m_Source == m_Destination || Ownership(m_Source) != 1 || Ownership(m_Destination) != 0) return Fail("exact magazine is not exclusive donor stock");
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(m_Magazine.FindComponent(InventoryItemComponent));
|
||||
if (!item || !item.GetParentSlot() || !ManagedStorage(m_Source, item.GetParentSlot().GetStorage(), m_Donor)) return Fail("exact source magazine storage does not belong to original donor");
|
||||
m_Storage = m_Destination.FindStorageForItem(m_Magazine, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (m_Storage && !ManagedStorage(m_Destination, m_Storage, m_Recipient)) return Fail("recipient storage ownership unavailable");
|
||||
bool capacity = m_Storage && m_Source.CanMoveItemToStorage(m_Magazine, m_Storage, -1);
|
||||
if (!ERR_InfantryAmmoAccess.Clear(m_Donor, m_Recipient)) return Fail("no unobstructed physical reach to an authored donor storage access point");
|
||||
if (!capacity)
|
||||
{
|
||||
if (!m_ReturnDone && ReturnEmpty()) return ERR_InfantryAmmoTransferState.PENDING;
|
||||
return Fail("recipient has no native compatible available storage after at most one empty return");
|
||||
}
|
||||
if (m_LauncherMagazine)
|
||||
{
|
||||
m_OriginalSlot = item.GetParentSlot(); m_OriginalStorage = m_OriginalSlot.GetStorage();
|
||||
if (m_OriginalSlot.GetAttachedEntity() != m_Magazine) return Fail("launcher source slot lacks reciprocal item proof");
|
||||
}
|
||||
m_Callback = new ERR_InfantryAmmoCallback(); m_Callback.Transfer = this;
|
||||
m_Submitted = true;
|
||||
m_Accepted = m_Source.TryMoveItemToStorage(m_Magazine, m_Storage, -1, m_Callback);
|
||||
if (m_Acknowledgement == 1) m_Accepted = true;
|
||||
if (!m_Accepted) return Fail("native magazine move rejected; exact item will not be retried");
|
||||
}
|
||||
// Native callbacks may synchronously cancel this leaf or revoke ownership.
|
||||
if (m_LauncherMagazine && m_State != ERR_InfantryAmmoTransferState.PENDING) return m_State;
|
||||
if (m_LauncherMagazine && !Context()) return Fail("launcher custody authority changed during native submission");
|
||||
if (PhysicalProof())
|
||||
{
|
||||
m_State = ERR_InfantryAmmoTransferState.SUCCEEDED;
|
||||
m_Reason = "native callback and exact destination storage confirm one physical magazine transferred";
|
||||
return m_State;
|
||||
}
|
||||
if (m_Acknowledgement < 0) return Fail("native magazine move failed");
|
||||
return ERR_InfantryAmmoTransferState.PENDING;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"test_infantry_ammo_transfer.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "382b7a567d4a99fa6521160aac5d7eb332eeae20e14bf64eb603d59fdd0bb1a8",
|
||||
"log": "/tmp/erri-launcher-review-1130/test_infantry_ammo_transfer.py.log"
|
||||
},
|
||||
"test_infantry_resupply_planning.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "7bcb3461cc16b04a0247e928c1c831a3fb0944f48edcb9125cf7ef463a07168a",
|
||||
"log": "/tmp/erri-launcher-review-1130/test_infantry_resupply_planning.py.log"
|
||||
},
|
||||
"test_infantry_resupply_coordinator.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "f9cf7d583a25a12cec9f5f80539b2590449560331f4ef34e33ea2db283c5d54a",
|
||||
"log": "/tmp/erri-launcher-review-1130/test_infantry_resupply_coordinator.py.log"
|
||||
},
|
||||
"test_mounted_supply_integration.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "f52acf7a16c9d5273894eb5a4afe7657a7c97de2ed72ba06671d25e163eea5d7",
|
||||
"log": "/tmp/erri-launcher-review-1130/test_mounted_supply_integration.py.log"
|
||||
},
|
||||
"test_hq_multiconvoy_supply.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "e58d1d6eba48b2c5c6073d54c76302b2a7250ad22176fa94c386e1bc2ce65588",
|
||||
"log": "/tmp/erri-launcher-review-1130/test_hq_multiconvoy_supply.py.log"
|
||||
},
|
||||
"test_infantry_mine_coordinator.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "2fdb2138685a329cef11557973450bd5189d3d7d657dac5cc51b3df2530fab6b",
|
||||
"log": "/tmp/erri-launcher-review-1130/test_infantry_mine_coordinator.py.log"
|
||||
},
|
||||
"test_infantry_supply_pool.py": {
|
||||
"exit_code": 0,
|
||||
"test_sha256": "6fc0053337bbb889021c6eba608796ef946ff4d30a1b01cf678927293218d9d2",
|
||||
"log": "/tmp/erri-launcher-review-1130/test_infantry_supply_pool.py.log"
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
PASS: actual HQ rotates finite donors, skips unavailable/contacted trucks, uses delivered reports and dispatches at most one trip per planning cadence
|
||||
+1
@@ -0,0 +1 @@
|
||||
PASS: actual bounded empty-return and finite delivery; phase-isolated callbacks, exact ownership proof, stop quarantine and original actor/vehicle boundaries
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
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
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
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
|
||||
+1
@@ -0,0 +1 @@
|
||||
PASS: actual convoy binding, finite identities, disjoint tokens, shared report replay/cooldown, original recipients and HQ cleanup
|
||||
+1
@@ -0,0 +1 @@
|
||||
PASS: actual finite convoy supply pool; immutable slots, shared demand state, sparse/capped discovery and permanent shutdown
|
||||
+1
@@ -0,0 +1 @@
|
||||
PASS: actual shared mounted stock/history ownership, atomic exact HQ pair claim, and bounded stopped-owner proof polling
|
||||
@@ -0,0 +1 @@
|
||||
{"sha256": "670073db5d7531aca4466cc9d2827d10a46bfcfbd35f27ac97724de9d2fd3725"}
|
||||
@@ -0,0 +1,13 @@
|
||||
Finite launcher resupply proposal (no target writes/native runs)
|
||||
|
||||
Apply fix.patch; 4 production files and 8 existing regression programs. No check.py registration required. baseline.json captures before working/index SHA256; source-manifest.json freezes proposed files. apply-check.log records successful check against current target.
|
||||
|
||||
Delivered complete ammunition reports now carry LauncherMuzzles: supported positive reloadable magazine-well presence even when a consumed round no longer has an inventory identity. Snapshot copies it; Valid bounds it to Members*32. Demand needs fresh full-member launcher sampling, positive supported muzzles, zero aggregate rounds and zero chambers. Unsupported/disposable/non-reloadable configurations cannot establish demand. Partial sampling remains unknown. Rifle and mounted planning run first, then launcher, then mine supply.
|
||||
|
||||
Only original existing positive-ammo finite truck magazines are eligible, using exact native magazine-well match, immutable original carrier attribution, native inventory access/custody and shared reservations/spent/replay/cooldown. Availability scans at most96*32 actor/magazine pairs; native runtime performance is unmeasured. Loaded identity, when present, requires exact reciprocal muzzle parent slot; native null storage is supported. Empty muzzle is valid when supported well and both reloadability checks pass. Launcher trips do not return speculative empty fired-round entities. No stock creation or replenishment changed.
|
||||
|
||||
Submitted unknown launcher moves keep exact truck and recipient claims through Stop. Inline Stop or ownership revocation cannot become success after native return. Exact destination proof/deletion resolves retained uncertainty without resubmission/refund. A terminal negative callback also settles only when pinned original source/destination managers remain unchanged and the item is still exclusively in its exact reciprocal original managed source slot. Detached, foreign, duplicate, unknown or changed-manager evidence cannot settle refusal.
|
||||
|
||||
Eleven focused actual-method programs pass (final-results.json and final-*.log). Prior original-Compatible and original-HQ-loop negative controls compiled and failed runtime assertions (red-controls.json). Earlier failed C++ adapter logs are retained separately; final-* logs are corrected current-source outcomes. Independent final schema review is requested from hq_memory_review. Native engine, actual projectile firing lifetime, full-suite and multiplayer acceptance remain untested here.
|
||||
|
||||
Installed evidence is retained in sdk-review.json/sdk-files.json, source hashes sdk-evidence.json. RPG7/GP25/M203 authored round resources are Projectile entities WITH native MagazineComponent and wells, not generated/default-only guesses. RPG75 is explicitly disposable/nonreloadable. Exact fired-round deletion timing is unknown and is not assumed. Existing production IssueMagazines already requests each admitted cargo weapon muzzle's default stock; this patch changes none of that random initial availability.
|
||||
@@ -0,0 +1,16 @@
|
||||
from pathlib import Path
|
||||
P=Path('/tmp/erri-launcher-resupply-1140');R=Path('/home/jakub/devel/escapistsrereintroduced')
|
||||
for name in ['test_mounted_supply_integration.py','test_hq_multiconvoy_supply.py']:
|
||||
src=R/'tests'/name;s=src.read_text();(P/'baseline/tests'/name).write_text(s)
|
||||
if name=='test_mounted_supply_integration.py':s=s.replace('struct Service{HQ*','struct Service{bool m_LauncherTrip=false;HQ*')
|
||||
else:
|
||||
s=s.replace("'mineRecipient','m_Watch'", "'mineRecipient','launcherRecipient','m_Watch'")
|
||||
s=s.replace('bool infantry=true,', 'int launcherCalls=0,launcherAccepted=0;bool launcher=false;bool infantry=true,')
|
||||
s=s.replace(' bool TryPlanMines(',''' bool TryPlanLauncher(Unit*r,Unit*,Entry*d,Entry*s,float){launcherCalls++;if(!launcher||r!=recipient||d!=demand||s!=donorReport||!d||!s)return false;launcherAccepted++;state=2;return true;}
|
||||
bool TryPlanMines(''')
|
||||
s=s.replace('int main(){','''int main(){
|
||||
{Fixture f;f.first.infantry=false;f.first.launcher=true;f.first.mines=true;f.hq.PlanResupply(100);assert(f.first.launcherAccepted==1&&f.first.mineCalls==0&&f.second.calls==0);}
|
||||
{Fixture f;f.first.launcher=true;f.hq.PlanResupply(100);assert(f.first.accepted==1&&f.first.launcherCalls==0);}
|
||||
{Fixture f;f.first.infantry=false;f.first.mounted=true;f.first.launcher=true;f.hq.PlanResupply(100);assert(f.first.mountedAccepted==1&&f.first.launcherCalls==0);}
|
||||
''')
|
||||
(P/'tests'/name).write_text(s)
|
||||
@@ -0,0 +1,268 @@
|
||||
enum ERR_InfantryAmmoTransferState { FAILED = -1, PENDING, SUCCEEDED }
|
||||
|
||||
class ERR_InfantryAmmoCallback : ScriptedInventoryOperationCallback
|
||||
{
|
||||
ERR_InfantryAmmoTransfer Transfer;
|
||||
bool Returning;
|
||||
protected override void OnComplete() { if (Transfer) Transfer.Acknowledge(true, Returning); }
|
||||
protected override void OnFailed() { if (Transfer) Transfer.Acknowledge(false, Returning); }
|
||||
protected override void OnAllComplete() { if (Transfer) Transfer.Acknowledge(true, Returning); }
|
||||
protected override void OnAllFailed() { if (Transfer) Transfer.Acknowledge(false, Returning); }
|
||||
}
|
||||
|
||||
// One exact physical magazine movement. The caller owns HQ authorization and
|
||||
// the spent-item reservation; this leaf never selects tasks or retries a move.
|
||||
class ERR_InfantryAmmoTransfer
|
||||
{
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected Vehicle m_Donor;
|
||||
protected SCR_AIGroup m_DonorGroup;
|
||||
protected IEntity m_Recipient;
|
||||
protected SCR_AIGroup m_RecipientGroup;
|
||||
protected IEntity m_Magazine;
|
||||
protected IEntity m_Driver;
|
||||
protected ERR_ForceRecord m_DonorRecord;
|
||||
protected ERR_ForceRecord m_RecipientRecord;
|
||||
protected InventoryStorageManagerComponent m_Source;
|
||||
protected InventoryStorageManagerComponent m_Destination;
|
||||
protected BaseInventoryStorageComponent m_Storage;
|
||||
protected ref ERR_InfantryAmmoCallback m_Callback;
|
||||
protected bool m_RecycleEmpty;
|
||||
protected bool m_LauncherMagazine;
|
||||
protected IEntity m_ReturnMagazine;
|
||||
protected BaseInventoryStorageComponent m_ReturnStorage;
|
||||
protected ref ERR_InfantryAmmoCallback m_ReturnCallback;
|
||||
protected bool m_ReturnSubmitted;
|
||||
protected bool m_ReturnAccepted;
|
||||
protected bool m_ReturnDone;
|
||||
protected int m_ReturnAcknowledgement;
|
||||
protected float m_PhaseStarted = -1;
|
||||
protected bool m_Submitted;
|
||||
protected bool m_Accepted;
|
||||
protected int m_Acknowledgement;
|
||||
protected float m_Started = -1;
|
||||
protected ERR_InfantryAmmoTransferState m_State = ERR_InfantryAmmoTransferState.PENDING;
|
||||
protected string m_Reason;
|
||||
void ERR_InfantryAmmoTransfer(ERR_GameMode mode, Vehicle donorVehicle, SCR_AIGroup donorGroup, IEntity recipientActor, SCR_AIGroup recipientGroup, IEntity exactMagazine, bool recycleEmpty = false, bool launcherMagazine = false)
|
||||
{
|
||||
m_RecycleEmpty = recycleEmpty; m_LauncherMagazine = launcherMagazine;
|
||||
m_Mode = mode; m_Donor = donorVehicle; m_DonorGroup = donorGroup;
|
||||
m_Recipient = recipientActor; m_RecipientGroup = recipientGroup; m_Magazine = exactMagazine;
|
||||
}
|
||||
static bool Compatible(IEntity actor, IEntity exactMagazine, bool launcherMagazine = false)
|
||||
{
|
||||
if (!actor || !exactMagazine) return false;
|
||||
BaseMagazineComponent magazine = BaseMagazineComponent.Cast(exactMagazine.FindComponent(BaseMagazineComponent));
|
||||
BaseWeaponManagerComponent manager = BaseWeaponManagerComponent.Cast(actor.FindComponent(BaseWeaponManagerComponent));
|
||||
if (!magazine || !manager || !magazine.GetMagazineWell() || magazine.GetAmmoCount() <= 0 || magazine.GetMaxAmmoCount() <= 0 || magazine.GetAmmoCount() > magazine.GetMaxAmmoCount()) return false;
|
||||
array<IEntity> weapons = {}; manager.GetWeaponsList(weapons);
|
||||
if (weapons.IsEmpty() || weapons.Count() > 8) return false;
|
||||
foreach (IEntity rifle : weapons)
|
||||
{
|
||||
if (!rifle) continue;
|
||||
BaseWeaponComponent weapon = BaseWeaponComponent.Cast(rifle.FindComponent(BaseWeaponComponent));
|
||||
if (!weapon || weapon.GetOwner() != rifle) continue;
|
||||
EWeaponType kind = weapon.GetWeaponType();
|
||||
if (kind != EWeaponType.WT_RIFLE && kind != EWeaponType.WT_SNIPERRIFLE && kind != EWeaponType.WT_MACHINEGUN && kind != EWeaponType.WT_HANDGUN) continue;
|
||||
array<BaseMuzzleComponent> muzzles = {}; weapon.GetMuzzlesList(muzzles);
|
||||
if (muzzles.Count() > 4) continue;
|
||||
foreach (BaseMuzzleComponent muzzle : muzzles)
|
||||
{
|
||||
if (!muzzle || !muzzle.GetMagazineWell()) continue;
|
||||
EMuzzleType muzzleKind = muzzle.GetMuzzleType();
|
||||
if (muzzleKind != EMuzzleType.MT_BaseMuzzle) continue;
|
||||
// Same direction as the stock magazine inventory predicate: a magazine's
|
||||
// derived well must inherit the actual carried small-arms muzzle's well.
|
||||
if (magazine.GetMagazineWell().IsInherited(muzzle.GetMagazineWell().Type())) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static bool StoredUnder(IEntity entity, IEntity root)
|
||||
{
|
||||
for (int depth = 0; entity && depth < 16; depth++)
|
||||
{
|
||||
if (entity == root) return true;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent));
|
||||
if (item && item.GetParentSlot())
|
||||
{
|
||||
BaseInventoryStorageComponent storage = item.GetParentSlot().GetStorage();
|
||||
if (!storage) return false;
|
||||
entity = storage.GetOwner();
|
||||
}
|
||||
else entity = entity.GetParent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected bool ManagedStorage(InventoryStorageManagerComponent inventory, BaseInventoryStorageComponent storage, IEntity owner)
|
||||
{
|
||||
if (!inventory || !storage || inventory.GetOwner() != owner || !StoredUnder(storage.GetOwner(), owner)) return false;
|
||||
array<BaseInventoryStorageComponent> storages = {};
|
||||
inventory.GetStorages(storages, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
return storages.Count() <= 64 && storages.Contains(storage);
|
||||
}
|
||||
protected int Ownership(InventoryStorageManagerComponent inventory, IEntity exact = null)
|
||||
{
|
||||
if (!exact) exact = m_Magazine;
|
||||
if (!inventory || !exact) return -1;
|
||||
array<IEntity> items = {}; inventory.GetItems(items, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (items.Count() > 512) return -1;
|
||||
if (items.Contains(exact)) return 1;
|
||||
return 0;
|
||||
}
|
||||
protected bool Proof(IEntity exact, InventoryStorageManagerComponent source, InventoryStorageManagerComponent destination, BaseInventoryStorageComponent storage, IEntity owner, int acknowledgement)
|
||||
{
|
||||
if (!exact || acknowledgement != 1 || Ownership(source, exact) != 0 || Ownership(destination, exact) != 1 || !ManagedStorage(destination, storage, owner)) return false;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(exact.FindComponent(InventoryItemComponent));
|
||||
return item && item.GetParentSlot() && item.GetParentSlot().GetStorage() == storage;
|
||||
}
|
||||
protected bool PhysicalProof()
|
||||
{
|
||||
return Proof(m_Magazine, m_Source, m_Destination, m_Storage, m_Recipient, m_Acknowledgement);
|
||||
}
|
||||
protected bool ReturnProof()
|
||||
{
|
||||
return Proof(m_ReturnMagazine, m_Destination, m_Source, m_ReturnStorage, m_Donor, m_ReturnAcknowledgement);
|
||||
}
|
||||
void Acknowledge(bool success, bool returning = false)
|
||||
{
|
||||
if (returning)
|
||||
{
|
||||
if (m_ReturnAcknowledgement == 0)
|
||||
{
|
||||
if (success) m_ReturnAcknowledgement = 1;
|
||||
else m_ReturnAcknowledgement = -1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (m_Acknowledgement != 0) return;
|
||||
if (success) m_Acknowledgement = 1;
|
||||
else m_Acknowledgement = -1;
|
||||
}
|
||||
// Both native operations remain observable after Stop. Returning an empty
|
||||
// magazine never proves delivery or grants authority for a later full one.
|
||||
bool Unresolved()
|
||||
{
|
||||
if ((m_ReturnAccepted || (m_LauncherMagazine && m_ReturnSubmitted)) && !m_ReturnDone && m_ReturnMagazine && m_ReturnAcknowledgement >= 0 && !ReturnProof()) return true;
|
||||
if (m_LauncherMagazine && m_Submitted) return m_Magazine && !PhysicalProof();
|
||||
if (!m_Accepted || !m_Magazine || m_Acknowledgement < 0) return false;
|
||||
return !PhysicalProof();
|
||||
}
|
||||
// Any native submission consumes this incoming identity at the coordinator,
|
||||
// even when only the empty return was attempted and delivery never started.
|
||||
bool Submitted() { return m_Submitted || m_ReturnSubmitted; }
|
||||
string Reason() { return m_Reason; }
|
||||
protected ERR_InfantryAmmoTransferState Fail(string reason)
|
||||
{
|
||||
if (m_State == ERR_InfantryAmmoTransferState.PENDING) { m_State = ERR_InfantryAmmoTransferState.FAILED; m_Reason = reason; }
|
||||
return m_State;
|
||||
}
|
||||
void Stop() { Fail("HQ authorization stopped; an accepted native move cannot be cancelled"); }
|
||||
protected bool Context()
|
||||
{
|
||||
if (!Replication.IsServer() || SCR_Global.IsEditMode() || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active() || !m_Mode.Forces()) return false;
|
||||
if (!m_Donor || !ERR_Actors.Alive(m_Donor) || !m_DonorGroup || !m_RecipientGroup || m_DonorGroup == m_RecipientGroup) return false;
|
||||
if (!ERR_EnemyEquipmentRules.Eligible(m_Mode, m_RecipientGroup, m_Recipient) || CompartmentAccessComponent.GetVehicleIn(m_Recipient)) return false;
|
||||
if (m_DonorGroup.IsSlave() || m_RecipientGroup.IsSlave() || m_DonorGroup.GetPlayerCount(true) > 0 || m_DonorGroup.GetFaction() != m_Mode.EnemyFaction()) return false;
|
||||
if (SCR_FactionManager.SGetFaction(m_Donor) != m_Mode.EnemyFaction() || SCR_FactionManager.SGetFaction(m_Recipient) != m_Mode.EnemyFaction()) return false;
|
||||
ERR_ForceRecord donorRecord = m_Mode.Forces().FindGroup(m_DonorGroup);
|
||||
ERR_ForceRecord recipientRecord = m_Mode.Forces().FindGroup(m_RecipientGroup);
|
||||
if (!donorRecord || !recipientRecord || donorRecord.Group != m_DonorGroup || recipientRecord.Group != m_RecipientGroup || !donorRecord.Physical || !recipientRecord.Physical || donorRecord.Eliminated || recipientRecord.Eliminated) return false;
|
||||
if (m_DonorRecord && (m_DonorRecord != donorRecord || m_RecipientRecord != recipientRecord)) return false;
|
||||
SCR_AIGroupUtilityComponent utility = m_DonorGroup.GetGroupUtilityComponent();
|
||||
if (!utility || !utility.IsMilitary() || !utility.m_VehicleMgr || !utility.m_VehicleMgr.FindVehicle(m_Donor)) return false;
|
||||
if (ERR_ConvoyRules.Captured(m_Donor, m_DonorGroup)) return false;
|
||||
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(m_Donor.FindComponent(SCR_AIVehicleUsageComponent));
|
||||
if (!usage || !usage.GetPilotCompartmentSlot()) return false;
|
||||
IEntity driver = usage.GetPilotCompartmentSlot().GetOccupant();
|
||||
if (!ERR_EnemyEquipmentRules.Eligible(m_Mode, m_DonorGroup, driver) || !ERR_ConvoyRules.PartOf(CompartmentAccessComponent.GetVehicleIn(driver), m_Donor)) return false;
|
||||
if (SCR_FactionManager.SGetFaction(driver) != m_Mode.EnemyFaction() || (m_Driver && driver != m_Driver)) return false;
|
||||
Physics physics = m_Donor.GetPhysics();
|
||||
if (!physics) return false;
|
||||
float speed = physics.GetVelocity().Length();
|
||||
float angular = physics.GetAngularVelocity().Length();
|
||||
float distance = vector.Distance(m_Donor.GetOrigin(), m_Recipient.GetOrigin());
|
||||
if (!(speed >= 0 && speed <= 0.25 && angular >= 0 && angular <= 0.1 && distance >= 0 && distance <= 15)) return false;
|
||||
if (distance > 4 && !m_Donor.FindComponent(VehicleHelicopterSimulation)) return false;
|
||||
if (!Compatible(m_Recipient, m_Magazine, m_LauncherMagazine)) return false;
|
||||
m_Driver = driver; m_DonorRecord = donorRecord; m_RecipientRecord = recipientRecord;
|
||||
return true;
|
||||
}
|
||||
// One optional capacity recovery, using only an unused exact empty identity.
|
||||
protected bool ReturnEmpty()
|
||||
{
|
||||
if (!m_RecycleEmpty || m_ReturnSubmitted || m_ReturnMagazine) return false;
|
||||
BaseMagazineComponent incoming = BaseMagazineComponent.Cast(m_Magazine.FindComponent(BaseMagazineComponent));
|
||||
if (!incoming || !incoming.GetMagazineWell()) return false;
|
||||
array<IEntity> items = {}; m_Destination.GetItems(items, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (items.Count() > 128) return false;
|
||||
foreach (IEntity candidate : items)
|
||||
{
|
||||
if (!candidate || candidate == m_Magazine) continue;
|
||||
BaseMagazineComponent empty = BaseMagazineComponent.Cast(candidate.FindComponent(BaseMagazineComponent));
|
||||
if (!empty || empty.IsUsed() || empty.GetAmmoCount() != 0 || empty.GetMaxAmmoCount() <= 0
|
||||
|| !empty.GetMagazineWell() || empty.GetMagazineWell().Type() != incoming.GetMagazineWell().Type()) continue;
|
||||
if (Ownership(m_Destination, candidate) != 1 || Ownership(m_Source, candidate) != 0) continue;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(candidate.FindComponent(InventoryItemComponent));
|
||||
if (!item || !item.GetParentSlot() || !ManagedStorage(m_Destination, item.GetParentSlot().GetStorage(), m_Recipient)) continue;
|
||||
BaseInventoryStorageComponent storage = m_Source.FindStorageForItem(candidate, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (!ManagedStorage(m_Source, storage, m_Donor) || !m_Destination.CanMoveItemToStorage(candidate, storage, -1)) continue;
|
||||
m_ReturnMagazine = candidate; m_ReturnStorage = storage;
|
||||
m_ReturnCallback = new ERR_InfantryAmmoCallback(); m_ReturnCallback.Transfer = this; m_ReturnCallback.Returning = true;
|
||||
m_ReturnSubmitted = true;
|
||||
m_ReturnAccepted = m_Destination.TryMoveItemToStorage(candidate, storage, -1, m_ReturnCallback);
|
||||
if (m_ReturnAcknowledgement == 1) m_ReturnAccepted = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
ERR_InfantryAmmoTransferState Tick(float now)
|
||||
{
|
||||
if (m_State != ERR_InfantryAmmoTransferState.PENDING) return m_State;
|
||||
if (!ERR_ForceRules.Finite(now) || now < 0) return Fail("invalid transaction clock");
|
||||
if (m_Started < 0) { m_Started = now; m_PhaseStarted = now; }
|
||||
if (now < m_Started || now < m_PhaseStarted || now - m_Started >= 16000 || now - m_PhaseStarted >= 8000) return Fail("native magazine move deadline expired");
|
||||
if (!Context()) return Fail("original owned groups, vehicle, driver, recipient, proximity or compatible weapon unavailable");
|
||||
if (m_ReturnSubmitted && !m_ReturnDone)
|
||||
{
|
||||
if (!m_ReturnAccepted || m_ReturnAcknowledgement < 0) return Fail("native empty return rejected; neither identity will be retried");
|
||||
if (!ReturnProof()) return ERR_InfantryAmmoTransferState.PENDING;
|
||||
m_ReturnDone = true; m_PhaseStarted = now;
|
||||
// Separate slow tick allows loss of HQ authority to stop before delivery.
|
||||
return ERR_InfantryAmmoTransferState.PENDING;
|
||||
}
|
||||
if (!m_Submitted)
|
||||
{
|
||||
m_Source = InventoryStorageManagerComponent.Cast(m_Donor.FindComponent(InventoryStorageManagerComponent));
|
||||
m_Destination = InventoryStorageManagerComponent.Cast(m_Recipient.FindComponent(InventoryStorageManagerComponent));
|
||||
if (!m_Source || !m_Destination || m_Source == m_Destination || Ownership(m_Source) != 1 || Ownership(m_Destination) != 0) return Fail("exact magazine is not exclusive donor stock");
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(m_Magazine.FindComponent(InventoryItemComponent));
|
||||
if (!item || !item.GetParentSlot() || !ManagedStorage(m_Source, item.GetParentSlot().GetStorage(), m_Donor)) return Fail("exact source magazine storage does not belong to original donor");
|
||||
m_Storage = m_Destination.FindStorageForItem(m_Magazine, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (m_Storage && !ManagedStorage(m_Destination, m_Storage, m_Recipient)) return Fail("recipient storage ownership unavailable");
|
||||
bool capacity = m_Storage && m_Source.CanMoveItemToStorage(m_Magazine, m_Storage, -1);
|
||||
if (!ERR_InfantryAmmoAccess.Clear(m_Donor, m_Recipient)) return Fail("no unobstructed physical reach to an authored donor storage access point");
|
||||
if (!capacity)
|
||||
{
|
||||
if (!m_ReturnDone && ReturnEmpty()) return ERR_InfantryAmmoTransferState.PENDING;
|
||||
return Fail("recipient has no native compatible available storage after at most one empty return");
|
||||
}
|
||||
m_Callback = new ERR_InfantryAmmoCallback(); m_Callback.Transfer = this;
|
||||
m_Submitted = true;
|
||||
m_Accepted = m_Source.TryMoveItemToStorage(m_Magazine, m_Storage, -1, m_Callback);
|
||||
if (m_Acknowledgement == 1) m_Accepted = true;
|
||||
if (!m_Accepted) return Fail("native magazine move rejected; exact item will not be retried");
|
||||
}
|
||||
// Native callbacks may synchronously cancel this leaf or revoke ownership.
|
||||
if (m_LauncherMagazine && m_State != ERR_InfantryAmmoTransferState.PENDING) return m_State;
|
||||
if (m_LauncherMagazine && !Context()) return Fail("launcher custody authority changed during native submission");
|
||||
if (PhysicalProof())
|
||||
{
|
||||
m_State = ERR_InfantryAmmoTransferState.SUCCEEDED;
|
||||
m_Reason = "native callback and exact destination storage confirm one physical magazine transferred";
|
||||
return m_State;
|
||||
}
|
||||
if (m_Acknowledgement < 0) return Fail("native magazine move failed");
|
||||
return ERR_InfantryAmmoTransferState.PENDING;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c": {
|
||||
"working_sha256": "966068555a6724e620a2c377b98d8dc02f141466c5cdd44636f86cb1cfe9ac78",
|
||||
"index_sha256": "966068555a6724e620a2c377b98d8dc02f141466c5cdd44636f86cb1cfe9ac78"
|
||||
},
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoTransfer.c": {
|
||||
"working_sha256": "401a29ec31712d113c203d6975e44aed7bb3166025454f4950a4c7cb6f5ad6eb",
|
||||
"index_sha256": "401a29ec31712d113c203d6975e44aed7bb3166025454f4950a4c7cb6f5ad6eb"
|
||||
},
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c": {
|
||||
"working_sha256": "1f129b979abcb86ee10622e2866aa987807f070f2ab2d818af015e12a8bcfd8c",
|
||||
"index_sha256": "1f129b979abcb86ee10622e2866aa987807f070f2ab2d818af015e12a8bcfd8c"
|
||||
},
|
||||
"tests/test_hq_multiconvoy_supply.py": {
|
||||
"working_sha256": "c96a941e75673a2c95adee83b57a84d3d8e2af772e96b197f85d78a90d469a2e",
|
||||
"index_sha256": "c96a941e75673a2c95adee83b57a84d3d8e2af772e96b197f85d78a90d469a2e"
|
||||
},
|
||||
"tests/test_mounted_supply_integration.py": {
|
||||
"working_sha256": "2f365cd9918bf39457fab046b8e046392f7b35e2b2eff45a0d2ed7d6ffee956a",
|
||||
"index_sha256": "2f365cd9918bf39457fab046b8e046392f7b35e2b2eff45a0d2ed7d6ffee956a"
|
||||
},
|
||||
"tests/test_infantry_resupply_coordinator.py": {
|
||||
"working_sha256": "65fa119778289175766ba7fdecd9e5685199736dffecc32056692121cbc6a682",
|
||||
"index_sha256": "65fa119778289175766ba7fdecd9e5685199736dffecc32056692121cbc6a682"
|
||||
},
|
||||
"tests/test_infantry_resupply_planning.py": {
|
||||
"working_sha256": "211e3d0ee20242f271afc46885e1ba7845ff2406d663bfe5118f40578cd5d709",
|
||||
"index_sha256": "211e3d0ee20242f271afc46885e1ba7845ff2406d663bfe5118f40578cd5d709"
|
||||
},
|
||||
"tests/test_infantry_ammo_transfer.py": {
|
||||
"working_sha256": "e1172dda6350d2f8b89166ba63995cd1590435930ba220cd38b022307103a306",
|
||||
"index_sha256": "e1172dda6350d2f8b89166ba63995cd1590435930ba220cd38b022307103a306"
|
||||
},
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQAmmoSampler.c": {
|
||||
"working_sha256": "258ad63c9dbf221f2d8402e93feb7b4d51897417954abf477004b5302e819bc3",
|
||||
"index_sha256": "258ad63c9dbf221f2d8402e93feb7b4d51897417954abf477004b5302e819bc3"
|
||||
},
|
||||
"tests/test_hq_watch.py": {
|
||||
"working_sha256": "5bf0debdeecb8f7fff6be94cc3772acfa948b839771d309291b1720b249b2890",
|
||||
"index_sha256": "5bf0debdeecb8f7fff6be94cc3772acfa948b839771d309291b1720b249b2890"
|
||||
},
|
||||
"tests/test_hq_ammo_reporting.py": {
|
||||
"working_sha256": "7cdf673d137a5c016630dd61d21d29f860469519c56303a12e19ee9630e1664f",
|
||||
"index_sha256": "7cdf673d137a5c016630dd61d21d29f860469519c56303a12e19ee9630e1664f"
|
||||
},
|
||||
"tests/test_hq_ammo_sampler.py": {
|
||||
"working_sha256": "4f59b80291ec6f636a0ba5f748689f2664d3a1e44e74c2f709fb73c8019c7572",
|
||||
"index_sha256": "4f59b80291ec6f636a0ba5f748689f2664d3a1e44e74c2f709fb73c8019c7572"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
"""Execute the actual finite native magazine transaction; engine behavior remains separate."""
|
||||
from pathlib import Path
|
||||
import os,re
|
||||
from policy_harness import ROOT,run,body
|
||||
from test_finite_magazine_reservation import translated_reservation
|
||||
p=Path(os.environ.get('ERR_INFANTRY_TRANSFER_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoTransfer.c'))
|
||||
s=p.read_text()
|
||||
for forbidden in ('TrySpawn','DeleteEntity','SetOrigin','SetTransform','TryInsert','TryRemove','SetAmmoCount'):
|
||||
assert forbidden not in s,forbidden
|
||||
callback=s[s.index('class ERR_InfantryAmmoCallback'):s.index('// One exact physical')]
|
||||
code=s[s.index('class ERR_InfantryAmmoTransfer\n'):]
|
||||
code=re.sub(r'//[^\n]*','',code).replace('protected ','').replace('class ','struct ')
|
||||
for src,dst in {
|
||||
'm_Donor.FindComponent(VehicleHelicopterSimulation)':'m_Donor->helicopter',
|
||||
'InventoryItemComponent.Cast(loaded.GetOwner().FindComponent(InventoryItemComponent))':'loaded->GetOwner()->item',
|
||||
'BaseMagazineComponent.Cast(exactMagazine.FindComponent(BaseMagazineComponent))':'exactMagazine->magazine',
|
||||
'BaseWeaponManagerComponent.Cast(actor.FindComponent(BaseWeaponManagerComponent))':'actor->weapons',
|
||||
'BaseWeaponComponent.Cast(rifle.FindComponent(BaseWeaponComponent))':'rifle->weapon',
|
||||
'InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent))':'entity->item',
|
||||
'InventoryItemComponent.Cast(m_Magazine.FindComponent(InventoryItemComponent))':'m_Magazine->item',
|
||||
'InventoryItemComponent.Cast(exact.FindComponent(InventoryItemComponent))':'exact->item',
|
||||
'InventoryItemComponent.Cast(candidate.FindComponent(InventoryItemComponent))':'candidate->item',
|
||||
'BaseMagazineComponent.Cast(m_Magazine.FindComponent(BaseMagazineComponent))':'m_Magazine->magazine',
|
||||
'BaseMagazineComponent.Cast(candidate.FindComponent(BaseMagazineComponent))':'candidate->magazine',
|
||||
'SCR_AIVehicleUsageComponent.Cast(m_Donor.FindComponent(SCR_AIVehicleUsageComponent))':'m_Donor->usage',
|
||||
'InventoryStorageManagerComponent.Cast(m_Donor.FindComponent(InventoryStorageManagerComponent))':'m_Donor->inventory',
|
||||
'InventoryStorageManagerComponent.Cast(m_Recipient.FindComponent(InventoryStorageManagerComponent))':'m_Recipient->inventory',
|
||||
}.items():code=code.replace(src,dst)
|
||||
code=code.replace('SCR_MuzzleInMagComponent.Cast(muzzle)', '(muzzle->inMag ? muzzle : null)')
|
||||
code=code.replace('ref ERR_InfantryAmmoCallback','ERR_InfantryAmmoCallback*').replace('array<','Array<').replace('foreach (','for (').replace('null','nullptr')
|
||||
code=re.sub(r'\bERR_GameMode (?=\w)','ERR_GameMode* ',code)
|
||||
code=code.replace('void ERR_InfantryAmmoTransfer(','ERR_InfantryAmmoTransfer(').replace('EWeaponType kind','int kind').replace('EMuzzleType muzzleKind','int muzzleKind')
|
||||
for name in ('ERR_InfantryAmmoTransferState','ERR_InfantryAmmoAccess','ERR_GameMode','ERR_EnemyEquipmentRules','ERR_ForceRules','Replication','SCR_Global','ERR_Actors','SCR_FactionManager','ERR_ConvoyRules','CompartmentAccessComponent','EWeaponType','EMuzzleType','EStoragePurpose','vector'):
|
||||
code=code.replace(name+'.',name+'::')
|
||||
for name in ('m_Mode','m_Donor','m_DonorGroup','m_Recipient','m_RecipientGroup','m_Source','m_Destination','m_Storage','m_Callback','magazine','reloadable','loaded','loadedItem','loadedSlot','manager','weapon','muzzle','entity','item','storage','inventory','incoming','empty','source','destination','m_ReturnCallback','donorRecord','recipientRecord','utility','usage','physics'):
|
||||
code=re.sub(r'\b'+name+r'\.',name+'->',code)
|
||||
code=code.replace('GetMagazineWell().','GetMagazineWell()->').replace('GetParentSlot().','GetParentSlot()->').replace('Forces().','Forces()->').replace('m_VehicleMgr.','m_VehicleMgr->').replace('GetPilotCompartmentSlot().','GetPilotCompartmentSlot()->')
|
||||
code=re.sub(r'\b(bool|int|Vehicle|IEntity|SCR_AIGroup|ERR_ForceRecord|InventoryStorageManagerComponent|BaseInventoryStorageComponent|ERR_GameMode\*|ERR_InfantryAmmoCallback\*) (\w+);',r'\1 \2{};',code)
|
||||
code=code.replace('\n}\n','\n};\n')
|
||||
callback=callback.replace('class ERR_InfantryAmmoCallback : ScriptedInventoryOperationCallback','').strip()[1:-1]
|
||||
callbacks=[]
|
||||
for name in ('OnComplete','OnFailed','OnAllComplete','OnAllFailed'):
|
||||
m=re.search(r'protected override void '+name+r'\(\) (\{[^\n]+\})',callback)
|
||||
callbacks.append('void ERR_InfantryAmmoCallback::'+name+'() '+m[1].replace('Transfer.','Transfer->'))
|
||||
# Integrate the actual coordinator transfer selector with this actual leaf.
|
||||
coordinator_path=Path(os.environ.get('ERR_INFANTRY_RESUPPLY_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c'))
|
||||
transfer=body(coordinator_path,'protected void Transfer(float now)').replace('protected ','').replace('null','nullptr').replace('foreach (','for (')
|
||||
transfer=transfer.replace('ERR_HQAmmoSnapshot local','ERR_HQAmmoSnapshot* local')
|
||||
for name in ('ERR_Actors','ERR_HQAmmoSampler','ERR_InfantryAmmoTransfer','vector'):
|
||||
transfer=transfer.replace(name+'.',name+'::')
|
||||
transfer=transfer.replace('ERR_FiniteMagazineReservation reservation', 'ERR_FiniteMagazineReservation* reservation').replace('new ERR_FiniteMagazineReservation(m_Stock, m_Spent,', 'new ERR_FiniteMagazineReservation(&m_Stock, &m_Spent,')
|
||||
for name in ('m_Move','actor','m_Truck','m_Recipient','local','reservation'):
|
||||
transfer=transfer.replace(name+'.',name+'->')
|
||||
transfer=transfer.replace('Entity.GetOrigin()', 'Entity->GetOrigin()')
|
||||
pre=r'''
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <functional>
|
||||
using string=std::string;
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}bool IsEmpty(){return this->empty();}void Insert(T t){this->push_back(t);}void RemoveItem(T t){this->erase(std::remove(this->begin(),this->end(),t),this->end());}bool Contains(T t){return std::find(this->begin(),this->end(),t)!=this->end();}};
|
||||
struct vector{float x=0,y=0,z=0;float Length(){return std::sqrt(x*x+y*y+z*z);}static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}static float Distance(vector a,vector b){return vector{a.x-b.x,a.y-b.y,a.z-b.z}.Length();}};
|
||||
struct Entity;struct Group;struct Inventory;struct Storage;struct Item;struct Slot;struct Magazine;struct Weapon;struct Muzzle;struct Manager;struct Utility;struct Usage;struct Physics_;struct Record;
|
||||
using IEntity=Entity*;using Vehicle=Entity*;using SCR_AIGroup=Group*;using ERR_ForceRecord=Record*;
|
||||
using InventoryStorageSlot=Slot*;using InventoryStorageManagerComponent=Inventory*;using BaseInventoryStorageComponent=Storage*;using InventoryItemComponent=Item*;using BaseMagazineComponent=Magazine*;
|
||||
using BaseWeaponManagerComponent=Manager*;using BaseWeaponComponent=Weapon*;using BaseMuzzleComponent=Muzzle*;using SCR_MuzzleInMagComponent=Muzzle*;
|
||||
using SCR_AIGroupUtilityComponent=Utility*;using SCR_AIVehicleUsageComponent=Usage*;using Physics=Physics_*;
|
||||
enum ERR_InfantryAmmoTransferState{FAILED=-1,PENDING,SUCCEEDED};
|
||||
struct ERR_InfantryAmmoTransfer;
|
||||
struct ScriptedInventoryOperationCallback{virtual void OnComplete(){}virtual void OnFailed(){}virtual void OnAllComplete(){}virtual void OnAllFailed(){}virtual ~ScriptedInventoryOperationCallback()=default;};
|
||||
struct ERR_InfantryAmmoCallback:ScriptedInventoryOperationCallback{ERR_InfantryAmmoTransfer*Transfer=nullptr;bool Returning=false;void OnComplete()override;void OnFailed()override;void OnAllComplete()override;void OnAllFailed()override;};
|
||||
struct EWeaponType{static const int WT_RIFLE=1,WT_SNIPERRIFLE=2,WT_MACHINEGUN=3,WT_HANDGUN=4,WT_GRENADELAUNCHER=5,WT_ROCKETLAUNCHER=6;};
|
||||
struct EMuzzleType{static const int MT_BaseMuzzle=0,MT_UGLMuzzle=1,MT_RPGMuzzle=2;};struct EStoragePurpose{static const int PURPOSE_DEPOSIT=1;};
|
||||
struct Well{int type=1,parent=0;int Type(){return type;}bool IsInherited(int t){return type==t||parent==t;}};
|
||||
struct Magazine{IEntity owner=nullptr;IEntity GetOwner(){return owner;}Well*well=nullptr;int rounds=30,capacity=30;bool used=false;bool IsUsed(){return used;}Well*GetMagazineWell(){return well;}int GetAmmoCount(){return rounds;}int GetMaxAmmoCount(){return capacity;}};
|
||||
struct Muzzle{bool inMag=false,canReload=true;bool CanBeReloaded(){return canReload;}bool disposable=false;bool IsDisposable(){return disposable;}Magazine*loaded=nullptr;Magazine*GetMagazine(){return loaded;}Well*well=nullptr;int kind=0;Well*GetMagazineWell(){return well;}int GetMuzzleType(){return kind;}};
|
||||
struct Weapon{IEntity owner=nullptr;Array<Muzzle*>muzzles;int kind=1;IEntity GetOwner(){return owner;}int GetWeaponType(){return kind;}void GetMuzzlesList(Array<Muzzle*>&a){a=muzzles;}};
|
||||
struct Manager{Array<IEntity>weapons;void GetWeaponsList(Array<IEntity>&a){a=weapons;}};
|
||||
struct Storage{IEntity owner=nullptr;IEntity GetOwner(){return owner;}};
|
||||
struct Slot{Storage*storage=nullptr;IEntity occupant=nullptr;Muzzle*container=nullptr;IEntity attached=nullptr;Muzzle*GetParentContainer(){return container;}IEntity GetAttachedEntity(){return attached;}Storage*GetStorage(){return storage;}IEntity GetOccupant(){return occupant;}};
|
||||
struct Item{Slot*slot=nullptr;Slot*GetParentSlot(){return slot;}};
|
||||
struct Physics_{vector velocity,angular;vector GetVelocity(){return velocity;}vector GetAngularVelocity(){return angular;}};
|
||||
struct Usage{Slot*pilot=nullptr;Slot*GetPilotCompartmentSlot(){return pilot;}};
|
||||
struct VehicleManager{IEntity vehicle=nullptr;IEntity FindVehicle(IEntity v){return vehicle==v?v:nullptr;}};
|
||||
struct Utility{VehicleManager*m_VehicleMgr=nullptr;bool military=true;bool IsMilitary(){return military;}};
|
||||
struct Group{int faction=1,players=0;bool slave=false;Utility*utility=nullptr;int GetPlayerCount(bool){return players;}int GetFaction(){return faction;}bool IsSlave(){return slave;}Utility*GetGroupUtilityComponent(){return utility;}};
|
||||
struct Entity{bool helicopter=false;bool alive=true,awake=true,player=false,captured=false;int faction=1;Group*group=nullptr;IEntity vehicle=nullptr,parent=nullptr;
|
||||
Inventory*inventory=nullptr;Magazine*magazine=nullptr;Manager*weapons=nullptr;Weapon*weapon=nullptr;Item*item=nullptr;Usage*usage=nullptr;Physics_*physics=nullptr;vector position;
|
||||
IEntity GetParent(){return parent;}Physics_*GetPhysics(){return physics;}vector GetOrigin(){return position;}};
|
||||
struct Record{SCR_AIGroup Group=nullptr;bool Physical=true,Eliminated=false;};
|
||||
struct Ledger{Array<Record*>records;Record*FindGroup(SCR_AIGroup g){for(auto*r:records)if(r->Group==g)return r;return nullptr;}};
|
||||
struct ERR_GameMode{inline static ERR_GameMode*current=nullptr;bool active=true;Ledger*ledger=nullptr;static ERR_GameMode*Get(){return current;}bool Active(){return active;}Ledger*Forces(){return ledger;}int EnemyFaction(){return 1;}};
|
||||
struct Replication{inline static bool server=true;static bool IsServer(){return server;}};
|
||||
struct SCR_Global{inline static bool editor=false;static bool IsEditMode(){return editor;}};
|
||||
struct ERR_Actors{static bool CanFight(IEntity a){return a&&a->alive&&a->awake;}static bool Alive(IEntity a){return a&&a->alive;}};
|
||||
struct SCR_FactionManager{static int SGetFaction(IEntity a){return a?a->faction:-1;}};
|
||||
struct CompartmentAccessComponent{static IEntity GetVehicleIn(IEntity a){return a?a->vehicle:nullptr;}};
|
||||
struct ERR_ConvoyRules{static bool Captured(Vehicle v,SCR_AIGroup g){return v->captured||g->players>0;}static bool PartOf(IEntity a,IEntity root){return a==root;}};
|
||||
struct ERR_EnemyEquipmentRules{static bool Eligible(ERR_GameMode*m,SCR_AIGroup g,IEntity a){return m&&g&&a&&a->alive&&a->awake&&!a->player&&a->group==g&&g->players==0&&g->faction==m->EnemyFaction()&&g->utility&&g->utility->military;}};
|
||||
struct ERR_ForceRules{static bool Finite(float n){return std::isfinite(n);}};
|
||||
struct ERR_InfantryAmmoAccess{static inline bool clear=true;static inline int checks=0;static bool Clear(Vehicle donor,IEntity recipient){assert(donor&&recipient);checks++;return clear;}};
|
||||
struct Inventory{IEntity owner=nullptr;Array<IEntity>items;Array<Storage*>storages;Storage*selected=nullptr;bool allow=true,accept=true;int calls=0,callback=0;ScriptedInventoryOperationCallback*last=nullptr;std::function<void()> effect;
|
||||
IEntity GetOwner(){return owner;}void GetItems(Array<IEntity>&a,int purpose){assert(purpose==1);a=items;}void GetStorages(Array<Storage*>&a,int purpose){assert(purpose==1);a=storages;}
|
||||
Storage*FindStorageForItem(IEntity,int purpose){assert(purpose==1);return selected;}
|
||||
bool CanMoveItemToStorage(IEntity,Storage*,int slot){assert(slot==-1);return allow;}
|
||||
bool TryMoveItemToStorage(IEntity,Storage*,int slot,ScriptedInventoryOperationCallback*cb){assert(slot==-1);calls++;last=cb;if(effect)effect();if(callback==1){cb->OnComplete();}if(callback==2){cb->OnFailed();}return accept;}};
|
||||
'''
|
||||
main=r'''
|
||||
struct Fixture{ERR_GameMode mode;Ledger ledger;Group donorGroup,recipientGroup,otherGroup;Record donorRecord,recipientRecord;Entity truck,recipient,driver,spareDriver,magEntity,rifle,foreign;
|
||||
VehicleManager vehicleManager;Utility donorUtility,recipientUtility;Physics_ physics;Usage usage;Slot pilot,fromSlot,toSlot;Storage sourceStorage,targetStorage;Inventory source,destination;Item item;Magazine magazine;Manager weapons;Weapon weapon;Muzzle muzzle;Well well;
|
||||
Fixture(){ERR_GameMode::current=&mode;mode.ledger=&ledger;donorRecord.Group=&donorGroup;recipientRecord.Group=&recipientGroup;ledger.records={&donorRecord,&recipientRecord};
|
||||
donorGroup.utility=&donorUtility;recipientGroup.utility=&recipientUtility;donorUtility.m_VehicleMgr=&vehicleManager;vehicleManager.vehicle=&truck;
|
||||
truck.physics=&physics;truck.usage=&usage;usage.pilot=&pilot;pilot.occupant=&driver;driver.group=&donorGroup;driver.vehicle=&truck;spareDriver.group=&donorGroup;spareDriver.vehicle=&truck;
|
||||
recipient.group=&recipientGroup;recipient.position.x=4;truck.inventory=&source;source.owner=&truck;recipient.inventory=&destination;destination.owner=&recipient;
|
||||
sourceStorage.owner=&truck;targetStorage.owner=&recipient;source.storages={&sourceStorage};destination.storages={&targetStorage};destination.selected=&targetStorage;
|
||||
source.items={&magEntity};magEntity.item=&item;item.slot=&fromSlot;fromSlot.storage=&sourceStorage;toSlot.storage=&targetStorage;magEntity.magazine=&magazine;magazine.well=&well;
|
||||
recipient.weapons=&weapons;weapons.weapons={&rifle};rifle.weapon=&weapon;weapon.owner=&rifle;weapon.muzzles={&muzzle};muzzle.well=&well;}
|
||||
ERR_InfantryAmmoTransfer Job(){return ERR_InfantryAmmoTransfer(&mode,&truck,&donorGroup,&recipient,&recipientGroup,&magEntity,true);}
|
||||
void Move(){source.items.RemoveItem(&magEntity);destination.items.Insert(&magEntity);item.slot=&toSlot;}
|
||||
};
|
||||
struct EmptyFixture:Fixture{
|
||||
Entity empty,secondEmpty;Magazine emptyMag;Item emptyItem,secondItem;Slot emptyFrom,emptyTo;
|
||||
EmptyFixture(){emptyMag.well=&well;emptyMag.rounds=0;empty.magazine=&emptyMag;empty.item=&emptyItem;
|
||||
emptyFrom.storage=&targetStorage;emptyTo.storage=&sourceStorage;emptyItem.slot=&emptyFrom;
|
||||
secondEmpty.magazine=&emptyMag;secondEmpty.item=&secondItem;secondItem.slot=&emptyFrom;
|
||||
destination.items={&empty,&secondEmpty};destination.selected=nullptr;source.selected=&sourceStorage;}
|
||||
void Return(bool capacity=true){destination.items.RemoveItem(&empty);source.items.Insert(&empty);emptyItem.slot=&emptyTo;if(capacity)destination.selected=&targetStorage;}
|
||||
};
|
||||
struct ERR_HQAmmoSnapshot{bool Complete=true,LauncherComplete=true;int Members=1,LauncherMuzzles=1,LauncherMagazines=1,LauncherRounds=0,LauncherChambers=0;bool Valid(int n){return n==Members;}int SmallArmsMuzzles=1,SmallArmsRounds=0,SmallArmsChambers=0;};
|
||||
struct ERR_HQAmmoSampler{static void Inspect(IEntity actor,SCR_AIGroup group,ERR_HQAmmoSnapshot* stock){assert(actor&&actor->group==group&&stock);}};
|
||||
struct SupplyTruck{IEntity Entity;SCR_AIGroup Group;};
|
||||
struct SupplyRecipient{SCR_AIGroup Group;};
|
||||
struct Coordinator{bool m_LauncherTrip=false;ERR_GameMode* m_Mode;SupplyTruck* m_Truck;SupplyRecipient* m_Recipient;
|
||||
ERR_InfantryAmmoTransfer* m_Move=nullptr;Array<IEntity>m_Members,m_Stock,m_Spent,m_Served;IEntity m_Receiving=nullptr;
|
||||
int m_Delivered=0,m_TripDelivered=0,finishes=0,audits=0;
|
||||
void Finish(string){finishes++;if(m_Move)m_Move->Stop();}
|
||||
void Audit(string){audits++;}
|
||||
bool Contains(IEntity owner,IEntity item){return owner&&owner->inventory&&owner->inventory->items.Count()<=128&&owner->inventory->items.Contains(item);}
|
||||
'''+transfer+r'''
|
||||
};
|
||||
struct SupplyFixture:Fixture{SupplyTruck supplyTruck;SupplyRecipient supplyRecipient;Coordinator c;
|
||||
SupplyFixture():supplyTruck{&truck,&donorGroup},supplyRecipient{&recipientGroup}{
|
||||
c.m_Mode=&mode;c.m_Truck=&supplyTruck;c.m_Recipient=&supplyRecipient;c.m_Members={&recipient};c.m_Stock={&magEntity};}
|
||||
};
|
||||
struct LauncherFixture:SupplyFixture{Entity loaded;Magazine empty;Item loadedItem;Slot loadedSlot;
|
||||
LauncherFixture(){c.m_LauncherTrip=true;weapon.kind=EWeaponType::WT_GRENADELAUNCHER;muzzle.kind=EMuzzleType::MT_UGLMuzzle;
|
||||
rifle.parent=&recipient;loaded.parent=&rifle;loaded.magazine=∅empty.owner=&loaded;empty.well=&well;empty.rounds=0;muzzle.loaded=∅loaded.item=&loadedItem;loadedItem.slot=&loadedSlot;loadedSlot.container=&muzzle;loadedSlot.attached=&loaded;}
|
||||
};
|
||||
int main(){
|
||||
{LauncherFixture f;f.muzzle.loaded=nullptr;assert(ERR_InfantryAmmoTransfer::Compatible(&f.recipient,&f.magEntity,true));f.c.Transfer(0);assert(f.source.calls==1&&f.c.m_Move->Submitted());}
|
||||
|
||||
{LauncherFixture f;assert(!ERR_InfantryAmmoTransfer::Compatible(&f.recipient,&f.magEntity));assert(ERR_InfantryAmmoTransfer::Compatible(&f.recipient,&f.magEntity,true));
|
||||
f.c.Transfer(0);assert(f.c.m_Move&&f.c.m_Move->Submitted()&&f.c.m_Spent.Count()==1&&f.source.calls==1);
|
||||
f.Move();f.source.last->OnComplete();f.c.Transfer(1000);f.c.Transfer(2000);assert(f.c.m_Delivered==1&&f.source.calls==1&&f.c.m_Served.Count()==1);}
|
||||
for(int bad=0;bad<8;bad++){LauncherFixture f;if(bad==0)f.magazine.used=true;if(bad==1)f.muzzle.well=nullptr;if(bad==2)f.muzzle.disposable=true;if(bad==3)f.empty.well=nullptr;if(bad==4)f.loadedSlot.attached=nullptr;if(bad==5)f.rifle.parent=nullptr;if(bad==6)f.empty.capacity=0;if(bad==7){f.muzzle.inMag=true;f.muzzle.canReload=false;}
|
||||
f.c.Transfer(0);assert(!f.c.m_Move&&f.source.calls==0&&f.c.m_Spent.IsEmpty());}
|
||||
{LauncherFixture f;f.source.accept=false;f.c.Transfer(0);assert(f.c.m_Move&&f.c.m_Move->Submitted()&&f.c.m_Move->Unresolved()&&f.c.m_Spent.Count()==1);f.c.Transfer(1000);assert(f.source.calls==1);f.Move();f.source.last->OnComplete();assert(!f.c.m_Move->Unresolved());}
|
||||
{LauncherFixture f;auto t=ERR_InfantryAmmoTransfer(&f.mode,&f.truck,&f.donorGroup,&f.recipient,&f.recipientGroup,&f.magEntity,false,true);
|
||||
f.source.effect=[&](){t.Stop();f.Move();};f.source.callback=1;assert(t.Tick(0)==FAILED&&!t.Unresolved()&&f.source.calls==1);assert(t.Tick(1)==FAILED);}
|
||||
|
||||
// A capacity recovery uses one exact unused empty, separately proving both native operations.
|
||||
{Fixture f;Entity empty;Magazine emptyMag;Item emptyItem;Slot emptyFrom{&f.targetStorage},emptyTo{&f.sourceStorage};
|
||||
emptyMag.well=&f.well;emptyMag.rounds=0;empty.magazine=&emptyMag;empty.item=&emptyItem;emptyItem.slot=&emptyFrom;
|
||||
f.destination.items={&empty};f.destination.selected=nullptr;f.source.selected=&f.sourceStorage;
|
||||
auto t=f.Job();assert(t.Tick(0)==PENDING&&t.Submitted()&&t.Unresolved()&&f.destination.calls==1&&f.source.calls==0);
|
||||
auto returning=f.destination.last;returning->OnComplete();assert(t.Tick(1)==PENDING&&f.source.calls==0&&t.Unresolved());
|
||||
f.destination.items.clear();f.source.items.Insert(&empty);emptyItem.slot=&emptyTo;f.destination.selected=&f.targetStorage;
|
||||
assert(t.Tick(2)==PENDING&&!t.Unresolved()&&f.source.calls==0);assert(t.Tick(3)==PENDING&&f.source.calls==1&&t.Unresolved());
|
||||
returning->OnAllComplete();returning->OnFailed();assert(t.Tick(4)==PENDING&&t.Unresolved());
|
||||
f.Move();assert(t.Tick(5)==PENDING);f.source.last->OnComplete();assert(t.Tick(6)==SUCCEEDED&&!t.Unresolved());
|
||||
assert(f.destination.calls==1&&f.source.calls==1);}
|
||||
// Ground coordinator reserves the incoming identity as soon as reverse native movement is submitted.
|
||||
// A failed/ambiguous return never releases it to try a second empty or another incoming magazine.
|
||||
for(int outcome=0;outcome<3;outcome++){EmptyFixture f;SupplyTruck truck{&f.truck,&f.donorGroup};SupplyRecipient recipient{&f.recipientGroup};Coordinator c;
|
||||
c.m_Mode=&f.mode;c.m_Truck=&truck;c.m_Recipient=&recipient;c.m_Members={&f.recipient};c.m_Stock={&f.magEntity};
|
||||
if(outcome==0){f.destination.accept=false;}
|
||||
c.Transfer(0);assert(c.m_Move&&c.m_Move->Submitted()&&c.m_Spent.Count()==1&&c.m_Receiving==&f.recipient&&c.m_Delivered==0);
|
||||
if(outcome==1){f.Return(false);f.destination.last->OnComplete();c.Transfer(1000);}
|
||||
c.Transfer(outcome==2?8000:2000);assert(c.finishes==1&&c.m_Spent.Contains(&f.magEntity)&&c.m_Delivered==0&&f.destination.calls==1&&f.source.calls==0);
|
||||
assert(c.m_Move->Unresolved()==(outcome==2));}
|
||||
{EmptyFixture f;SupplyTruck truck{&f.truck,&f.donorGroup};SupplyRecipient recipient{&f.recipientGroup};Coordinator c;
|
||||
c.m_Mode=&f.mode;c.m_Truck=&truck;c.m_Recipient=&recipient;c.m_Members={&f.recipient};c.m_Stock={&f.magEntity};
|
||||
c.Transfer(0);f.Return();f.destination.last->OnComplete();c.Transfer(1000);assert(c.m_Delivered==0&&f.source.calls==0);
|
||||
c.Transfer(2000);assert(c.m_Delivered==0&&f.source.calls==1);f.Move();f.source.last->OnComplete();c.Transfer(3000);c.Transfer(4000);
|
||||
assert(c.m_Delivered==1&&c.m_Served.Count()==1&&c.m_Spent.Count()==1&&f.source.calls==1&&f.destination.calls==1&&f.source.items.Contains(&f.empty));}
|
||||
// Default callers (including air delivery) retain the original one-way behavior.
|
||||
{EmptyFixture f;ERR_InfantryAmmoTransfer t(&f.mode,&f.truck,&f.donorGroup,&f.recipient,&f.recipientGroup,&f.magEntity);
|
||||
assert(t.Tick(0)==FAILED&&!t.Submitted()&&f.destination.calls==0&&f.source.calls==0);}
|
||||
// No return when capacity already exists, even with carried empties.
|
||||
{EmptyFixture f;f.destination.selected=&f.targetStorage;auto t=f.Job();assert(t.Tick(0)==PENDING&&f.source.calls==1&&f.destination.calls==0);}
|
||||
// Unused, zero-round, exact-well, exclusive recipient storage ownership and donor space are required.
|
||||
for(int invalid=0;invalid<12;invalid++){EmptyFixture f;f.destination.items={&f.empty};Well other;other.type=2;
|
||||
if(invalid==0)f.emptyMag.used=true;if(invalid==1)f.emptyMag.rounds=1;if(invalid==2)f.emptyMag.rounds=-1;
|
||||
if(invalid==3)f.emptyMag.capacity=0;if(invalid==4)f.emptyMag.well=&other;if(invalid==5)f.emptyMag.well=nullptr;
|
||||
if(invalid==6)f.emptyItem.slot=&f.emptyTo;if(invalid==7)f.source.items.Insert(&f.empty);
|
||||
if(invalid==8)f.source.selected=nullptr;if(invalid==9)f.destination.allow=false;
|
||||
if(invalid==10){for(int i=0;i<129;i++)f.destination.items.Insert(&f.empty);}if(invalid==11)ERR_InfantryAmmoAccess::clear=false;
|
||||
auto t=f.Job();assert(t.Tick(0)==FAILED&&!t.Submitted()&&!t.Unresolved()&&f.destination.calls==0&&f.source.calls==0);
|
||||
ERR_InfantryAmmoAccess::clear=true;}
|
||||
// Return proof needs exact destination slot as well as callback; no second candidate is ever moved.
|
||||
{EmptyFixture f;auto t=f.Job();t.Tick(0);f.Return();f.emptyItem.slot=&f.emptyFrom;f.destination.last->OnComplete();
|
||||
assert(t.Tick(1)==PENDING&&t.Unresolved()&&f.source.calls==0&&f.destination.calls==1);
|
||||
f.emptyItem.slot=&f.emptyTo;assert(t.Tick(2)==PENDING&&!t.Unresolved());assert(t.Tick(3)==PENDING&&f.source.calls==1&&f.destination.calls==1);}
|
||||
// Inline success callbacks override a false API result for the reverse operation, but require physical proof.
|
||||
{EmptyFixture f;f.destination.accept=false;f.destination.callback=1;f.destination.effect=[&](){f.Return();};auto t=f.Job();
|
||||
assert(t.Tick(0)==PENDING&&t.Submitted()&&!t.Unresolved());assert(t.Tick(1)==PENDING&&f.source.calls==0);
|
||||
f.source.accept=false;f.source.callback=1;f.source.effect=[&](){f.Move();};assert(t.Tick(2)==SUCCEEDED&&f.source.calls==1&&f.destination.calls==1);}
|
||||
// Every attempted reverse move burns the incoming identity, including synchronous rejection.
|
||||
for(int failure=0;failure<3;failure++){EmptyFixture f;if(failure==0)f.destination.accept=false;if(failure==1)f.destination.callback=2;
|
||||
auto t=f.Job();assert(t.Tick(0)==PENDING&&t.Submitted()&&f.destination.calls==1&&f.source.calls==0);
|
||||
assert(t.Tick(failure==2?8000:1)==FAILED);assert(t.Unresolved()==(failure==2));
|
||||
for(int i=0;i<3;i++){t.Tick(9000+i);}assert(f.destination.calls==1&&f.source.calls==0);}
|
||||
// Stop before, during, or after confirmed return can never launch the incoming move.
|
||||
for(int stopAt=0;stopAt<3;stopAt++){EmptyFixture f;auto t=f.Job();if(stopAt==0){t.Stop();assert(t.Tick(0)==FAILED&&!t.Submitted());continue;}
|
||||
t.Tick(0);if(stopAt==2){f.Return();f.destination.last->OnComplete();assert(t.Tick(1)==PENDING&&!t.Unresolved());}
|
||||
t.Stop();assert(t.Tick(2)==FAILED&&t.Submitted()&&f.source.calls==0);if(stopAt==1){assert(t.Unresolved());f.Return();f.destination.last->OnAllComplete();}
|
||||
assert(!t.Unresolved()&&t.Tick(3)==FAILED&&f.source.calls==0&&f.destination.calls==1);}
|
||||
// Confirmed return cannot trigger a second empty or bypass revalidated incoming custody/capacity/access.
|
||||
for(int changed=0;changed<6;changed++){EmptyFixture f;auto t=f.Job();t.Tick(0);f.Return(changed!=0);f.destination.last->OnComplete();assert(t.Tick(1)==PENDING);
|
||||
if(changed==1)f.source.items.RemoveItem(&f.magEntity);if(changed==2)f.destination.items.Insert(&f.magEntity);
|
||||
if(changed==3)f.truck.captured=true;if(changed==4)f.recipient.player=true;if(changed==5)ERR_InfantryAmmoAccess::clear=false;
|
||||
assert(t.Tick(2)==FAILED&&t.Submitted()&&!t.Unresolved()&&f.source.calls==0&&f.destination.calls==1);
|
||||
assert(t.Tick(3)==FAILED&&f.destination.calls==1);ERR_InfantryAmmoAccess::clear=true;}
|
||||
// Original owner/driver/ledger loss while a return is accepted quarantines that exact operation.
|
||||
for(int changed=0;changed<4;changed++){EmptyFixture f;auto t=f.Job();t.Tick(0);
|
||||
if(changed==0)f.truck.captured=true;if(changed==1)f.recipient.player=true;if(changed==2)f.pilot.occupant=&f.spareDriver;if(changed==3)f.donorRecord.Eliminated=true;
|
||||
assert(t.Tick(1)==FAILED&&t.Unresolved());f.destination.last->OnFailed();assert(!t.Unresolved()&&f.source.calls==0&&f.destination.calls==1);}
|
||||
// Both native phases are bounded independently, with no renewal while waiting for callbacks.
|
||||
{EmptyFixture f;auto t=f.Job();t.Tick(0);f.Return();f.destination.last->OnComplete();assert(t.Tick(7999)==PENDING);
|
||||
assert(t.Tick(8000)==PENDING&&f.source.calls==1);assert(t.Tick(15999)==FAILED&&t.Unresolved());}
|
||||
// XZ approach can precede valid vertical reach, stopped state, capacity or clear access.
|
||||
// None of those no-submission outcomes consumes cargo or ends the trip; the same exact item can later submit once.
|
||||
for(int obstruction=0;obstruction<5;obstruction++){SupplyFixture f;
|
||||
if(obstruction==0){f.recipient.position.y=3;}if(obstruction==1){f.physics.velocity.x=1;}
|
||||
if(obstruction==2){f.destination.selected=nullptr;}if(obstruction==3){f.source.allow=false;}
|
||||
if(obstruction==4){ERR_InfantryAmmoAccess::clear=false;}
|
||||
for(int tick=0;tick<3;tick++){f.c.Transfer(tick*1000);assert(f.source.calls==0&&f.c.m_Spent.IsEmpty()&&!f.c.m_Move&&!f.c.m_Receiving&&f.c.finishes==0);}
|
||||
f.recipient.position.y=0;f.physics.velocity.x=0;f.destination.selected=&f.targetStorage;f.source.allow=true;ERR_InfantryAmmoAccess::clear=true;
|
||||
f.c.Transfer(3000);assert(f.source.calls==1&&f.c.m_Spent.Count()==1&&f.c.m_Move&&f.c.m_Move->Submitted());
|
||||
f.Move();f.source.last->OnComplete();f.c.Transfer(4000);assert(f.c.m_Delivered==1&&f.c.m_Served.Contains(&f.recipient)&&f.source.calls==1&&f.c.m_Spent.Count()==1);
|
||||
}
|
||||
// A capacity-blocked first member cannot prevent another original member from receiving this magazine.
|
||||
{SupplyFixture f;Fixture second;ERR_GameMode::current=&f.mode;
|
||||
second.recipient.group=&f.recipientGroup;second.recipientRecord.Group=&f.recipientGroup;
|
||||
f.c.m_Members.Insert(&second.recipient);f.destination.selected=nullptr;
|
||||
f.c.Transfer(0);assert(f.source.calls==1&&f.c.m_Receiving==&second.recipient&&f.c.m_Spent.Count()==1&&f.c.finishes==0);
|
||||
f.source.items.clear();second.destination.items={&f.magEntity};f.item.slot=&second.toSlot;f.source.last->OnComplete();
|
||||
f.c.Transfer(1000);assert(f.c.m_Delivered==1&&f.c.m_Served.Contains(&second.recipient)&&!f.c.m_Served.Contains(&f.recipient));}
|
||||
// A native API may complete both physical movement and callback inline. Account once on the next slow tick.
|
||||
{SupplyFixture f;f.source.callback=1;f.source.effect=[&](){f.Move();};f.c.Transfer(0);
|
||||
assert(f.source.calls==1&&f.c.m_Move&&f.c.m_Move->Submitted()&&f.c.m_Delivered==0&&f.c.m_Spent.Count()==1);
|
||||
f.c.Transfer(1000);f.c.Transfer(2000);assert(f.c.m_Delivered==1&&f.c.m_Served.Count()==1&&f.source.calls==1&&f.c.finishes==0);}
|
||||
// Crossing the native submission boundary burns the exact identity even if native refusal is synchronous.
|
||||
for(int refusal=0;refusal<2;refusal++){SupplyFixture f;if(refusal==0){f.source.accept=false;}else{f.source.callback=2;}
|
||||
f.c.Transfer(0);assert(f.source.calls==1&&f.c.m_Spent.Count()==1&&f.c.m_Move->Submitted());
|
||||
f.c.Transfer(1000);assert(f.c.finishes==1&&f.c.m_Spent.Count()==1&&f.source.calls==1);
|
||||
}
|
||||
// Accepted but unresolved loss stays retained/spent; changing geometry cannot cause a second native move.
|
||||
{SupplyFixture f;f.c.Transfer(0);f.recipient.position.x=8;f.c.Transfer(1000);
|
||||
assert(f.c.finishes==1&&f.c.m_Move&&f.c.m_Move->Unresolved()&&f.c.m_Spent.Count()==1&&f.source.calls==1);}
|
||||
// Several equivalent magazines do not multiply a blocked actor's expensive preflight work on one slow tick.
|
||||
{SupplyFixture f;Entity another=f.magEntity;f.source.items.Insert(&another);f.c.m_Stock.Insert(&another);
|
||||
ERR_InfantryAmmoAccess::clear=false;ERR_InfantryAmmoAccess::checks=0;f.c.Transfer(0);
|
||||
assert(ERR_InfantryAmmoAccess::checks==1&&f.source.calls==0&&f.c.m_Spent.IsEmpty());ERR_InfantryAmmoAccess::clear=true;}
|
||||
// A blocked native access check happens before submission and is terminal.
|
||||
{Fixture f;auto t=f.Job();ERR_InfantryAmmoAccess::clear=false;ERR_InfantryAmmoAccess::checks=0;
|
||||
assert(t.Tick(0)==FAILED&&!t.Submitted()&&!t.Unresolved()&&f.source.calls==0&&ERR_InfantryAmmoAccess::checks==1);
|
||||
ERR_InfantryAmmoAccess::clear=true;assert(t.Tick(1)==FAILED&&f.source.calls==0&&ERR_InfantryAmmoAccess::checks==1);}
|
||||
// Once native move is accepted, later geometry cannot cause a second operation or pretend cancellation.
|
||||
{Fixture f;auto t=f.Job();ERR_InfantryAmmoAccess::checks=0;assert(t.Tick(0)==PENDING&&ERR_InfantryAmmoAccess::checks==1);
|
||||
ERR_InfantryAmmoAccess::clear=false;assert(t.Tick(1)==PENDING&&t.Unresolved()&&f.source.calls==1&&ERR_InfantryAmmoAccess::checks==1);
|
||||
f.Move();f.source.last->OnComplete();assert(t.Tick(2)==SUCCEEDED);ERR_InfantryAmmoAccess::clear=true;}
|
||||
// Read-only selection follows the native inherited-well direction, excluding launcher muzzles and weapons.
|
||||
{Fixture f;assert(ERR_InfantryAmmoTransfer::Compatible(&f.recipient,&f.magEntity));Well derived;derived.type=2;derived.parent=1;f.magazine.well=&derived;assert(ERR_InfantryAmmoTransfer::Compatible(&f.recipient,&f.magEntity));
|
||||
f.magazine.well=&f.well;f.muzzle.well=&derived;assert(!ERR_InfantryAmmoTransfer::Compatible(&f.recipient,&f.magEntity));
|
||||
f.muzzle.well=&f.well;f.muzzle.kind=EMuzzleType::MT_UGLMuzzle;assert(!ERR_InfantryAmmoTransfer::Compatible(&f.recipient,&f.magEntity));
|
||||
f.muzzle.kind=0;f.weapon.kind=EWeaponType::WT_ROCKETLAUNCHER;assert(!ERR_InfantryAmmoTransfer::Compatible(&f.recipient,&f.magEntity));}
|
||||
// Exactly one source move; neither a callback alone nor physical movement alone is a successful transaction.
|
||||
{Fixture f;auto t=f.Job();assert(t.Tick(100)==PENDING&&f.source.calls==1&&t.Unresolved());f.source.last->OnComplete();assert(t.Tick(101)==PENDING&&t.Unresolved());
|
||||
f.Move();assert(t.Tick(102)==SUCCEEDED&&!t.Unresolved());t.Stop();assert(t.Tick(103)==SUCCEEDED&&f.source.calls==1);}
|
||||
{Fixture f;auto t=f.Job();t.Tick(100);f.Move();assert(t.Tick(101)==PENDING&&t.Unresolved());f.source.last->OnAllComplete();assert(t.Tick(102)==SUCCEEDED);}
|
||||
// Exact destination slot and known donor absence are mandatory even after success callback.
|
||||
{Fixture f;auto t=f.Job();t.Tick(100);f.Move();f.item.slot=&f.fromSlot;f.source.last->OnComplete();assert(t.Tick(101)==PENDING&&t.Unresolved());
|
||||
f.item.slot=&f.toSlot;for(int i=0;i<513;i++)f.source.items.Insert(&f.foreign);assert(t.Tick(102)==PENDING&&t.Unresolved());}
|
||||
// Late callbacks resolve retained reservations after Stop without Tick or further operations; the stopped task stays failed.
|
||||
{Fixture f;auto t=f.Job();t.Tick(0);t.Stop();assert(t.Unresolved());f.Move();f.source.last->OnAllComplete();assert(!t.Unresolved());assert(t.Tick(2)==FAILED&&f.source.calls==1);}
|
||||
{Fixture f;auto t=f.Job();t.Tick(0);t.Stop();f.source.last->OnAllFailed();assert(!t.Unresolved());assert(t.Tick(1)==FAILED&&f.source.calls==1);}
|
||||
{Fixture f;auto t=f.Job();t.Tick(0);f.source.last->OnComplete();f.source.last->OnFailed();assert(t.Unresolved());f.Move();assert(!t.Unresolved()&&t.Tick(1)==SUCCEEDED);}
|
||||
// Failure/rejection and the 8s deadline never submit a second move.
|
||||
for(int failure=0;failure<3;failure++){Fixture f;if(failure==0)f.source.accept=false;if(failure==1)f.source.callback=2;auto t=f.Job();t.Tick(100);
|
||||
if(failure==2){assert(t.Tick(8100)==FAILED&&t.Unresolved());}else assert(t.Tick(101)==FAILED&&!t.Unresolved());
|
||||
for(int i=0;i<10;i++)t.Tick(8200+i);assert(f.source.calls==1);}
|
||||
// Invalid physical state before submission spends no native action; the terminal object cannot revive.
|
||||
for(int invalid=0;invalid<25;invalid++){Fixture f;auto t=f.Job();
|
||||
if(invalid==0)Replication::server=false;if(invalid==1)SCR_Global::editor=true;if(invalid==2)f.mode.active=false;
|
||||
if(invalid==3)f.truck.alive=false;if(invalid==4)f.recipient.alive=false;if(invalid==5)f.recipient.awake=false;if(invalid==6)f.recipient.player=true;
|
||||
if(invalid==7)f.recipient.vehicle=&f.truck;if(invalid==8)f.truck.captured=true;if(invalid==9)f.donorGroup.players=1;
|
||||
if(invalid==10)f.recipient.group=&f.otherGroup;if(invalid==11)f.donorRecord.Physical=false;if(invalid==12)f.recipientRecord.Eliminated=true;
|
||||
if(invalid==13)f.vehicleManager.vehicle=&f.foreign;if(invalid==14)f.driver.alive=false;if(invalid==15)f.driver.vehicle=nullptr;
|
||||
if(invalid==16)f.physics.velocity.x=.251f;if(invalid==17)f.physics.angular.x=.101f;if(invalid==18)f.recipient.position.x=4.001f;
|
||||
if(invalid==19)f.truck.faction=2;if(invalid==20)f.driver.faction=2;if(invalid==21)f.recipientGroup.slave=true;
|
||||
if(invalid==22)f.magazine.rounds=0;if(invalid==23)f.magazine.rounds=31;if(invalid==24)f.physics.velocity.x=std::numeric_limits<float>::quiet_NaN();
|
||||
assert(t.Tick(100)==FAILED&&f.source.calls==0&&!t.Unresolved());Replication::server=true;SCR_Global::editor=false;assert(t.Tick(200)==FAILED&&f.source.calls==0);}
|
||||
// Native stock/storage identity and capacity guards reject shared, foreign, oversized and inaccessible inventory.
|
||||
for(int invalid=0;invalid<9;invalid++){Fixture f;auto t=f.Job();if(invalid==0)f.source.items.clear();if(invalid==1)f.destination.items={&f.magEntity};
|
||||
if(invalid==2)f.fromSlot.storage=&f.targetStorage;if(invalid==3)f.sourceStorage.owner=&f.foreign;if(invalid==4)f.destination.selected=nullptr;
|
||||
if(invalid==5)f.targetStorage.owner=&f.foreign;if(invalid==6)f.source.allow=false;if(invalid==7)f.destination.storages.clear();if(invalid==8){for(int i=0;i<65;i++)f.destination.storages.Insert(&f.targetStorage);}
|
||||
assert(t.Tick(0)==FAILED&&f.source.calls==0);}
|
||||
// Conditions and original native driver/ledger identities remain strict while an accepted move is pending.
|
||||
for(int changed=0;changed<4;changed++){Fixture f;auto t=f.Job();t.Tick(100);Record replacement=f.recipientRecord;
|
||||
if(changed==0)f.pilot.occupant=&f.spareDriver;if(changed==1)f.ledger.records[1]=&replacement;if(changed==2)f.physics.velocity.x=1;if(changed==3)f.recipient.position.x=8;
|
||||
assert(t.Tick(101)==FAILED&&t.Unresolved());f.source.last->OnFailed();assert(!t.Unresolved()&&f.source.calls==1);}
|
||||
{Fixture f;auto t=f.Job();t.Tick(100);assert(t.Tick(99)==FAILED&&t.Unresolved());}
|
||||
// Clothing storage ownership follows native inventory slots and bounds cycles.
|
||||
{Fixture f;Entity vest;Item vestItem;Slot vestSlot;Storage body;body.owner=&f.recipient;vestSlot.storage=&body;vestItem.slot=&vestSlot;vest.item=&vestItem;f.targetStorage.owner=&vest;
|
||||
auto t=f.Job();assert(t.Tick(0)==PENDING);f.Move();f.source.last->OnComplete();assert(t.Tick(1)==SUCCEEDED);
|
||||
vestSlot.storage=&f.targetStorage;assert(!ERR_InfantryAmmoTransfer::StoredUnder(&vest,&f.recipient));}
|
||||
}
|
||||
'''
|
||||
# Brace tiny fixture statements to retain -Werror without suppressing useful compiler diagnostics.
|
||||
main=re.sub(r'if\((invalid|changed|failure)==(\d+)\)([^;{}]+);',r'if(\1==\2){\3;}',main)
|
||||
main=main.replace('for(int i=0;i<10;i++)t.Tick(8200+i);','for(int i=0;i<10;i++){t.Tick(8200+i);}')
|
||||
run(pre+translated_reservation()+code+'\n'.join(callbacks)+main)
|
||||
print('PASS: actual bounded empty-return and finite delivery; phase-isolated callbacks, exact ownership proof, stop quarantine and original actor/vehicle boundaries')
|
||||
@@ -0,0 +1,293 @@
|
||||
enum ERR_InfantryAmmoTransferState { FAILED = -1, PENDING, SUCCEEDED }
|
||||
|
||||
class ERR_InfantryAmmoCallback : ScriptedInventoryOperationCallback
|
||||
{
|
||||
ERR_InfantryAmmoTransfer Transfer;
|
||||
bool Returning;
|
||||
protected override void OnComplete() { if (Transfer) Transfer.Acknowledge(true, Returning); }
|
||||
protected override void OnFailed() { if (Transfer) Transfer.Acknowledge(false, Returning); }
|
||||
protected override void OnAllComplete() { if (Transfer) Transfer.Acknowledge(true, Returning); }
|
||||
protected override void OnAllFailed() { if (Transfer) Transfer.Acknowledge(false, Returning); }
|
||||
}
|
||||
|
||||
// One exact physical magazine movement. The caller owns HQ authorization and
|
||||
// the spent-item reservation; this leaf never selects tasks or retries a move.
|
||||
class ERR_InfantryAmmoTransfer
|
||||
{
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected Vehicle m_Donor;
|
||||
protected SCR_AIGroup m_DonorGroup;
|
||||
protected IEntity m_Recipient;
|
||||
protected SCR_AIGroup m_RecipientGroup;
|
||||
protected IEntity m_Magazine;
|
||||
protected IEntity m_Driver;
|
||||
protected ERR_ForceRecord m_DonorRecord;
|
||||
protected ERR_ForceRecord m_RecipientRecord;
|
||||
protected InventoryStorageManagerComponent m_Source;
|
||||
protected InventoryStorageManagerComponent m_Destination;
|
||||
protected BaseInventoryStorageComponent m_Storage;
|
||||
protected ref ERR_InfantryAmmoCallback m_Callback;
|
||||
protected bool m_RecycleEmpty;
|
||||
protected bool m_LauncherMagazine;
|
||||
protected IEntity m_ReturnMagazine;
|
||||
protected BaseInventoryStorageComponent m_ReturnStorage;
|
||||
protected ref ERR_InfantryAmmoCallback m_ReturnCallback;
|
||||
protected bool m_ReturnSubmitted;
|
||||
protected bool m_ReturnAccepted;
|
||||
protected bool m_ReturnDone;
|
||||
protected int m_ReturnAcknowledgement;
|
||||
protected float m_PhaseStarted = -1;
|
||||
protected bool m_Submitted;
|
||||
protected bool m_Accepted;
|
||||
protected int m_Acknowledgement;
|
||||
protected float m_Started = -1;
|
||||
protected ERR_InfantryAmmoTransferState m_State = ERR_InfantryAmmoTransferState.PENDING;
|
||||
protected string m_Reason;
|
||||
void ERR_InfantryAmmoTransfer(ERR_GameMode mode, Vehicle donorVehicle, SCR_AIGroup donorGroup, IEntity recipientActor, SCR_AIGroup recipientGroup, IEntity exactMagazine, bool recycleEmpty = false, bool launcherMagazine = false)
|
||||
{
|
||||
m_RecycleEmpty = recycleEmpty; m_LauncherMagazine = launcherMagazine;
|
||||
m_Mode = mode; m_Donor = donorVehicle; m_DonorGroup = donorGroup;
|
||||
m_Recipient = recipientActor; m_RecipientGroup = recipientGroup; m_Magazine = exactMagazine;
|
||||
}
|
||||
static bool Compatible(IEntity actor, IEntity exactMagazine, bool launcherMagazine = false)
|
||||
{
|
||||
if (!actor || !exactMagazine) return false;
|
||||
BaseMagazineComponent magazine = BaseMagazineComponent.Cast(exactMagazine.FindComponent(BaseMagazineComponent));
|
||||
BaseWeaponManagerComponent manager = BaseWeaponManagerComponent.Cast(actor.FindComponent(BaseWeaponManagerComponent));
|
||||
if (!magazine || !manager || !magazine.GetMagazineWell() || magazine.GetAmmoCount() <= 0 || magazine.GetMaxAmmoCount() <= 0 || magazine.GetAmmoCount() > magazine.GetMaxAmmoCount()) return false;
|
||||
if (launcherMagazine && magazine.IsUsed()) return false;
|
||||
array<IEntity> weapons = {}; manager.GetWeaponsList(weapons);
|
||||
if (weapons.IsEmpty() || weapons.Count() > 8) return false;
|
||||
foreach (IEntity rifle : weapons)
|
||||
{
|
||||
if (!rifle) continue;
|
||||
BaseWeaponComponent weapon = BaseWeaponComponent.Cast(rifle.FindComponent(BaseWeaponComponent));
|
||||
if (!weapon || weapon.GetOwner() != rifle) continue;
|
||||
EWeaponType kind = weapon.GetWeaponType();
|
||||
bool smallWeapon = kind == EWeaponType.WT_RIFLE || kind == EWeaponType.WT_SNIPERRIFLE || kind == EWeaponType.WT_MACHINEGUN || kind == EWeaponType.WT_HANDGUN;
|
||||
if (!launcherMagazine && !smallWeapon) continue;
|
||||
if (launcherMagazine && !smallWeapon && kind != EWeaponType.WT_ROCKETLAUNCHER && kind != EWeaponType.WT_GRENADELAUNCHER) continue;
|
||||
if (launcherMagazine && !StoredUnder(rifle, actor)) continue;
|
||||
array<BaseMuzzleComponent> muzzles = {}; weapon.GetMuzzlesList(muzzles);
|
||||
if (muzzles.Count() > 4) continue;
|
||||
foreach (BaseMuzzleComponent muzzle : muzzles)
|
||||
{
|
||||
if (!muzzle || !muzzle.GetMagazineWell()) continue;
|
||||
EMuzzleType muzzleKind = muzzle.GetMuzzleType();
|
||||
if (!launcherMagazine && muzzleKind != EMuzzleType.MT_BaseMuzzle) continue;
|
||||
if (launcherMagazine)
|
||||
{
|
||||
if (muzzleKind != EMuzzleType.MT_RPGMuzzle && muzzleKind != EMuzzleType.MT_UGLMuzzle
|
||||
&& kind != EWeaponType.WT_ROCKETLAUNCHER && kind != EWeaponType.WT_GRENADELAUNCHER) continue;
|
||||
// An expended single-round item may no longer exist. The real reloadable
|
||||
// muzzle/well proves capability; existing loaded items need exact custody.
|
||||
if (muzzle.IsDisposable()) continue;
|
||||
SCR_MuzzleInMagComponent reloadable = SCR_MuzzleInMagComponent.Cast(muzzle);
|
||||
if (reloadable && !reloadable.CanBeReloaded()) continue;
|
||||
BaseMagazineComponent loaded = muzzle.GetMagazine();
|
||||
if (loaded)
|
||||
{
|
||||
if (!loaded.GetOwner() || !loaded.GetMagazineWell() || loaded.GetMaxAmmoCount() <= 0) continue;
|
||||
InventoryItemComponent loadedItem = InventoryItemComponent.Cast(loaded.GetOwner().FindComponent(InventoryItemComponent));
|
||||
if (!loadedItem || !loadedItem.GetParentSlot()) continue;
|
||||
InventoryStorageSlot loadedSlot = loadedItem.GetParentSlot();
|
||||
if (loadedSlot.GetParentContainer() != muzzle || loadedSlot.GetAttachedEntity() != loaded.GetOwner()) continue;
|
||||
if (loaded.GetMagazineWell().Type() != muzzle.GetMagazineWell().Type()) continue;
|
||||
}
|
||||
if (magazine.GetMagazineWell().Type() != muzzle.GetMagazineWell().Type()) continue;
|
||||
}
|
||||
// Same direction as the stock magazine inventory predicate: a magazine's
|
||||
// derived well must inherit the actual carried small-arms muzzle's well.
|
||||
if (magazine.GetMagazineWell().IsInherited(muzzle.GetMagazineWell().Type())) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static bool StoredUnder(IEntity entity, IEntity root)
|
||||
{
|
||||
for (int depth = 0; entity && depth < 16; depth++)
|
||||
{
|
||||
if (entity == root) return true;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent));
|
||||
if (item && item.GetParentSlot())
|
||||
{
|
||||
BaseInventoryStorageComponent storage = item.GetParentSlot().GetStorage();
|
||||
if (!storage) return false;
|
||||
entity = storage.GetOwner();
|
||||
}
|
||||
else entity = entity.GetParent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected bool ManagedStorage(InventoryStorageManagerComponent inventory, BaseInventoryStorageComponent storage, IEntity owner)
|
||||
{
|
||||
if (!inventory || !storage || inventory.GetOwner() != owner || !StoredUnder(storage.GetOwner(), owner)) return false;
|
||||
array<BaseInventoryStorageComponent> storages = {};
|
||||
inventory.GetStorages(storages, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
return storages.Count() <= 64 && storages.Contains(storage);
|
||||
}
|
||||
protected int Ownership(InventoryStorageManagerComponent inventory, IEntity exact = null)
|
||||
{
|
||||
if (!exact) exact = m_Magazine;
|
||||
if (!inventory || !exact) return -1;
|
||||
array<IEntity> items = {}; inventory.GetItems(items, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (items.Count() > 512) return -1;
|
||||
if (items.Contains(exact)) return 1;
|
||||
return 0;
|
||||
}
|
||||
protected bool Proof(IEntity exact, InventoryStorageManagerComponent source, InventoryStorageManagerComponent destination, BaseInventoryStorageComponent storage, IEntity owner, int acknowledgement)
|
||||
{
|
||||
if (!exact || acknowledgement != 1 || Ownership(source, exact) != 0 || Ownership(destination, exact) != 1 || !ManagedStorage(destination, storage, owner)) return false;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(exact.FindComponent(InventoryItemComponent));
|
||||
return item && item.GetParentSlot() && item.GetParentSlot().GetStorage() == storage;
|
||||
}
|
||||
protected bool PhysicalProof()
|
||||
{
|
||||
return Proof(m_Magazine, m_Source, m_Destination, m_Storage, m_Recipient, m_Acknowledgement);
|
||||
}
|
||||
protected bool ReturnProof()
|
||||
{
|
||||
return Proof(m_ReturnMagazine, m_Destination, m_Source, m_ReturnStorage, m_Donor, m_ReturnAcknowledgement);
|
||||
}
|
||||
void Acknowledge(bool success, bool returning = false)
|
||||
{
|
||||
if (returning)
|
||||
{
|
||||
if (m_ReturnAcknowledgement == 0)
|
||||
{
|
||||
if (success) m_ReturnAcknowledgement = 1;
|
||||
else m_ReturnAcknowledgement = -1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (m_Acknowledgement != 0) return;
|
||||
if (success) m_Acknowledgement = 1;
|
||||
else m_Acknowledgement = -1;
|
||||
}
|
||||
// Both native operations remain observable after Stop. Returning an empty
|
||||
// magazine never proves delivery or grants authority for a later full one.
|
||||
bool Unresolved()
|
||||
{
|
||||
if ((m_ReturnAccepted || (m_LauncherMagazine && m_ReturnSubmitted)) && !m_ReturnDone && m_ReturnMagazine && m_ReturnAcknowledgement >= 0 && !ReturnProof()) return true;
|
||||
if (m_LauncherMagazine && m_Submitted) return m_Magazine && !PhysicalProof();
|
||||
if (!m_Accepted || !m_Magazine || m_Acknowledgement < 0) return false;
|
||||
return !PhysicalProof();
|
||||
}
|
||||
// Any native submission consumes this incoming identity at the coordinator,
|
||||
// even when only the empty return was attempted and delivery never started.
|
||||
bool Submitted() { return m_Submitted || m_ReturnSubmitted; }
|
||||
string Reason() { return m_Reason; }
|
||||
protected ERR_InfantryAmmoTransferState Fail(string reason)
|
||||
{
|
||||
if (m_State == ERR_InfantryAmmoTransferState.PENDING) { m_State = ERR_InfantryAmmoTransferState.FAILED; m_Reason = reason; }
|
||||
return m_State;
|
||||
}
|
||||
void Stop() { Fail("HQ authorization stopped; an accepted native move cannot be cancelled"); }
|
||||
protected bool Context()
|
||||
{
|
||||
if (!Replication.IsServer() || SCR_Global.IsEditMode() || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active() || !m_Mode.Forces()) return false;
|
||||
if (!m_Donor || !ERR_Actors.Alive(m_Donor) || !m_DonorGroup || !m_RecipientGroup || m_DonorGroup == m_RecipientGroup) return false;
|
||||
if (!ERR_EnemyEquipmentRules.Eligible(m_Mode, m_RecipientGroup, m_Recipient) || CompartmentAccessComponent.GetVehicleIn(m_Recipient)) return false;
|
||||
if (m_DonorGroup.IsSlave() || m_RecipientGroup.IsSlave() || m_DonorGroup.GetPlayerCount(true) > 0 || m_DonorGroup.GetFaction() != m_Mode.EnemyFaction()) return false;
|
||||
if (SCR_FactionManager.SGetFaction(m_Donor) != m_Mode.EnemyFaction() || SCR_FactionManager.SGetFaction(m_Recipient) != m_Mode.EnemyFaction()) return false;
|
||||
ERR_ForceRecord donorRecord = m_Mode.Forces().FindGroup(m_DonorGroup);
|
||||
ERR_ForceRecord recipientRecord = m_Mode.Forces().FindGroup(m_RecipientGroup);
|
||||
if (!donorRecord || !recipientRecord || donorRecord.Group != m_DonorGroup || recipientRecord.Group != m_RecipientGroup || !donorRecord.Physical || !recipientRecord.Physical || donorRecord.Eliminated || recipientRecord.Eliminated) return false;
|
||||
if (m_DonorRecord && (m_DonorRecord != donorRecord || m_RecipientRecord != recipientRecord)) return false;
|
||||
SCR_AIGroupUtilityComponent utility = m_DonorGroup.GetGroupUtilityComponent();
|
||||
if (!utility || !utility.IsMilitary() || !utility.m_VehicleMgr || !utility.m_VehicleMgr.FindVehicle(m_Donor)) return false;
|
||||
if (ERR_ConvoyRules.Captured(m_Donor, m_DonorGroup)) return false;
|
||||
SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(m_Donor.FindComponent(SCR_AIVehicleUsageComponent));
|
||||
if (!usage || !usage.GetPilotCompartmentSlot()) return false;
|
||||
IEntity driver = usage.GetPilotCompartmentSlot().GetOccupant();
|
||||
if (!ERR_EnemyEquipmentRules.Eligible(m_Mode, m_DonorGroup, driver) || !ERR_ConvoyRules.PartOf(CompartmentAccessComponent.GetVehicleIn(driver), m_Donor)) return false;
|
||||
if (SCR_FactionManager.SGetFaction(driver) != m_Mode.EnemyFaction() || (m_Driver && driver != m_Driver)) return false;
|
||||
Physics physics = m_Donor.GetPhysics();
|
||||
if (!physics) return false;
|
||||
float speed = physics.GetVelocity().Length();
|
||||
float angular = physics.GetAngularVelocity().Length();
|
||||
float distance = vector.Distance(m_Donor.GetOrigin(), m_Recipient.GetOrigin());
|
||||
if (!(speed >= 0 && speed <= 0.25 && angular >= 0 && angular <= 0.1 && distance >= 0 && distance <= 15)) return false;
|
||||
if (distance > 4 && !m_Donor.FindComponent(VehicleHelicopterSimulation)) return false;
|
||||
if (!Compatible(m_Recipient, m_Magazine, m_LauncherMagazine)) return false;
|
||||
m_Driver = driver; m_DonorRecord = donorRecord; m_RecipientRecord = recipientRecord;
|
||||
return true;
|
||||
}
|
||||
// One optional capacity recovery, using only an unused exact empty identity.
|
||||
protected bool ReturnEmpty()
|
||||
{
|
||||
if (!m_RecycleEmpty || m_ReturnSubmitted || m_ReturnMagazine) return false;
|
||||
BaseMagazineComponent incoming = BaseMagazineComponent.Cast(m_Magazine.FindComponent(BaseMagazineComponent));
|
||||
if (!incoming || !incoming.GetMagazineWell()) return false;
|
||||
array<IEntity> items = {}; m_Destination.GetItems(items, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (items.Count() > 128) return false;
|
||||
foreach (IEntity candidate : items)
|
||||
{
|
||||
if (!candidate || candidate == m_Magazine) continue;
|
||||
BaseMagazineComponent empty = BaseMagazineComponent.Cast(candidate.FindComponent(BaseMagazineComponent));
|
||||
if (!empty || empty.IsUsed() || empty.GetAmmoCount() != 0 || empty.GetMaxAmmoCount() <= 0
|
||||
|| !empty.GetMagazineWell() || empty.GetMagazineWell().Type() != incoming.GetMagazineWell().Type()) continue;
|
||||
if (Ownership(m_Destination, candidate) != 1 || Ownership(m_Source, candidate) != 0) continue;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(candidate.FindComponent(InventoryItemComponent));
|
||||
if (!item || !item.GetParentSlot() || !ManagedStorage(m_Destination, item.GetParentSlot().GetStorage(), m_Recipient)) continue;
|
||||
BaseInventoryStorageComponent storage = m_Source.FindStorageForItem(candidate, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (!ManagedStorage(m_Source, storage, m_Donor) || !m_Destination.CanMoveItemToStorage(candidate, storage, -1)) continue;
|
||||
m_ReturnMagazine = candidate; m_ReturnStorage = storage;
|
||||
m_ReturnCallback = new ERR_InfantryAmmoCallback(); m_ReturnCallback.Transfer = this; m_ReturnCallback.Returning = true;
|
||||
m_ReturnSubmitted = true;
|
||||
m_ReturnAccepted = m_Destination.TryMoveItemToStorage(candidate, storage, -1, m_ReturnCallback);
|
||||
if (m_ReturnAcknowledgement == 1) m_ReturnAccepted = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
ERR_InfantryAmmoTransferState Tick(float now)
|
||||
{
|
||||
if (m_State != ERR_InfantryAmmoTransferState.PENDING) return m_State;
|
||||
if (!ERR_ForceRules.Finite(now) || now < 0) return Fail("invalid transaction clock");
|
||||
if (m_Started < 0) { m_Started = now; m_PhaseStarted = now; }
|
||||
if (now < m_Started || now < m_PhaseStarted || now - m_Started >= 16000 || now - m_PhaseStarted >= 8000) return Fail("native magazine move deadline expired");
|
||||
if (!Context()) return Fail("original owned groups, vehicle, driver, recipient, proximity or compatible weapon unavailable");
|
||||
if (m_ReturnSubmitted && !m_ReturnDone)
|
||||
{
|
||||
if (!m_ReturnAccepted || m_ReturnAcknowledgement < 0) return Fail("native empty return rejected; neither identity will be retried");
|
||||
if (!ReturnProof()) return ERR_InfantryAmmoTransferState.PENDING;
|
||||
m_ReturnDone = true; m_PhaseStarted = now;
|
||||
// Separate slow tick allows loss of HQ authority to stop before delivery.
|
||||
return ERR_InfantryAmmoTransferState.PENDING;
|
||||
}
|
||||
if (!m_Submitted)
|
||||
{
|
||||
m_Source = InventoryStorageManagerComponent.Cast(m_Donor.FindComponent(InventoryStorageManagerComponent));
|
||||
m_Destination = InventoryStorageManagerComponent.Cast(m_Recipient.FindComponent(InventoryStorageManagerComponent));
|
||||
if (!m_Source || !m_Destination || m_Source == m_Destination || Ownership(m_Source) != 1 || Ownership(m_Destination) != 0) return Fail("exact magazine is not exclusive donor stock");
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(m_Magazine.FindComponent(InventoryItemComponent));
|
||||
if (!item || !item.GetParentSlot() || !ManagedStorage(m_Source, item.GetParentSlot().GetStorage(), m_Donor)) return Fail("exact source magazine storage does not belong to original donor");
|
||||
m_Storage = m_Destination.FindStorageForItem(m_Magazine, EStoragePurpose.PURPOSE_DEPOSIT);
|
||||
if (m_Storage && !ManagedStorage(m_Destination, m_Storage, m_Recipient)) return Fail("recipient storage ownership unavailable");
|
||||
bool capacity = m_Storage && m_Source.CanMoveItemToStorage(m_Magazine, m_Storage, -1);
|
||||
if (!ERR_InfantryAmmoAccess.Clear(m_Donor, m_Recipient)) return Fail("no unobstructed physical reach to an authored donor storage access point");
|
||||
if (!capacity)
|
||||
{
|
||||
if (!m_ReturnDone && ReturnEmpty()) return ERR_InfantryAmmoTransferState.PENDING;
|
||||
return Fail("recipient has no native compatible available storage after at most one empty return");
|
||||
}
|
||||
m_Callback = new ERR_InfantryAmmoCallback(); m_Callback.Transfer = this;
|
||||
m_Submitted = true;
|
||||
m_Accepted = m_Source.TryMoveItemToStorage(m_Magazine, m_Storage, -1, m_Callback);
|
||||
if (m_Acknowledgement == 1) m_Accepted = true;
|
||||
if (!m_Accepted) return Fail("native magazine move rejected; exact item will not be retried");
|
||||
}
|
||||
// Native callbacks may synchronously cancel this leaf or revoke ownership.
|
||||
if (m_LauncherMagazine && m_State != ERR_InfantryAmmoTransferState.PENDING) return m_State;
|
||||
if (m_LauncherMagazine && !Context()) return Fail("launcher custody authority changed during native submission");
|
||||
if (PhysicalProof())
|
||||
{
|
||||
m_State = ERR_InfantryAmmoTransferState.SUCCEEDED;
|
||||
m_Reason = "native callback and exact destination storage confirm one physical magazine transferred";
|
||||
return m_State;
|
||||
}
|
||||
if (m_Acknowledgement < 0) return Fail("native magazine move failed");
|
||||
return ERR_InfantryAmmoTransferState.PENDING;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/tmp/err-policy-7562cy7g/check.cpp: In function ‘int main()’:
|
||||
/tmp/err-policy-7562cy7g/check.cpp:48:3: error: ‘Fixture’ was not declared in this scope
|
||||
48 | {Fixture f;Leaf leaf;f.s.m_Move=&leaf;f.s.m_LauncherTrip=true;f.truck.SupplyHeld=true;f.s.Stop();assert(f.s.m_State==7&&f.hq.token==7&&f.truck.SupplyHeld);
|
||||
| ^~~~~~~
|
||||
/tmp/err-policy-7562cy7g/check.cpp:48:13: error: ‘Leaf’ was not declared in this scope
|
||||
48 | {Fixture f;Leaf leaf;f.s.m_Move=&leaf;f.s.m_LauncherTrip=true;f.truck.SupplyHeld=true;f.s.Stop();assert(f.s.m_State==7&&f.hq.token==7&&f.truck.SupplyHeld);
|
||||
| ^~~~
|
||||
/tmp/err-policy-7562cy7g/check.cpp:48:23: error: ‘f’ was not declared in this scope
|
||||
48 | {Fixture f;Leaf leaf;f.s.m_Move=&leaf;f.s.m_LauncherTrip=true;f.truck.SupplyHeld=true;f.s.Stop();assert(f.s.m_State==7&&f.hq.token==7&&f.truck.SupplyHeld);
|
||||
| ^
|
||||
/tmp/err-policy-7562cy7g/check.cpp:48:35: error: ‘leaf’ was not declared in this scope
|
||||
48 | {Fixture f;Leaf leaf;f.s.m_Move=&leaf;f.s.m_LauncherTrip=true;f.truck.SupplyHeld=true;f.s.Stop();assert(f.s.m_State==7&&f.hq.token==7&&f.truck.SupplyHeld);
|
||||
| ^~~~
|
||||
infantry resupply actual rules, radio transitions, owned cleanup and unresolved-operation tests passed
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-launcher-resupply-1140/tests/test_infantry_resupply_coordinator.py", line 111, in <module>
|
||||
run(r'''
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<31 lines>...
|
||||
}
|
||||
^
|
||||
'''.replace('METHOD',s))
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
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-7562cy7g/check.cpp', '-o', '/tmp/err-policy-7562cy7g/check']' returned non-zero exit status 1.
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"test_hq_ammo_sampler.py": 0,
|
||||
"test_infantry_ammo_transfer.py": 0,
|
||||
"test_infantry_resupply_planning.py": 0,
|
||||
"test_infantry_resupply_coordinator.py": 0,
|
||||
"test_mounted_supply_integration.py": 0,
|
||||
"test_hq_multiconvoy_supply.py": 0,
|
||||
"test_hq_ammo_reporting.py": 0,
|
||||
"test_hq_watch.py": 0,
|
||||
"test_infantry_mine_coordinator.py": 0,
|
||||
"test_infantry_supply_pool.py": 0,
|
||||
"test_hq_status_sampler.py": 0
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
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
|
||||
+1
@@ -0,0 +1 @@
|
||||
PASS: actual HQ rotates finite donors, skips unavailable/contacted trucks, uses delivered reports and dispatches at most one trip per planning cadence
|
||||
+1
@@ -0,0 +1 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
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
|
||||
+1
@@ -0,0 +1 @@
|
||||
PASS: actual bounded empty-return and finite delivery; phase-isolated callbacks, exact ownership proof, stop quarantine and original actor/vehicle boundaries
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
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
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
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
|
||||
+1
@@ -0,0 +1 @@
|
||||
PASS: actual convoy binding, finite identities, disjoint tokens, shared report replay/cooldown, original recipients and HQ cleanup
|
||||
+1
@@ -0,0 +1 @@
|
||||
PASS: actual finite convoy supply pool; immutable slots, shared demand state, sparse/capped discovery and permanent shutdown
|
||||
+1
@@ -0,0 +1 @@
|
||||
PASS: actual shared mounted stock/history ownership, atomic exact HQ pair claim, and bounded stopped-owner proof polling
|
||||
@@ -0,0 +1,29 @@
|
||||
from pathlib import Path
|
||||
import json,hashlib,subprocess
|
||||
p=Path('/tmp/erri-launcher-resupply-1140'); root=Path('/home/jakub/devel/escapistsrereintroduced')
|
||||
f=p/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c';s=f.read_text().replace('if (++checks > 128) return false;', 'if (++checks > 96 * 32) return false;').replace('magazine, true, m_LauncherTrip);','magazine, !m_LauncherTrip, m_LauncherTrip);');f.write_text(s)
|
||||
f=p/'tests/test_infantry_resupply_planning.py';s=f.read_text().replace('static inline bool compatible=true;', 'static inline IEntity only=nullptr;static inline int checks=0;static inline bool compatible=true;').replace('return actor&&item&&launcher&&compatible;', 'checks++;return actor&&item&&launcher&&compatible&&(!only||item==only);');s=s.replace('int main(){','''int main(){
|
||||
{Fixture f;for(int i=48;i<96;i++)f.inventory.items.Insert(&f.items[i]);f.s.Stock(0);
|
||||
ERR_InfantryAmmoTransfer::only=&f.items[95];ERR_InfantryAmmoTransfer::checks=0;
|
||||
assert(f.s.TryPlanLauncher(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0));
|
||||
assert(ERR_InfantryAmmoTransfer::checks==191);ERR_InfantryAmmoTransfer::only=nullptr;}
|
||||
''',1);f.write_text(s)
|
||||
f=p/'tests/test_hq_ammo_sampler.py';s=f.read_text().replace('launcher.disposable=false;','''launcher.disposable=false;
|
||||
launcher.inMag=true;launcher.canReload=false;auto singleUse=Fresh();ERR_HQAmmoSampler::Inspect(&actor,&group,&singleUse);assert(!singleUse.LauncherComplete&&singleUse.LauncherMuzzles==0);launcher.inMag=false;launcher.canReload=true;
|
||||
auto badMuzzles=expended;badMuzzles.LauncherMuzzles=-1;assert(!badMuzzles.Valid(1));badMuzzles.LauncherMuzzles=33;assert(!badMuzzles.Valid(1));badMuzzles.LauncherMuzzles=32;assert(badMuzzles.Valid(1));''');f.write_text(s)
|
||||
rel='tests/test_hq_watch.py';f=p/rel;orig=(root/rel).read_text();b=p/'baseline'/rel;b.parent.mkdir(parents=True,exist_ok=True);b.write_text(orig)
|
||||
s=orig.replace('original.MineComplete=true;', 'original.LauncherComplete=true;original.LauncherMuzzles=2;original.MineComplete=true;',1).replace('original.ReadyMines=0;', 'original.LauncherMuzzles=0;original.ReadyMines=0;',1)
|
||||
s=s.replace('RadioIntake radio;StatusUnit sender;', 'assert(mineWire.AmmoState->LauncherMuzzles==2);\n RadioIntake radio;StatusUnit sender;',1)
|
||||
s=s.replace('mineWire.AmmoState->ReadyMines=0;mineWire.AmmoState->MineComplete=false;', 'assert(mines->AmmoState->LauncherMuzzles==2);mineWire.AmmoState->LauncherMuzzles=0;assert(mines->AmmoState->LauncherMuzzles==2);\n mineWire.AmmoState->ReadyMines=0;mineWire.AmmoState->MineComplete=false;',1)
|
||||
s=s.replace('// Mounted payload obeys', '''mineWire.MessageId=4;mineWire.ObservedAt=6000;mineWire.AmmoState->LauncherMuzzles=-1;
|
||||
assert(!radio.m_Watch.Ingest(&mineWire,7000)&&mines->MessageId==3);
|
||||
mineWire.AmmoState->LauncherMuzzles=129;assert(!radio.m_Watch.Ingest(&mineWire,7000));
|
||||
// Mounted payload obeys''');f.write_text(s)
|
||||
meta=json.loads((p/'baseline.json').read_text())
|
||||
for f in (p/'tests').glob('test_*.py'):
|
||||
rel=str(f.relative_to(p));base=p/'baseline'/rel
|
||||
if not base.exists():base.write_bytes((root/rel).read_bytes())
|
||||
if rel not in meta:
|
||||
indexed=subprocess.check_output(['git','show',':'+rel],cwd=root)
|
||||
meta[rel]={'working_sha256':hashlib.sha256(base.read_bytes()).hexdigest(),'index_sha256':hashlib.sha256(indexed).hexdigest()}
|
||||
(p/'baseline.json').write_text(json.dumps(meta,indent=2)+'\n')
|
||||
@@ -0,0 +1,707 @@
|
||||
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c
|
||||
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c
|
||||
@@ -1296,6 +1296,10 @@
|
||||
// finite truck and delivered radio entries only after that scan fails.
|
||||
foreach (ERR_HQUnit mounted : m_Units)
|
||||
if (!ConvoyContact(mounted, now) && supply.TryPlanMounted(mounted, donor, m_Watch.Find(mounted.ForceSourceId), donorStatus, now)) return true;
|
||||
+ // Supported depleted launcher magazines follow rifle/mounted demand and
|
||||
+ // share the same finite stock, radio replay and physical rendezvous service.
|
||||
+ foreach (ERR_HQUnit launcherRecipient : m_Units)
|
||||
+ if (supply.TryPlanLauncher(launcherRecipient, donor, m_Watch.Find(launcherRecipient.ForceSourceId), donorStatus, now)) return true;
|
||||
// Optional finite mines are last: only a delivered known one-mine formation
|
||||
// may top up an existing carrier; no new issue or engineer-task reset.
|
||||
foreach (ERR_HQUnit mineRecipient : m_Units)
|
||||
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_HQAmmoSampler.c
|
||||
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_HQAmmoSampler.c
|
||||
@@ -12,6 +12,7 @@
|
||||
int SmallArmsRounds;
|
||||
int SmallArmsMagazines;
|
||||
int SmallArmsChambers;
|
||||
+ int LauncherMuzzles;
|
||||
int LauncherRounds;
|
||||
int LauncherMagazines;
|
||||
int LauncherChambers;
|
||||
@@ -25,7 +26,7 @@
|
||||
copy.Complete = Complete; copy.LauncherComplete = LauncherComplete; copy.Members = Members;
|
||||
copy.SmallArmsMuzzles = SmallArmsMuzzles; copy.SmallArmsRounds = SmallArmsRounds;
|
||||
copy.SmallArmsMagazines = SmallArmsMagazines; copy.SmallArmsChambers = SmallArmsChambers;
|
||||
- copy.LauncherRounds = LauncherRounds; copy.LauncherMagazines = LauncherMagazines;
|
||||
+ copy.LauncherMuzzles = LauncherMuzzles; copy.LauncherRounds = LauncherRounds; copy.LauncherMagazines = LauncherMagazines;
|
||||
copy.LauncherChambers = LauncherChambers;
|
||||
copy.MineComplete = MineComplete; copy.MineMembers = MineMembers; copy.ReadyMines = ReadyMines;
|
||||
return copy;
|
||||
@@ -39,6 +40,7 @@
|
||||
if (SmallArmsRounds < 0 || SmallArmsRounds > 4096000) return false;
|
||||
if (SmallArmsMagazines < 0 || SmallArmsMagazines > 4096) return false;
|
||||
if (SmallArmsChambers < 0 || SmallArmsChambers > 4096) return false;
|
||||
+ if (LauncherMuzzles < 0 || LauncherMuzzles > Members * 32) return false;
|
||||
if (LauncherRounds < 0 || LauncherRounds > 4096000) return false;
|
||||
if (LauncherMagazines < 0 || LauncherMagazines > 4096) return false;
|
||||
return LauncherChambers >= 0 && LauncherChambers <= 4096;
|
||||
@@ -137,7 +139,15 @@
|
||||
int category = Category(weapon, muzzle);
|
||||
if (category == 0) { result.Complete = false; result.LauncherComplete = false; continue; }
|
||||
if (category == 1) { small.Insert(muzzle); result.SmallArmsMuzzles++; }
|
||||
- else launchers.Insert(muzzle);
|
||||
+ else
|
||||
+ {
|
||||
+ launchers.Insert(muzzle);
|
||||
+ SCR_MuzzleInMagComponent reloadable = SCR_MuzzleInMagComponent.Cast(muzzle);
|
||||
+ bool supported = muzzle.GetMagazineWell() && !muzzle.IsDisposable();
|
||||
+ if (reloadable && !reloadable.CanBeReloaded()) supported = false;
|
||||
+ if (supported) result.LauncherMuzzles++;
|
||||
+ else result.LauncherComplete = false;
|
||||
+ }
|
||||
if (!muzzle.GetMagazineWell())
|
||||
{
|
||||
if (category == 1) result.Complete = false;
|
||||
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoTransfer.c
|
||||
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoTransfer.c
|
||||
@@ -26,8 +26,11 @@
|
||||
protected InventoryStorageManagerComponent m_Source;
|
||||
protected InventoryStorageManagerComponent m_Destination;
|
||||
protected BaseInventoryStorageComponent m_Storage;
|
||||
+ protected InventoryStorageSlot m_OriginalSlot;
|
||||
+ protected BaseInventoryStorageComponent m_OriginalStorage;
|
||||
protected ref ERR_InfantryAmmoCallback m_Callback;
|
||||
protected bool m_RecycleEmpty;
|
||||
+ protected bool m_LauncherMagazine;
|
||||
protected IEntity m_ReturnMagazine;
|
||||
protected BaseInventoryStorageComponent m_ReturnStorage;
|
||||
protected ref ERR_InfantryAmmoCallback m_ReturnCallback;
|
||||
@@ -42,18 +45,19 @@
|
||||
protected float m_Started = -1;
|
||||
protected ERR_InfantryAmmoTransferState m_State = ERR_InfantryAmmoTransferState.PENDING;
|
||||
protected string m_Reason;
|
||||
- void ERR_InfantryAmmoTransfer(ERR_GameMode mode, Vehicle donorVehicle, SCR_AIGroup donorGroup, IEntity recipientActor, SCR_AIGroup recipientGroup, IEntity exactMagazine, bool recycleEmpty = false)
|
||||
- {
|
||||
- m_RecycleEmpty = recycleEmpty;
|
||||
+ void ERR_InfantryAmmoTransfer(ERR_GameMode mode, Vehicle donorVehicle, SCR_AIGroup donorGroup, IEntity recipientActor, SCR_AIGroup recipientGroup, IEntity exactMagazine, bool recycleEmpty = false, bool launcherMagazine = false)
|
||||
+ {
|
||||
+ m_RecycleEmpty = recycleEmpty; m_LauncherMagazine = launcherMagazine;
|
||||
m_Mode = mode; m_Donor = donorVehicle; m_DonorGroup = donorGroup;
|
||||
m_Recipient = recipientActor; m_RecipientGroup = recipientGroup; m_Magazine = exactMagazine;
|
||||
}
|
||||
- static bool Compatible(IEntity actor, IEntity exactMagazine)
|
||||
+ static bool Compatible(IEntity actor, IEntity exactMagazine, bool launcherMagazine = false)
|
||||
{
|
||||
if (!actor || !exactMagazine) return false;
|
||||
BaseMagazineComponent magazine = BaseMagazineComponent.Cast(exactMagazine.FindComponent(BaseMagazineComponent));
|
||||
BaseWeaponManagerComponent manager = BaseWeaponManagerComponent.Cast(actor.FindComponent(BaseWeaponManagerComponent));
|
||||
if (!magazine || !manager || !magazine.GetMagazineWell() || magazine.GetAmmoCount() <= 0 || magazine.GetMaxAmmoCount() <= 0 || magazine.GetAmmoCount() > magazine.GetMaxAmmoCount()) return false;
|
||||
+ if (launcherMagazine && magazine.IsUsed()) return false;
|
||||
array<IEntity> weapons = {}; manager.GetWeaponsList(weapons);
|
||||
if (weapons.IsEmpty() || weapons.Count() > 8) return false;
|
||||
foreach (IEntity rifle : weapons)
|
||||
@@ -62,14 +66,38 @@
|
||||
BaseWeaponComponent weapon = BaseWeaponComponent.Cast(rifle.FindComponent(BaseWeaponComponent));
|
||||
if (!weapon || weapon.GetOwner() != rifle) continue;
|
||||
EWeaponType kind = weapon.GetWeaponType();
|
||||
- if (kind != EWeaponType.WT_RIFLE && kind != EWeaponType.WT_SNIPERRIFLE && kind != EWeaponType.WT_MACHINEGUN && kind != EWeaponType.WT_HANDGUN) continue;
|
||||
+ bool smallWeapon = kind == EWeaponType.WT_RIFLE || kind == EWeaponType.WT_SNIPERRIFLE || kind == EWeaponType.WT_MACHINEGUN || kind == EWeaponType.WT_HANDGUN;
|
||||
+ if (!launcherMagazine && !smallWeapon) continue;
|
||||
+ if (launcherMagazine && !smallWeapon && kind != EWeaponType.WT_ROCKETLAUNCHER && kind != EWeaponType.WT_GRENADELAUNCHER) continue;
|
||||
+ if (launcherMagazine && !StoredUnder(rifle, actor)) continue;
|
||||
array<BaseMuzzleComponent> muzzles = {}; weapon.GetMuzzlesList(muzzles);
|
||||
if (muzzles.Count() > 4) continue;
|
||||
foreach (BaseMuzzleComponent muzzle : muzzles)
|
||||
{
|
||||
if (!muzzle || !muzzle.GetMagazineWell()) continue;
|
||||
EMuzzleType muzzleKind = muzzle.GetMuzzleType();
|
||||
- if (muzzleKind != EMuzzleType.MT_BaseMuzzle) continue;
|
||||
+ if (!launcherMagazine && muzzleKind != EMuzzleType.MT_BaseMuzzle) continue;
|
||||
+ if (launcherMagazine)
|
||||
+ {
|
||||
+ if (muzzleKind != EMuzzleType.MT_RPGMuzzle && muzzleKind != EMuzzleType.MT_UGLMuzzle
|
||||
+ && kind != EWeaponType.WT_ROCKETLAUNCHER && kind != EWeaponType.WT_GRENADELAUNCHER) continue;
|
||||
+ // An expended single-round item may no longer exist. The real reloadable
|
||||
+ // muzzle/well proves capability; existing loaded items need exact custody.
|
||||
+ if (muzzle.IsDisposable()) continue;
|
||||
+ SCR_MuzzleInMagComponent reloadable = SCR_MuzzleInMagComponent.Cast(muzzle);
|
||||
+ if (reloadable && !reloadable.CanBeReloaded()) continue;
|
||||
+ BaseMagazineComponent loaded = muzzle.GetMagazine();
|
||||
+ if (loaded)
|
||||
+ {
|
||||
+ if (!loaded.GetOwner() || !loaded.GetMagazineWell() || loaded.GetMaxAmmoCount() <= 0) continue;
|
||||
+ InventoryItemComponent loadedItem = InventoryItemComponent.Cast(loaded.GetOwner().FindComponent(InventoryItemComponent));
|
||||
+ if (!loadedItem || !loadedItem.GetParentSlot()) continue;
|
||||
+ InventoryStorageSlot loadedSlot = loadedItem.GetParentSlot();
|
||||
+ if (loadedSlot.GetParentContainer() != muzzle || loadedSlot.GetAttachedEntity() != loaded.GetOwner()) continue;
|
||||
+ if (loaded.GetMagazineWell().Type() != muzzle.GetMagazineWell().Type()) continue;
|
||||
+ }
|
||||
+ if (magazine.GetMagazineWell().Type() != muzzle.GetMagazineWell().Type()) continue;
|
||||
+ }
|
||||
// Same direction as the stock magazine inventory predicate: a magazine's
|
||||
// derived well must inherit the actual carried small-arms muzzle's well.
|
||||
if (magazine.GetMagazineWell().IsInherited(muzzle.GetMagazineWell().Type())) return true;
|
||||
@@ -119,6 +147,20 @@
|
||||
{
|
||||
return Proof(m_Magazine, m_Source, m_Destination, m_Storage, m_Recipient, m_Acknowledgement);
|
||||
}
|
||||
+ // Terminal negative callback is settled only with exact unchanged source
|
||||
+ // custody. Native false without a callback still retains all uncertainty.
|
||||
+ protected bool RefusedAtSource()
|
||||
+ {
|
||||
+ if (!m_Submitted || m_Acknowledgement != -1 || !m_Magazine || !m_Donor || !m_Recipient) return false;
|
||||
+ if (!m_OriginalSlot || !m_OriginalStorage) return false;
|
||||
+ if (InventoryStorageManagerComponent.Cast(m_Donor.FindComponent(InventoryStorageManagerComponent)) != m_Source) return false;
|
||||
+ if (InventoryStorageManagerComponent.Cast(m_Recipient.FindComponent(InventoryStorageManagerComponent)) != m_Destination) return false;
|
||||
+ if (Ownership(m_Source) != 1 || Ownership(m_Destination) != 0) return false;
|
||||
+ if (!ManagedStorage(m_Source, m_OriginalStorage, m_Donor)) return false;
|
||||
+ InventoryItemComponent item = InventoryItemComponent.Cast(m_Magazine.FindComponent(InventoryItemComponent));
|
||||
+ if (!item || item.GetParentSlot() != m_OriginalSlot) return false;
|
||||
+ return m_OriginalSlot.GetStorage() == m_OriginalStorage && m_OriginalSlot.GetAttachedEntity() == m_Magazine;
|
||||
+ }
|
||||
protected bool ReturnProof()
|
||||
{
|
||||
return Proof(m_ReturnMagazine, m_Destination, m_Source, m_ReturnStorage, m_Donor, m_ReturnAcknowledgement);
|
||||
@@ -142,7 +184,8 @@
|
||||
// magazine never proves delivery or grants authority for a later full one.
|
||||
bool Unresolved()
|
||||
{
|
||||
- if (m_ReturnAccepted && !m_ReturnDone && m_ReturnMagazine && m_ReturnAcknowledgement >= 0 && !ReturnProof()) return true;
|
||||
+ if ((m_ReturnAccepted || (m_LauncherMagazine && m_ReturnSubmitted)) && !m_ReturnDone && m_ReturnMagazine && m_ReturnAcknowledgement >= 0 && !ReturnProof()) return true;
|
||||
+ if (m_LauncherMagazine && m_Submitted) return m_Magazine && !PhysicalProof() && !RefusedAtSource();
|
||||
if (!m_Accepted || !m_Magazine || m_Acknowledgement < 0) return false;
|
||||
return !PhysicalProof();
|
||||
}
|
||||
@@ -182,7 +225,7 @@
|
||||
float distance = vector.Distance(m_Donor.GetOrigin(), m_Recipient.GetOrigin());
|
||||
if (!(speed >= 0 && speed <= 0.25 && angular >= 0 && angular <= 0.1 && distance >= 0 && distance <= 15)) return false;
|
||||
if (distance > 4 && !m_Donor.FindComponent(VehicleHelicopterSimulation)) return false;
|
||||
- if (!Compatible(m_Recipient, m_Magazine)) return false;
|
||||
+ if (!Compatible(m_Recipient, m_Magazine, m_LauncherMagazine)) return false;
|
||||
m_Driver = driver; m_DonorRecord = donorRecord; m_RecipientRecord = recipientRecord;
|
||||
return true;
|
||||
}
|
||||
@@ -245,12 +288,20 @@
|
||||
if (!m_ReturnDone && ReturnEmpty()) return ERR_InfantryAmmoTransferState.PENDING;
|
||||
return Fail("recipient has no native compatible available storage after at most one empty return");
|
||||
}
|
||||
+ if (m_LauncherMagazine)
|
||||
+ {
|
||||
+ m_OriginalSlot = item.GetParentSlot(); m_OriginalStorage = m_OriginalSlot.GetStorage();
|
||||
+ if (m_OriginalSlot.GetAttachedEntity() != m_Magazine) return Fail("launcher source slot lacks reciprocal item proof");
|
||||
+ }
|
||||
m_Callback = new ERR_InfantryAmmoCallback(); m_Callback.Transfer = this;
|
||||
m_Submitted = true;
|
||||
m_Accepted = m_Source.TryMoveItemToStorage(m_Magazine, m_Storage, -1, m_Callback);
|
||||
if (m_Acknowledgement == 1) m_Accepted = true;
|
||||
if (!m_Accepted) return Fail("native magazine move rejected; exact item will not be retried");
|
||||
}
|
||||
+ // Native callbacks may synchronously cancel this leaf or revoke ownership.
|
||||
+ if (m_LauncherMagazine && m_State != ERR_InfantryAmmoTransferState.PENDING) return m_State;
|
||||
+ if (m_LauncherMagazine && !Context()) return Fail("launcher custody authority changed during native submission");
|
||||
if (PhysicalProof())
|
||||
{
|
||||
m_State = ERR_InfantryAmmoTransferState.SUCCEEDED;
|
||||
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c
|
||||
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c
|
||||
@@ -13,6 +13,19 @@
|
||||
&& entry.AmmoState.Complete && entry.AmmoState.Members == entry.Count
|
||||
&& entry.AmmoState.SmallArmsMuzzles > 0
|
||||
&& entry.AmmoState.SmallArmsRounds + entry.AmmoState.SmallArmsChambers <= entry.Count * 30;
|
||||
+ }
|
||||
+ // Only positively sampled reloadable magazine-well muzzles establish launcher demand.
|
||||
+ static bool LauncherNeed(ERR_HQWatchEntry entry, float now)
|
||||
+ {
|
||||
+ return Fresh(entry, now) && entry.AmmoState && entry.AmmoState.Valid(entry.Count)
|
||||
+ && entry.AmmoState.LauncherComplete && entry.AmmoState.Members == entry.Count
|
||||
+ && entry.AmmoState.LauncherMuzzles > 0
|
||||
+ && entry.AmmoState.LauncherRounds == 0 && entry.AmmoState.LauncherChambers == 0;
|
||||
+ }
|
||||
+ static bool LauncherPair(ERR_HQWatchEntry recipient, ERR_HQWatchEntry donor, float now)
|
||||
+ {
|
||||
+ return LauncherNeed(recipient, now) && Fresh(donor, now) && recipient.SourceId != donor.SourceId
|
||||
+ && vector.DistanceXZ(recipient.Position, donor.Position) <= 200;
|
||||
}
|
||||
static bool MinePair(ERR_HQWatchEntry recipient, ERR_HQWatchEntry donor, float now)
|
||||
{
|
||||
@@ -43,6 +56,7 @@
|
||||
protected ref ERR_ConvoyMineStock m_MineBatch;
|
||||
protected bool m_MineStockCaptured;
|
||||
protected bool m_MineTrip;
|
||||
+ protected bool m_LauncherTrip;
|
||||
protected int m_MineRecipientSlot = -1;
|
||||
protected bool m_EmptyMineReplacement;
|
||||
protected int m_MinesDelivered;
|
||||
@@ -58,7 +72,7 @@
|
||||
protected AIWaypoint m_MoveWaypoint;
|
||||
protected AIWaypoint m_HoldWaypoint;
|
||||
protected vector m_Rendezvous;
|
||||
- protected int m_State; // 0 stock startup, 1 idle, 2 truck downlink, 3 stopped status, 4 infantry downlink, 5 transfer, 7 unresolved mine custody
|
||||
+ protected int m_State; // 0 stock startup, 1 idle, 2 truck downlink, 3 stopped status, 4 infantry downlink, 5 transfer, 7 unresolved item custody
|
||||
protected int m_Trip;
|
||||
protected int m_Token;
|
||||
protected int m_ReportId;
|
||||
@@ -92,7 +106,11 @@
|
||||
protected void PollTransfers(float now)
|
||||
{
|
||||
if (m_Mounted) m_Mounted.Tick(now);
|
||||
- if (m_State <= 1 && m_Move && !m_Move.Unresolved()) m_Move = null;
|
||||
+ if (m_Move && !m_Move.Unresolved() && (m_State <= 1 || (m_State == 7 && m_LauncherTrip)))
|
||||
+ {
|
||||
+ m_Move = null;
|
||||
+ if (m_State == 7) Finish("launcher custody settled; original claims released without retry or refund");
|
||||
+ }
|
||||
if (m_MineMove && !m_MineMove.Unresolved() && (m_State <= 1 || m_State == 7))
|
||||
{
|
||||
m_MineMove = null;
|
||||
@@ -135,6 +153,7 @@
|
||||
protected void Audit(string reason)
|
||||
{
|
||||
if (m_MineTrip) reason = "mine top-up: " + reason;
|
||||
+ if (m_LauncherTrip) reason = "depleted launcher magazine: " + reason;
|
||||
m_Reason = reason;
|
||||
m_HQ.Journal().Write("INFANTRY SUPPLY", string.Format("convoy-slot=%1 trip=%2 state=%3 moved=%4 spent=%5/%6; %7", m_ConvoySlot, m_Trip, m_State, m_Delivered, m_Spent.Count(), m_Stock.Count(), reason), m_ReportId);
|
||||
}
|
||||
@@ -230,10 +249,10 @@
|
||||
}
|
||||
m_State = 1; Audit("truck magazine identities captured once after startup; absent/looted stock is never replaced");
|
||||
}
|
||||
- bool TryPlan(ERR_HQUnit recipient, ERR_HQUnit donor, ERR_HQWatchEntry demand, ERR_HQWatchEntry donorStatus, float now, bool mineTrip = false)
|
||||
+ bool TryPlan(ERR_HQUnit recipient, ERR_HQUnit donor, ERR_HQWatchEntry demand, ERR_HQWatchEntry donorStatus, float now, bool mineTrip = false, bool launcherTrip = false)
|
||||
{
|
||||
if (!Current() || m_State != 1 || m_Move || m_MineMove || (m_Mounted && m_Mounted.Busy()) || m_Trip >= 128 || !TruckValid()) return false;
|
||||
- if (m_Truck.Combat.Held || m_Truck.SupplyToken > 0 || !recipient || !donor) return false;
|
||||
+ if ((mineTrip && launcherTrip) || m_Truck.Combat.Held || m_Truck.SupplyToken > 0 || !recipient || !donor) return false;
|
||||
if (donor.Group != m_Truck.Group || recipient.HomeGuard || !Quiet(recipient) || !Quiet(donor)) return false;
|
||||
if (donor.Downlink || donor.PendingOrder || donor.PendingConvoyOrder) return false;
|
||||
if (mineTrip)
|
||||
@@ -253,6 +272,10 @@
|
||||
if (!designated) return false;
|
||||
}
|
||||
}
|
||||
+ else if (launcherTrip)
|
||||
+ {
|
||||
+ if (!ERR_InfantryResupplyRules.LauncherPair(demand, donorStatus, now)) return false;
|
||||
+ }
|
||||
else if (!ERR_InfantryResupplyRules.Pair(demand, donorStatus, now)) return false;
|
||||
if (demand.SourceId != recipient.ForceSourceId || donorStatus.SourceId != donor.ForceSourceId) return false;
|
||||
if (demand.MessageId <= m_Consumed.Get(demand.SourceId) || now < m_NextAttempt.Get(demand.SourceId)) return false;
|
||||
@@ -263,6 +286,7 @@
|
||||
foreach (IEntity mine : m_MineStock)
|
||||
if (ERR_InfantryMineTransfer.ReadyMine(mine) && !m_Spent.Contains(mine) && Contains(m_Truck.Entity, mine)) { available = true; break; }
|
||||
}
|
||||
+ else if (launcherTrip) available = LauncherStock(recipient);
|
||||
else foreach (IEntity magazine : m_Stock) if (magazine && !m_Spent.Contains(magazine) && Contains(m_Truck.Entity, magazine)) { available = true; break; }
|
||||
if (m_Spent.Count() >= 96) return false;
|
||||
if (!available) return false;
|
||||
@@ -279,12 +303,13 @@
|
||||
if (slot < 0) slot = 0; // Isolated legacy fixture uses the first convoy's range.
|
||||
int token = 100000 + slot * 128 + m_Trip + 1;
|
||||
if (!m_HQ.ReserveLogistics(recipient.Group, token)) { m_Recipient = null; m_Donor = null; return false; }
|
||||
- m_MineTrip = mineTrip; m_MineRecipientSlot = -1; m_Receiving = null; m_EmptyMineReplacement = false;
|
||||
+ m_MineTrip = mineTrip; m_LauncherTrip = launcherTrip; m_MineRecipientSlot = -1; m_Receiving = null; m_EmptyMineReplacement = false;
|
||||
m_Trip++; m_Token = token; m_Truck.SupplyToken = token; m_ReportId = demand.MessageId; m_TripDelivered = 0; m_Served.Clear();
|
||||
m_Consumed.Set(demand.SourceId, demand.MessageId); m_NextAttempt.Set(demand.SourceId, now + 180000);
|
||||
m_Deadline = Math.Min(now + 120000, demand.ObservedAt + 180000); m_State = 2;
|
||||
string purpose = "stop existing ammo truck and report position";
|
||||
if (mineTrip) purpose += "; one finite PMN4 top-up for an existing carrier";
|
||||
+ if (launcherTrip) purpose += "; finite compatible magazine for a reported depleted launcher";
|
||||
m_Order = m_HQ.Downlink(donor.Group, "INFANTRY SUPPLY HOLD", purpose, m_ReportId);
|
||||
if (!m_Order) Finish("truck radio downlink unavailable");
|
||||
else Audit("existing patrol reserved; truck hold awaits radio delivery");
|
||||
@@ -306,6 +331,31 @@
|
||||
if (item && !m_MineStock.Contains(item)) m_MineStock.Insert(item);
|
||||
}
|
||||
return true;
|
||||
+ }
|
||||
+ // Compatibility-only preflight of original own carriers; HQ still uses the
|
||||
+ // delivered report for demand and positions. No item is reserved or moved here.
|
||||
+ protected bool LauncherStock(ERR_HQUnit recipient)
|
||||
+ {
|
||||
+ if (!recipient || !recipient.StatusSampler || m_Stock.Count() > 96) return false;
|
||||
+ int strength = recipient.StatusSampler.InitialStrength();
|
||||
+ if (strength < 1 || strength > 32) return false;
|
||||
+ int checks;
|
||||
+ foreach (IEntity magazine : m_Stock)
|
||||
+ {
|
||||
+ if (!magazine || m_Spent.Contains(magazine) || !Contains(m_Truck.Entity, magazine)) continue;
|
||||
+ for (int slot = 0; slot < strength; slot++)
|
||||
+ {
|
||||
+ if (++checks > 96 * 32) return false;
|
||||
+ IEntity actor = recipient.StatusSampler.InitialMember(slot);
|
||||
+ if (!actor || !recipient.StatusSampler.CanAttribute(slot, actor) || !ERR_Actors.CanFight(actor)) continue;
|
||||
+ if (ERR_InfantryAmmoTransfer.Compatible(actor, magazine, true)) return true;
|
||||
+ }
|
||||
+ }
|
||||
+ return false;
|
||||
+ }
|
||||
+ bool TryPlanLauncher(ERR_HQUnit recipient, ERR_HQUnit donor, ERR_HQWatchEntry demand, ERR_HQWatchEntry donorStatus, float now)
|
||||
+ {
|
||||
+ return TryPlan(recipient, donor, demand, donorStatus, now, false, true);
|
||||
}
|
||||
bool TryPlanMines(ERR_HQUnit recipient, ERR_HQUnit donor, ERR_HQWatchEntry demand, ERR_HQWatchEntry donorStatus, float now)
|
||||
{
|
||||
@@ -345,10 +395,10 @@
|
||||
m_Order = null;
|
||||
// A late native move must not race another truck's newer report. Retain both
|
||||
// exact claims until this one submitted identity proves custody or deletion.
|
||||
- if (m_MineMove && m_MineMove.Unresolved()) { m_State = 7; return; }
|
||||
+ if ((m_MineMove && m_MineMove.Unresolved()) || (m_LauncherTrip && m_Move && m_Move.Unresolved())) { m_State = 7; return; }
|
||||
if (m_Recipient) m_HQ.ReleaseLogistics(m_Recipient.Group, m_Token);
|
||||
if (m_Truck && m_Truck.SupplyToken == m_Token) { m_Truck.SupplyToken = 0; m_Truck.SupplyHeld = false; }
|
||||
- m_Recipient = null; m_Donor = null; m_Order = null; m_Receiving = null; m_State = 1; m_MineTrip = false; m_MineRecipientSlot = -1;
|
||||
+ m_Recipient = null; m_Donor = null; m_Order = null; m_Receiving = null; m_State = 1; m_MineTrip = false; m_LauncherTrip = false; m_MineRecipientSlot = -1;
|
||||
}
|
||||
protected void TransferMine(float now)
|
||||
{
|
||||
@@ -414,14 +464,19 @@
|
||||
if (!ERR_Actors.CanFight(actor) || m_Served.Contains(actor) || vector.DistanceXZ(actor.GetOrigin(), m_Truck.Entity.GetOrigin()) > 4) continue;
|
||||
ERR_HQAmmoSnapshot local = new ERR_HQAmmoSnapshot(); local.Complete = true; local.LauncherComplete = true;
|
||||
ERR_HQAmmoSampler.Inspect(actor, m_Recipient.Group, local);
|
||||
- if (!local.Complete || local.SmallArmsMuzzles <= 0 || local.SmallArmsRounds + local.SmallArmsChambers > 30) continue;
|
||||
+ if (m_LauncherTrip)
|
||||
+ {
|
||||
+ if (!local.Valid(1) || !local.LauncherComplete || local.Members != 1 || local.LauncherMuzzles <= 0
|
||||
+ || local.LauncherRounds != 0 || local.LauncherChambers != 0) continue;
|
||||
+ }
|
||||
+ else if (!local.Complete || local.SmallArmsMuzzles <= 0 || local.SmallArmsRounds + local.SmallArmsChambers > 30) continue;
|
||||
foreach (IEntity magazine : m_Stock)
|
||||
{
|
||||
- if (!magazine || m_Spent.Contains(magazine) || !Contains(m_Truck.Entity, magazine) || !ERR_InfantryAmmoTransfer.Compatible(actor, magazine)) continue;
|
||||
+ if (!magazine || m_Spent.Contains(magazine) || !Contains(m_Truck.Entity, magazine) || !ERR_InfantryAmmoTransfer.Compatible(actor, magazine, m_LauncherTrip)) continue;
|
||||
ERR_FiniteMagazineReservation reservation = new ERR_FiniteMagazineReservation(m_Stock, m_Spent, magazine);
|
||||
if (!reservation.Reserve()) continue;
|
||||
m_Receiving = actor;
|
||||
- m_Move = new ERR_InfantryAmmoTransfer(m_Mode, m_Truck.Entity, m_Truck.Group, actor, m_Recipient.Group, magazine, true);
|
||||
+ m_Move = new ERR_InfantryAmmoTransfer(m_Mode, m_Truck.Entity, m_Truck.Group, actor, m_Recipient.Group, magazine, !m_LauncherTrip, m_LauncherTrip);
|
||||
m_Move.Tick(now);
|
||||
if (m_Move.Submitted()) { reservation.Commit(); return; }
|
||||
// No native operation exists yet: approach geometry, stopped state or
|
||||
--- a/tests/test_hq_ammo_reporting.py
|
||||
+++ b/tests/test_hq_ammo_reporting.py
|
||||
@@ -34,7 +34,7 @@
|
||||
struct ERR_HQAmmoSnapshot{
|
||||
bool MineComplete=false;int MineMembers=0,ReadyMines=0;
|
||||
bool Complete=true;int Members=4,SmallArmsMuzzles=4,SmallArmsRounds=121,SmallArmsMagazines=4,SmallArmsChambers=0;
|
||||
- int LauncherRounds=0,LauncherMagazines=0,LauncherChambers=0;
|
||||
+ int LauncherMuzzles=0,LauncherRounds=0,LauncherMagazines=0,LauncherChambers=0;
|
||||
'''+body(A,' bool Valid(')+r'''
|
||||
};
|
||||
struct ERR_HQMountedAmmoSnapshot{bool Complete=true;int Weapons=1,Muzzles=1,Magazines=1,Rounds=100,Capacity=100,Chambers=0,LowMagazines=0;
|
||||
--- a/tests/test_hq_ammo_sampler.py
|
||||
+++ b/tests/test_hq_ammo_sampler.py
|
||||
@@ -12,10 +12,11 @@
|
||||
code=code.replace('BaseWeaponComponent.Cast(weaponEntity.FindComponent(BaseWeaponComponent))','weaponEntity->weapon')
|
||||
code=code.replace('BaseMagazineComponent.Cast(item.FindComponent(BaseMagazineComponent))','item->magazine')
|
||||
code=re.sub(r'\bERR_HQAmmoSnapshot (?=\w)', 'ERR_HQAmmoSnapshot* ',code)
|
||||
+code=code.replace('SCR_MuzzleInMagComponent.Cast(muzzle)', '(muzzle->inMag ? muzzle : null)')
|
||||
code=code.replace('array<','Array<').replace('foreach (','for (').replace('null','nullptr')
|
||||
for name in ('EWeaponType','EMuzzleType','Replication','ERR_Actors'):code=code.replace(name+'.',name+'::')
|
||||
code=code.replace('EWeaponType kind','int kind').replace('EMuzzleType muzzleKind','int muzzleKind')
|
||||
-for name in ('weapon','muzzle','magazine','actor','group','result','inventory','manager','loaded','copy'):
|
||||
+for name in ('weapon','muzzle','reloadable','magazine','actor','group','result','inventory','manager','loaded','copy'):
|
||||
code=re.sub(r'\b'+name+r'\.',name+'->',code)
|
||||
code=code.replace('GetMagazineWell().','GetMagazineWell()->')
|
||||
code=re.sub(r'\b(bool|int) (\w+);',r'\1 \2=0;',code)
|
||||
@@ -30,7 +31,7 @@
|
||||
void Insert(T n){this->push_back(n);}bool Contains(T n){return std::find(this->begin(),this->end(),n)!=this->end();}};
|
||||
struct Entity;struct Muzzle;struct Magazine;struct Weapon;struct Group;struct Inventory;struct Manager;
|
||||
using IEntity=Entity*;using SCR_AIGroup=Group*;using BaseWeaponComponent=Weapon*;
|
||||
-using BaseMuzzleComponent=Muzzle*;using BaseMagazineComponent=Magazine*;
|
||||
+using BaseMuzzleComponent=Muzzle*;using SCR_MuzzleInMagComponent=Muzzle*;using BaseMagazineComponent=Magazine*;
|
||||
using InventoryStorageManagerComponent=Inventory*;using BaseWeaponManagerComponent=Manager*;
|
||||
struct EWeaponType{static constexpr int WT_RIFLE=1,WT_SNIPERRIFLE=2,WT_MACHINEGUN=3,WT_HANDGUN=4,
|
||||
WT_ROCKETLAUNCHER=5,WT_GRENADELAUNCHER=6,WT_FRAGGRENADE=7,WT_SMOKEGRENADE=8;};
|
||||
@@ -39,7 +40,7 @@
|
||||
struct Magazine{IEntity owner=nullptr;Well* well=nullptr;int rounds=0,capacity=30;
|
||||
IEntity GetOwner(){return owner;}Well* GetMagazineWell(){return well;}
|
||||
int GetAmmoCount(){return rounds;}int GetMaxAmmoCount(){return capacity;}};
|
||||
-struct Muzzle{Well* well=nullptr;Magazine* magazine=nullptr;int kind=0,barrels=1;bool chamber=false;
|
||||
+struct Muzzle{bool inMag=false,canReload=true;bool CanBeReloaded(){return canReload;}bool disposable=false;bool IsDisposable(){return disposable;}Well* well=nullptr;Magazine* magazine=nullptr;int kind=0,barrels=1;bool chamber=false;
|
||||
int GetMuzzleType(){return kind;}Well* GetMagazineWell(){return well;}
|
||||
int GetBarrelsCount(){return barrels;}bool IsBarrelChambered(int){return chamber;}
|
||||
Magazine* GetMagazine(){return magazine;}};
|
||||
@@ -87,7 +88,14 @@
|
||||
Muzzle launcher;launcher.kind=EMuzzleType::MT_UGLMuzzle;launcher.well=&rocketWell;launcher.magazine=&unrelated;
|
||||
weapon.muzzles.Insert(&launcher);auto mixed=Fresh();ERR_HQAmmoSampler::Inspect(&actor,&group,&mixed);
|
||||
assert(mixed.Complete&&mixed.LauncherComplete&&mixed.SmallArmsRounds==0&&mixed.LauncherRounds==1);
|
||||
- assert(mixed.LauncherMagazines==1&&mixed.KnownSmallArmsEmpty()); // launcher stock is not rifle stock
|
||||
+ assert(mixed.LauncherMagazines==1&&mixed.LauncherMuzzles==1&&mixed.KnownSmallArmsEmpty());
|
||||
+ auto launcherWire=mixed.Snapshot();mixed.LauncherMuzzles=0;assert(launcherWire->LauncherMuzzles==1);
|
||||
+ launcher.magazine=nullptr;inv.items.clear();auto expended=Fresh();ERR_HQAmmoSampler::Inspect(&actor,&group,&expended);
|
||||
+ assert(expended.LauncherComplete&&expended.LauncherMuzzles==1&&expended.LauncherRounds==0&&expended.LauncherMagazines==0);
|
||||
+ launcher.disposable=true;auto disposable=Fresh();ERR_HQAmmoSampler::Inspect(&actor,&group,&disposable);assert(!disposable.LauncherComplete&&disposable.LauncherMuzzles==0);launcher.disposable=false;
|
||||
+ launcher.inMag=true;launcher.canReload=false;auto singleUse=Fresh();ERR_HQAmmoSampler::Inspect(&actor,&group,&singleUse);assert(!singleUse.LauncherComplete&&singleUse.LauncherMuzzles==0);launcher.inMag=false;launcher.canReload=true;
|
||||
+ auto badMuzzles=expended;badMuzzles.LauncherMuzzles=-1;assert(!badMuzzles.Valid(1));badMuzzles.LauncherMuzzles=33;assert(!badMuzzles.Valid(1));badMuzzles.LauncherMuzzles=32;assert(badMuzzles.Valid(1));
|
||||
+ launcher.magazine=&unrelated;inv.items={&loadedEntity,&spareEntity,&unrelatedEntity}; // launcher stock is not rifle stock
|
||||
launcher.well=nullptr;launcher.magazine=nullptr;auto loose=Fresh();ERR_HQAmmoSampler::Inspect(&actor,&group,&loose);
|
||||
assert(loose.Complete&&!loose.LauncherComplete);weapon.muzzles={&muzzle};
|
||||
// Empty/unavailable native launcher muzzles affect only launcher completeness.
|
||||
--- a/tests/test_hq_multiconvoy_supply.py
|
||||
+++ b/tests/test_hq_multiconvoy_supply.py
|
||||
@@ -8,7 +8,7 @@
|
||||
methods='\n'.join(body(p,s) for s in ('void PlanResupply(float now)','bool PlanResupplyTask(ERR_InfantryResupply supply, float now)'))
|
||||
methods=methods.replace('null','nullptr').replace('foreach (','for (')
|
||||
methods=methods.replace('ERR_HQUnit donor;','ERR_HQUnit donor=nullptr;')
|
||||
-for name in ('m_Resupply','supply','task','unit','donor','recipient','mounted','mineRecipient','m_Watch'):
|
||||
+for name in ('m_Resupply','supply','task','unit','donor','recipient','mounted','mineRecipient','launcherRecipient','m_Watch'):
|
||||
methods=methods.replace(name+'.',name+'->')
|
||||
methods=methods.replace('task->Vehicles[1].','task->Vehicles[1]->')
|
||||
run(r'''
|
||||
@@ -24,10 +24,11 @@
|
||||
struct Vehicle{int Group=0;};
|
||||
struct Task{bool Stopped=false,Ready=true;Array<Vehicle*> Vehicles;};using ERR_ConvoyTask=Task*;
|
||||
struct Supply{
|
||||
- int state=1,calls=0,accepted=0,mountedCalls=0,mountedAccepted=0;bool infantry=true,mounted=false,mines=false;int mineCalls=0,mineAccepted=0;Task*task=nullptr;Unit* recipient=nullptr;Entry* demand=nullptr;Entry* donorReport=nullptr;
|
||||
+ int state=1,calls=0,accepted=0,mountedCalls=0,mountedAccepted=0;int launcherCalls=0,launcherAccepted=0;bool launcher=false;bool infantry=true,mounted=false,mines=false;int mineCalls=0,mineAccepted=0;Task*task=nullptr;Unit* recipient=nullptr;Entry* demand=nullptr;Entry* donorReport=nullptr;
|
||||
int State(){return state;}Task*Task(){return task;}
|
||||
bool TryPlan(Unit*r,Unit*,Entry*d,Entry*s,float){calls++;if(!infantry||r!=recipient||!d||!s||d!=demand||s!=donorReport)return false;accepted++;state=2;return true;}
|
||||
bool TryPlanMounted(Unit*r,Unit*,Entry*d,Entry*s,float){mountedCalls++;if(!mounted||r!=recipient||d!=demand||s!=donorReport||!d||!s)return false;mountedAccepted++;state=6;return true;}
|
||||
+ bool TryPlanLauncher(Unit*r,Unit*,Entry*d,Entry*s,float){launcherCalls++;if(!launcher||r!=recipient||d!=demand||s!=donorReport||!d||!s)return false;launcherAccepted++;state=2;return true;}
|
||||
bool TryPlanMines(Unit*r,Unit*,Entry*d,Entry*s,float){mineCalls++;if(!mines||r!=recipient||d!=demand||s!=donorReport||!d||!s)return false;mineAccepted++;state=2;return true;}
|
||||
};using ERR_InfantryResupply=Supply*;
|
||||
struct Pool{Array<Supply*> services;int Count(){return services.Count();}Supply*At(int i){return services[i];}};
|
||||
@@ -43,6 +44,10 @@
|
||||
watch.delivered={{"a",&aStatus},{"b",&bStatus},{"recipient",&demand}};pool.services={&first,&second};hq.m_Resupply=&pool;hq.m_Watch=&watch;hq.m_Units={&da,&db,&recipient};}
|
||||
};
|
||||
int main(){
|
||||
+ {Fixture f;f.first.infantry=false;f.first.launcher=true;f.first.mines=true;f.hq.PlanResupply(100);assert(f.first.launcherAccepted==1&&f.first.mineCalls==0&&f.second.calls==0);}
|
||||
+ {Fixture f;f.first.launcher=true;f.hq.PlanResupply(100);assert(f.first.accepted==1&&f.first.launcherCalls==0);}
|
||||
+ {Fixture f;f.first.infantry=false;f.first.mounted=true;f.first.launcher=true;f.hq.PlanResupply(100);assert(f.first.mountedAccepted==1&&f.first.launcherCalls==0);}
|
||||
+
|
||||
{Fixture f;f.first.infantry=false;f.first.mounted=false;f.first.mines=true;f.hq.PlanResupply(100);assert(f.first.mineAccepted==1&&f.first.mountedCalls>0&&f.second.calls==0);}
|
||||
{Fixture f;f.first.mines=true;f.hq.PlanResupply(100);assert(f.first.accepted==1&&f.first.mineCalls==0);}
|
||||
{Fixture f;f.first.infantry=false;f.first.mounted=true;f.first.mines=true;f.hq.PlanResupply(100);assert(f.first.mountedAccepted==1&&f.first.mineCalls==0);}
|
||||
--- a/tests/test_hq_watch.py
|
||||
+++ b/tests/test_hq_watch.py
|
||||
@@ -136,15 +136,17 @@
|
||||
int main(){
|
||||
// Physical mine evidence is detached at sampling, then copied again only on delivered radio.
|
||||
ERR_HQAmmoSnapshot original;original.Complete=true;original.Members=4;
|
||||
- original.MineComplete=true;original.MineMembers=4;original.ReadyMines=3;
|
||||
+ original.LauncherComplete=true;original.LauncherMuzzles=2;original.MineComplete=true;original.MineMembers=4;original.ReadyMines=3;
|
||||
ERR_HQStatusReport mineWire;mineWire.AmmoState=original.Snapshot();
|
||||
- original.ReadyMines=0;original.MineComplete=false;original.MineMembers=0;
|
||||
+ original.LauncherMuzzles=0;original.ReadyMines=0;original.MineComplete=false;original.MineMembers=0;
|
||||
assert(mineWire.AmmoState!=&original&&mineWire.AmmoState->ReadyMines==3&&mineWire.AmmoState->MineComplete&&mineWire.AmmoState->MineMembers==4);
|
||||
+ assert(mineWire.AmmoState->LauncherMuzzles==2);
|
||||
RadioIntake radio;StatusUnit sender;RouteWire route;sender.Uplink=&route;sender.PendingStatus=&mineWire;
|
||||
radio.Deliver(&sender,1000);assert(!radio.m_Watch.Count()&&sender.PendingStatus==&mineWire); // still travelling
|
||||
route.result=-1;radio.Deliver(&sender,2000);assert(!radio.m_Watch.Count()&&!sender.PendingStatus&&!sender.Uplink); // interrupted route cannot deliver
|
||||
sender.Uplink=&route;sender.PendingStatus=&mineWire;route.result=1;radio.Deliver(&sender,3000);
|
||||
auto mines=radio.m_Watch.Find(mineWire.SourceId);assert(mines&&mines->AmmoState!=mineWire.AmmoState&&mines->AmmoState->ReadyMines==3&&mines->AmmoState->MineComplete&&mines->AmmoState->MineMembers==4);
|
||||
+ assert(mines->AmmoState->LauncherMuzzles==2);mineWire.AmmoState->LauncherMuzzles=0;assert(mines->AmmoState->LauncherMuzzles==2);
|
||||
mineWire.AmmoState->ReadyMines=0;mineWire.AmmoState->MineComplete=false;assert(mines->AmmoState->ReadyMines==3&&mines->AmmoState->MineComplete);
|
||||
// Relabelled stale observations and old radio arrivals cannot overwrite the latest delivered evidence.
|
||||
mineWire.MessageId=2;sender.Uplink=&route;sender.PendingStatus=&mineWire;radio.Deliver(&sender,4000);
|
||||
@@ -158,6 +160,9 @@
|
||||
mineWire.AmmoState->MineMembers=4;mineWire.AmmoState->ReadyMines=513;
|
||||
assert(!radio.m_Watch.Ingest(&mineWire,6000)&&mines->MessageId==2); // impossible finite count
|
||||
mineWire.AmmoState->ReadyMines=0;assert(radio.m_Watch.Ingest(&mineWire,6000)&&mines->AmmoState->MineComplete&&mines->AmmoState->ReadyMines==0);
|
||||
+ mineWire.MessageId=4;mineWire.ObservedAt=6000;mineWire.AmmoState->LauncherMuzzles=-1;
|
||||
+ assert(!radio.m_Watch.Ingest(&mineWire,7000)&&mines->MessageId==3);
|
||||
+ mineWire.AmmoState->LauncherMuzzles=129;assert(!radio.m_Watch.Ingest(&mineWire,7000));
|
||||
// Mounted payload obeys the same delivered chronology and is deep copied.
|
||||
Watch mountedWatch;ERR_HQStatusReport mountedReport;ERR_HQMountedAmmoSnapshot mountedState;
|
||||
mountedState.Complete=true;mountedState.Weapons=1;mountedState.Muzzles=1;mountedState.Magazines=1;
|
||||
--- a/tests/test_infantry_ammo_transfer.py
|
||||
+++ b/tests/test_infantry_ammo_transfer.py
|
||||
@@ -12,6 +12,7 @@
|
||||
code=re.sub(r'//[^\n]*','',code).replace('protected ','').replace('class ','struct ')
|
||||
for src,dst in {
|
||||
'm_Donor.FindComponent(VehicleHelicopterSimulation)':'m_Donor->helicopter',
|
||||
+ 'InventoryItemComponent.Cast(loaded.GetOwner().FindComponent(InventoryItemComponent))':'loaded->GetOwner()->item',
|
||||
'BaseMagazineComponent.Cast(exactMagazine.FindComponent(BaseMagazineComponent))':'exactMagazine->magazine',
|
||||
'BaseWeaponManagerComponent.Cast(actor.FindComponent(BaseWeaponManagerComponent))':'actor->weapons',
|
||||
'BaseWeaponComponent.Cast(rifle.FindComponent(BaseWeaponComponent))':'rifle->weapon',
|
||||
@@ -25,15 +26,16 @@
|
||||
'InventoryStorageManagerComponent.Cast(m_Donor.FindComponent(InventoryStorageManagerComponent))':'m_Donor->inventory',
|
||||
'InventoryStorageManagerComponent.Cast(m_Recipient.FindComponent(InventoryStorageManagerComponent))':'m_Recipient->inventory',
|
||||
}.items():code=code.replace(src,dst)
|
||||
+code=code.replace('SCR_MuzzleInMagComponent.Cast(muzzle)', '(muzzle->inMag ? muzzle : null)')
|
||||
code=code.replace('ref ERR_InfantryAmmoCallback','ERR_InfantryAmmoCallback*').replace('array<','Array<').replace('foreach (','for (').replace('null','nullptr')
|
||||
code=re.sub(r'\bERR_GameMode (?=\w)','ERR_GameMode* ',code)
|
||||
code=code.replace('void ERR_InfantryAmmoTransfer(','ERR_InfantryAmmoTransfer(').replace('EWeaponType kind','int kind').replace('EMuzzleType muzzleKind','int muzzleKind')
|
||||
for name in ('ERR_InfantryAmmoTransferState','ERR_InfantryAmmoAccess','ERR_GameMode','ERR_EnemyEquipmentRules','ERR_ForceRules','Replication','SCR_Global','ERR_Actors','SCR_FactionManager','ERR_ConvoyRules','CompartmentAccessComponent','EWeaponType','EMuzzleType','EStoragePurpose','vector'):
|
||||
code=code.replace(name+'.',name+'::')
|
||||
-for name in ('m_Mode','m_Donor','m_DonorGroup','m_Recipient','m_RecipientGroup','m_Source','m_Destination','m_Storage','m_Callback','magazine','manager','weapon','muzzle','entity','item','storage','inventory','incoming','empty','source','destination','m_ReturnCallback','donorRecord','recipientRecord','utility','usage','physics'):
|
||||
+for name in ('m_Mode','m_Donor','m_DonorGroup','m_Recipient','m_RecipientGroup','m_Source','m_Destination','m_Storage','m_OriginalSlot','m_Callback','magazine','reloadable','loaded','loadedItem','loadedSlot','manager','weapon','muzzle','entity','item','storage','inventory','incoming','empty','source','destination','m_ReturnCallback','donorRecord','recipientRecord','utility','usage','physics'):
|
||||
code=re.sub(r'\b'+name+r'\.',name+'->',code)
|
||||
code=code.replace('GetMagazineWell().','GetMagazineWell()->').replace('GetParentSlot().','GetParentSlot()->').replace('Forces().','Forces()->').replace('m_VehicleMgr.','m_VehicleMgr->').replace('GetPilotCompartmentSlot().','GetPilotCompartmentSlot()->')
|
||||
-code=re.sub(r'\b(bool|int|Vehicle|IEntity|SCR_AIGroup|ERR_ForceRecord|InventoryStorageManagerComponent|BaseInventoryStorageComponent|ERR_GameMode\*|ERR_InfantryAmmoCallback\*) (\w+);',r'\1 \2{};',code)
|
||||
+code=re.sub(r'\b(bool|int|Vehicle|IEntity|SCR_AIGroup|ERR_ForceRecord|InventoryStorageSlot|InventoryStorageManagerComponent|BaseInventoryStorageComponent|ERR_GameMode\*|ERR_InfantryAmmoCallback\*) (\w+);',r'\1 \2{};',code)
|
||||
code=code.replace('\n}\n','\n};\n')
|
||||
callback=callback.replace('class ERR_InfantryAmmoCallback : ScriptedInventoryOperationCallback','').strip()[1:-1]
|
||||
callbacks=[]
|
||||
@@ -63,8 +65,8 @@
|
||||
struct vector{float x=0,y=0,z=0;float Length(){return std::sqrt(x*x+y*y+z*z);}static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}static float Distance(vector a,vector b){return vector{a.x-b.x,a.y-b.y,a.z-b.z}.Length();}};
|
||||
struct Entity;struct Group;struct Inventory;struct Storage;struct Item;struct Slot;struct Magazine;struct Weapon;struct Muzzle;struct Manager;struct Utility;struct Usage;struct Physics_;struct Record;
|
||||
using IEntity=Entity*;using Vehicle=Entity*;using SCR_AIGroup=Group*;using ERR_ForceRecord=Record*;
|
||||
-using InventoryStorageManagerComponent=Inventory*;using BaseInventoryStorageComponent=Storage*;using InventoryItemComponent=Item*;using BaseMagazineComponent=Magazine*;
|
||||
-using BaseWeaponManagerComponent=Manager*;using BaseWeaponComponent=Weapon*;using BaseMuzzleComponent=Muzzle*;
|
||||
+using InventoryStorageSlot=Slot*;using InventoryStorageManagerComponent=Inventory*;using BaseInventoryStorageComponent=Storage*;using InventoryItemComponent=Item*;using BaseMagazineComponent=Magazine*;
|
||||
+using BaseWeaponManagerComponent=Manager*;using BaseWeaponComponent=Weapon*;using BaseMuzzleComponent=Muzzle*;using SCR_MuzzleInMagComponent=Muzzle*;
|
||||
using SCR_AIGroupUtilityComponent=Utility*;using SCR_AIVehicleUsageComponent=Usage*;using Physics=Physics_*;
|
||||
enum ERR_InfantryAmmoTransferState{FAILED=-1,PENDING,SUCCEEDED};
|
||||
struct ERR_InfantryAmmoTransfer;
|
||||
@@ -73,12 +75,12 @@
|
||||
struct EWeaponType{static const int WT_RIFLE=1,WT_SNIPERRIFLE=2,WT_MACHINEGUN=3,WT_HANDGUN=4,WT_GRENADELAUNCHER=5,WT_ROCKETLAUNCHER=6;};
|
||||
struct EMuzzleType{static const int MT_BaseMuzzle=0,MT_UGLMuzzle=1,MT_RPGMuzzle=2;};struct EStoragePurpose{static const int PURPOSE_DEPOSIT=1;};
|
||||
struct Well{int type=1,parent=0;int Type(){return type;}bool IsInherited(int t){return type==t||parent==t;}};
|
||||
-struct Magazine{Well*well=nullptr;int rounds=30,capacity=30;bool used=false;bool IsUsed(){return used;}Well*GetMagazineWell(){return well;}int GetAmmoCount(){return rounds;}int GetMaxAmmoCount(){return capacity;}};
|
||||
-struct Muzzle{Well*well=nullptr;int kind=0;Well*GetMagazineWell(){return well;}int GetMuzzleType(){return kind;}};
|
||||
+struct Magazine{IEntity owner=nullptr;IEntity GetOwner(){return owner;}Well*well=nullptr;int rounds=30,capacity=30;bool used=false;bool IsUsed(){return used;}Well*GetMagazineWell(){return well;}int GetAmmoCount(){return rounds;}int GetMaxAmmoCount(){return capacity;}};
|
||||
+struct Muzzle{bool inMag=false,canReload=true;bool CanBeReloaded(){return canReload;}bool disposable=false;bool IsDisposable(){return disposable;}Magazine*loaded=nullptr;Magazine*GetMagazine(){return loaded;}Well*well=nullptr;int kind=0;Well*GetMagazineWell(){return well;}int GetMuzzleType(){return kind;}};
|
||||
struct Weapon{IEntity owner=nullptr;Array<Muzzle*>muzzles;int kind=1;IEntity GetOwner(){return owner;}int GetWeaponType(){return kind;}void GetMuzzlesList(Array<Muzzle*>&a){a=muzzles;}};
|
||||
struct Manager{Array<IEntity>weapons;void GetWeaponsList(Array<IEntity>&a){a=weapons;}};
|
||||
struct Storage{IEntity owner=nullptr;IEntity GetOwner(){return owner;}};
|
||||
-struct Slot{Storage*storage=nullptr;IEntity occupant=nullptr;Storage*GetStorage(){return storage;}IEntity GetOccupant(){return occupant;}};
|
||||
+struct Slot{Storage*storage=nullptr;IEntity occupant=nullptr;Muzzle*container=nullptr;IEntity attached=nullptr;Muzzle*GetParentContainer(){return container;}IEntity GetAttachedEntity(){return attached;}Storage*GetStorage(){return storage;}IEntity GetOccupant(){return occupant;}};
|
||||
struct Item{Slot*slot=nullptr;Slot*GetParentSlot(){return slot;}};
|
||||
struct Physics_{vector velocity,angular;vector GetVelocity(){return velocity;}vector GetAngularVelocity(){return angular;}};
|
||||
struct Usage{Slot*pilot=nullptr;Slot*GetPilotCompartmentSlot(){return pilot;}};
|
||||
@@ -114,7 +116,7 @@
|
||||
truck.physics=&physics;truck.usage=&usage;usage.pilot=&pilot;pilot.occupant=&driver;driver.group=&donorGroup;driver.vehicle=&truck;spareDriver.group=&donorGroup;spareDriver.vehicle=&truck;
|
||||
recipient.group=&recipientGroup;recipient.position.x=4;truck.inventory=&source;source.owner=&truck;recipient.inventory=&destination;destination.owner=&recipient;
|
||||
sourceStorage.owner=&truck;targetStorage.owner=&recipient;source.storages={&sourceStorage};destination.storages={&targetStorage};destination.selected=&targetStorage;
|
||||
- source.items={&magEntity};magEntity.item=&item;item.slot=&fromSlot;fromSlot.storage=&sourceStorage;toSlot.storage=&targetStorage;magEntity.magazine=&magazine;magazine.well=&well;
|
||||
+ source.items={&magEntity};magEntity.item=&item;item.slot=&fromSlot;fromSlot.storage=&sourceStorage;fromSlot.attached=&magEntity;toSlot.storage=&targetStorage;magEntity.magazine=&magazine;magazine.well=&well;
|
||||
recipient.weapons=&weapons;weapons.weapons={&rifle};rifle.weapon=&weapon;weapon.owner=&rifle;weapon.muzzles={&muzzle};muzzle.well=&well;}
|
||||
ERR_InfantryAmmoTransfer Job(){return ERR_InfantryAmmoTransfer(&mode,&truck,&donorGroup,&recipient,&recipientGroup,&magEntity,true);}
|
||||
void Move(){source.items.RemoveItem(&magEntity);destination.items.Insert(&magEntity);item.slot=&toSlot;}
|
||||
@@ -127,11 +129,11 @@
|
||||
destination.items={&empty,&secondEmpty};destination.selected=nullptr;source.selected=&sourceStorage;}
|
||||
void Return(bool capacity=true){destination.items.RemoveItem(&empty);source.items.Insert(&empty);emptyItem.slot=&emptyTo;if(capacity)destination.selected=&targetStorage;}
|
||||
};
|
||||
-struct ERR_HQAmmoSnapshot{bool Complete=true,LauncherComplete=true;int SmallArmsMuzzles=1,SmallArmsRounds=0,SmallArmsChambers=0;};
|
||||
+struct ERR_HQAmmoSnapshot{bool Complete=true,LauncherComplete=true;int Members=1,LauncherMuzzles=1,LauncherMagazines=1,LauncherRounds=0,LauncherChambers=0;bool Valid(int n){return n==Members;}int SmallArmsMuzzles=1,SmallArmsRounds=0,SmallArmsChambers=0;};
|
||||
struct ERR_HQAmmoSampler{static void Inspect(IEntity actor,SCR_AIGroup group,ERR_HQAmmoSnapshot* stock){assert(actor&&actor->group==group&&stock);}};
|
||||
struct SupplyTruck{IEntity Entity;SCR_AIGroup Group;};
|
||||
struct SupplyRecipient{SCR_AIGroup Group;};
|
||||
-struct Coordinator{ERR_GameMode* m_Mode;SupplyTruck* m_Truck;SupplyRecipient* m_Recipient;
|
||||
+struct Coordinator{bool m_LauncherTrip=false;ERR_GameMode* m_Mode;SupplyTruck* m_Truck;SupplyRecipient* m_Recipient;
|
||||
ERR_InfantryAmmoTransfer* m_Move=nullptr;Array<IEntity>m_Members,m_Stock,m_Spent,m_Served;IEntity m_Receiving=nullptr;
|
||||
int m_Delivered=0,m_TripDelivered=0,finishes=0,audits=0;
|
||||
void Finish(string){finishes++;if(m_Move)m_Move->Stop();}
|
||||
@@ -143,7 +145,34 @@
|
||||
SupplyFixture():supplyTruck{&truck,&donorGroup},supplyRecipient{&recipientGroup}{
|
||||
c.m_Mode=&mode;c.m_Truck=&supplyTruck;c.m_Recipient=&supplyRecipient;c.m_Members={&recipient};c.m_Stock={&magEntity};}
|
||||
};
|
||||
+struct LauncherFixture:SupplyFixture{Entity loaded;Magazine empty;Item loadedItem;Slot loadedSlot;
|
||||
+ LauncherFixture(){c.m_LauncherTrip=true;weapon.kind=EWeaponType::WT_GRENADELAUNCHER;muzzle.kind=EMuzzleType::MT_UGLMuzzle;
|
||||
+ rifle.parent=&recipient;loaded.parent=&rifle;loaded.magazine=∅empty.owner=&loaded;empty.well=&well;empty.rounds=0;muzzle.loaded=∅loaded.item=&loadedItem;loadedItem.slot=&loadedSlot;loadedSlot.container=&muzzle;loadedSlot.attached=&loaded;}
|
||||
+};
|
||||
int main(){
|
||||
+ // Failed callback settles only exact original unchanged custody; unknown never does.
|
||||
+ {LauncherFixture f;f.source.callback=2;f.c.Transfer(0);assert(f.c.m_Move->Submitted()&&!f.c.m_Move->Unresolved()&&f.c.m_Spent.Count()==1);f.c.Transfer(1000);assert(f.source.calls==1&&f.c.m_Spent.Count()==1);}
|
||||
+ for(int bad=0;bad<7;bad++){LauncherFixture f;f.c.Transfer(0);assert(f.c.m_Move->Unresolved());
|
||||
+ if(bad!=0)f.source.last->OnFailed();
|
||||
+ if(bad==1)f.item.slot=nullptr;
|
||||
+ if(bad==2)f.fromSlot.attached=nullptr;
|
||||
+ if(bad==3)f.sourceStorage.owner=&f.foreign;
|
||||
+ if(bad==4)f.destination.items.Insert(&f.magEntity);
|
||||
+ if(bad==5)f.truck.inventory=&f.destination;
|
||||
+ if(bad==6)f.source.items.RemoveItem(&f.magEntity);
|
||||
+ assert(f.c.m_Move->Unresolved());f.c.m_Move->Stop();assert(f.c.m_Move->Unresolved());}
|
||||
+
|
||||
+ {LauncherFixture f;f.muzzle.loaded=nullptr;assert(ERR_InfantryAmmoTransfer::Compatible(&f.recipient,&f.magEntity,true));f.c.Transfer(0);assert(f.source.calls==1&&f.c.m_Move->Submitted());}
|
||||
+
|
||||
+ {LauncherFixture f;assert(!ERR_InfantryAmmoTransfer::Compatible(&f.recipient,&f.magEntity));assert(ERR_InfantryAmmoTransfer::Compatible(&f.recipient,&f.magEntity,true));
|
||||
+ f.c.Transfer(0);assert(f.c.m_Move&&f.c.m_Move->Submitted()&&f.c.m_Spent.Count()==1&&f.source.calls==1);
|
||||
+ f.Move();f.source.last->OnComplete();f.c.Transfer(1000);f.c.Transfer(2000);assert(f.c.m_Delivered==1&&f.source.calls==1&&f.c.m_Served.Count()==1);}
|
||||
+ for(int bad=0;bad<8;bad++){LauncherFixture f;if(bad==0)f.magazine.used=true;if(bad==1)f.muzzle.well=nullptr;if(bad==2)f.muzzle.disposable=true;if(bad==3)f.empty.well=nullptr;if(bad==4)f.loadedSlot.attached=nullptr;if(bad==5)f.rifle.parent=nullptr;if(bad==6)f.empty.capacity=0;if(bad==7){f.muzzle.inMag=true;f.muzzle.canReload=false;}
|
||||
+ f.c.Transfer(0);assert(!f.c.m_Move&&f.source.calls==0&&f.c.m_Spent.IsEmpty());}
|
||||
+ {LauncherFixture f;f.source.accept=false;f.c.Transfer(0);assert(f.c.m_Move&&f.c.m_Move->Submitted()&&f.c.m_Move->Unresolved()&&f.c.m_Spent.Count()==1);f.c.Transfer(1000);assert(f.source.calls==1);f.Move();f.source.last->OnComplete();assert(!f.c.m_Move->Unresolved());}
|
||||
+ {LauncherFixture f;auto t=ERR_InfantryAmmoTransfer(&f.mode,&f.truck,&f.donorGroup,&f.recipient,&f.recipientGroup,&f.magEntity,false,true);
|
||||
+ f.source.effect=[&](){t.Stop();f.Move();};f.source.callback=1;assert(t.Tick(0)==FAILED&&!t.Unresolved()&&f.source.calls==1);assert(t.Tick(1)==FAILED);}
|
||||
+
|
||||
// A capacity recovery uses one exact unused empty, separately proving both native operations.
|
||||
{Fixture f;Entity empty;Magazine emptyMag;Item emptyItem;Slot emptyFrom{&f.targetStorage},emptyTo{&f.sourceStorage};
|
||||
emptyMag.well=&f.well;emptyMag.rounds=0;empty.magazine=&emptyMag;empty.item=&emptyItem;emptyItem.slot=&emptyFrom;
|
||||
--- a/tests/test_infantry_resupply_coordinator.py
|
||||
+++ b/tests/test_infantry_resupply_coordinator.py
|
||||
@@ -50,7 +50,7 @@
|
||||
struct Watch {ERR_HQWatchEntry* entry=nullptr;ERR_HQWatchEntry* Find(int){return entry;}};
|
||||
struct HQ {int token=7,releases=0,downlinks=0;struct Watch watch;Route* next=nullptr;int Journal(){return 0;}int LogisticsToken(Group*){return token;}void ReleaseLogistics(Group*,int owned){releases++;if(token==owned)token=0;}struct Watch* Watch(){return &watch;} Route* Downlink(Group*,string,string,int){downlinks++;return next;}};
|
||||
struct Service {
|
||||
- HQ* m_HQ;Truck* m_Truck;Unit* m_Recipient;Unit* m_Donor;Leaf* m_Move=nullptr;Leaf*m_MineMove=nullptr;bool m_MineTrip=false;int m_MineRecipientSlot=-1;Mounted*m_Mounted=nullptr;Route* m_Order=nullptr;Entity* m_Receiving=nullptr;
|
||||
+ HQ* m_HQ;Truck* m_Truck;Unit* m_Recipient;Unit* m_Donor;Leaf* m_Move=nullptr;Leaf*m_MineMove=nullptr;bool m_MineTrip=false,m_LauncherTrip=false;int m_MineRecipientSlot=-1;Mounted*m_Mounted=nullptr;Route* m_Order=nullptr;Entity* m_Receiving=nullptr;
|
||||
Waypoint* m_MoveWaypoint=nullptr;Waypoint* m_HoldWaypoint=nullptr;Vec m_Rendezvous;int m_State=2,m_Token=7,m_ReportId=11;
|
||||
float m_NextTick=0,m_Deadline=200000,m_HeldAt=-1;bool m_Stopped=false,valid=true,quiet=true,roster=true,waypoints=true;int audits=0,transfers=0,waypointCalls=0,stocks=0;
|
||||
bool Current(){return !m_Stopped;}bool TruckValid(){return valid;}bool Quiet(Unit*){return quiet;}bool Roster(){return roster;}void Audit(string){audits++;}void Stock(float){stocks++;}bool Waypoints(){waypointCalls++;return waypoints;}void Transfer(float){transfers++;}int mineTransfers=0;void TransferMine(float){mineTransfers++;}
|
||||
@@ -60,6 +60,9 @@
|
||||
Fixture(){truck.Combat=&combat;truck.Entity=&entity;s.m_HQ=&hq;s.m_Truck=&truck;s.m_Recipient=&recipient;s.m_Donor=&donor;s.m_Order=ℴhq.next=&next;}
|
||||
};
|
||||
int main(){
|
||||
+ {Fixture f;Leaf leaf;f.s.m_Move=&leaf;f.s.m_LauncherTrip=true;f.truck.SupplyHeld=true;f.s.Stop();assert(f.s.m_State==7&&f.hq.token==7&&f.truck.SupplyHeld);
|
||||
+ f.s.Tick(200000);assert(f.s.m_State==7&&f.hq.token==7&&f.s.transfers==0);leaf.unresolved=false;f.s.Tick(201000);assert(f.s.m_State==1&&f.hq.token==0&&!f.truck.SupplyHeld&&!f.s.m_Move);}
|
||||
+
|
||||
{Fixture f;Leaf leaf;f.s.m_MineMove=&leaf;f.s.m_MineTrip=true;f.truck.SupplyHeld=true;f.s.Stop();assert(f.s.m_State==7&&f.hq.token==7&&f.truck.SupplyHeld&&f.s.m_Recipient);
|
||||
f.s.Tick(200000);assert(f.hq.token==7&&f.s.m_State==7&&f.s.transfers==0&&f.s.mineTransfers==0);leaf.unresolved=false;f.s.Tick(201000);assert(f.hq.token==0&&!f.truck.SupplyHeld&&f.s.m_State==1&&!f.s.m_MineMove);}
|
||||
{Fixture f;f.s.m_State=5;f.s.m_MineTrip=true;f.s.Tick(0);assert(f.s.mineTransfers==1&&f.s.transfers==0);}
|
||||
--- a/tests/test_infantry_resupply_planning.py
|
||||
+++ b/tests/test_infantry_resupply_planning.py
|
||||
@@ -13,7 +13,7 @@
|
||||
s=re.sub(r'foreach \((\w+) (\w+) : ([^)]+)\)',r'for (\1 \2 : \3)',s)
|
||||
for typ in ('InventoryStorageManagerComponent','ERR_ForceRecord','ERR_SupplyConvoyTask'):
|
||||
s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
|
||||
- for name in ('ERR_ForceRules','ERR_InfantryResupplyRules','ERR_InfantryMineTransfer','ERR_ConvoyRules','ERR_ForceKind','ERR_ConvoyResources','ERR_Inventory','ERR_Actors','SCR_FactionManager','CompartmentAccessComponent','Replication','Math'):
|
||||
+ for name in ('ERR_ForceRules','ERR_InfantryResupplyRules','ERR_InfantryMineTransfer','ERR_InfantryAmmoTransfer','ERR_ConvoyRules','ERR_ForceKind','ERR_ConvoyResources','ERR_Inventory','ERR_Actors','SCR_FactionManager','CompartmentAccessComponent','Replication','Math'):
|
||||
s=s.replace(name+'.',name+'::')
|
||||
s=s.replace('ERR_SupplyConvoyTask.Cast','ERR_SupplyConvoyTask::Cast').replace('FindComponent(BaseMagazineComponent)','HasMagazine()')
|
||||
s=s.replace('vector.DistanceXZ','Vec::DistanceXZ').replace('InventoryStorageManagerComponent.Cast','InventoryStorageManagerComponent::Cast').replace('FindComponent(InventoryStorageManagerComponent)','FindComponent()')
|
||||
@@ -24,10 +24,11 @@
|
||||
for name in ('Forces()','GetPrefabData()','Convoys()'):s=s.replace(name+'.',name+'->')
|
||||
s=s.replace('ERR_SupplyConvoyTask* supply', 'Convoy* supply')
|
||||
s=s.replace('bool designated;','bool designated=false;')
|
||||
+ s=s.replace('int checks;', 'int checks=0;')
|
||||
s=s.replace('bool available;','bool available=false;').replace('bool living;','bool living=false;')
|
||||
return s
|
||||
-rules='\n'.join(cpp(body(P,x)) for x in ('static bool Fresh(', 'static bool Need(', 'static bool Pair(', 'static bool MinePair('))
|
||||
-methods='\n'.join(cpp(body(P,x)) for x in ('bool Bind(', 'ERR_ConvoyTask Task(', 'protected bool ConvoyCurrent(', 'protected bool Contains(', 'protected bool TruckValid(', 'protected bool Quiet(', 'protected bool Roster(', 'protected void Stock(', 'protected bool CaptureMines(', 'bool TryPlanMines(', 'bool TryPlan(', 'protected void ClearWaypoints(', 'protected void Finish(', 'void Stop('))
|
||||
+rules='\n'.join(cpp(body(P,x)) for x in ('static bool Fresh(', 'static bool Need(', 'static bool Pair(', 'static bool MinePair(', 'static bool LauncherNeed(', 'static bool LauncherPair('))
|
||||
+methods='\n'.join(cpp(body(P,x)) for x in ('bool Bind(', 'ERR_ConvoyTask Task(', 'protected bool ConvoyCurrent(', 'protected bool Contains(', 'protected bool TruckValid(', 'protected bool Quiet(', 'protected bool Roster(', 'protected void Stock(', 'protected bool CaptureMines(', 'protected bool LauncherStock(', 'bool TryPlanLauncher(', 'bool TryPlanMines(', 'bool TryPlan(', 'protected void ClearWaypoints(', 'protected void Finish(', 'void Stop('))
|
||||
reserve=cpp(body(H,'bool ReserveLogistics('))+cpp(body(H,'void ReleaseLogistics('))
|
||||
unique=cpp(body(I,'static int AppendUnique('))+cpp(body(I,'static int Items('))
|
||||
run(r'''
|
||||
@@ -59,7 +60,7 @@
|
||||
struct ERR_Inventory{
|
||||
'''+unique+r'''
|
||||
};
|
||||
-struct Ammo{bool MineComplete=true;int MineMembers=2,ReadyMines=1;bool Complete=true;int Members=2,SmallArmsMuzzles=2,SmallArmsRounds=60,SmallArmsChambers=0;bool Valid(int n){return Members==n;}};
|
||||
+struct Ammo{bool LauncherComplete=true;int LauncherMuzzles=1,LauncherMagazines=1,LauncherRounds=0,LauncherChambers=0;bool MineComplete=true;int MineMembers=2,ReadyMines=1;bool Complete=true;int Members=2,SmallArmsMuzzles=2,SmallArmsRounds=60,SmallArmsChambers=0;bool Valid(int n){return Members==n;}};
|
||||
struct Entry{bool Closed=false;int Count=2,Fit=2,MessageId=1;float ObservedAt=0;Vec Position;string SourceId;Ammo* AmmoState=nullptr;};using ERR_HQWatchEntry=Entry*;
|
||||
struct ERR_InfantryResupplyRules{
|
||||
'''+rules+r'''
|
||||
@@ -90,7 +91,8 @@
|
||||
Route* Downlink(Group*,string,string,int){downlinks++;return routeAvailable?&route:nullptr;}
|
||||
'''+reserve+r'''
|
||||
};
|
||||
-struct Service{HQ* m_HQ;Mode* m_Mode;Convoy* m_Convoy=nullptr;Truck* m_Truck=nullptr;Unit* m_Recipient=nullptr;Unit* m_Donor=nullptr;Leaf* m_Move=nullptr;Leaf*m_MineMove=nullptr;MineBatch*m_MineBatch=nullptr;bool m_MineTrip=false,m_MineStockCaptured=false,m_EmptyMineReplacement=false;int m_MineRecipientSlot=-1;Mounted*m_Mounted=nullptr;Route* m_Order=nullptr;IEntity m_Receiving=nullptr;
|
||||
+struct ERR_InfantryAmmoTransfer{static inline IEntity only=nullptr;static inline int checks=0;static inline bool compatible=true;static bool Compatible(IEntity actor,IEntity item,bool launcher){checks++;return actor&&item&&launcher&&compatible&&(!only||item==only);}};
|
||||
+struct Service{HQ* m_HQ;Mode* m_Mode;Convoy* m_Convoy=nullptr;Truck* m_Truck=nullptr;Unit* m_Recipient=nullptr;Unit* m_Donor=nullptr;Leaf* m_Move=nullptr;Leaf*m_MineMove=nullptr;MineBatch*m_MineBatch=nullptr;bool m_LauncherTrip=false,m_MineTrip=false,m_MineStockCaptured=false,m_EmptyMineReplacement=false;int m_MineRecipientSlot=-1;Mounted*m_Mounted=nullptr;Route* m_Order=nullptr;IEntity m_Receiving=nullptr;
|
||||
Array<IEntity>m_Stock,m_MineStock,m_Spent,m_Members,m_Served;Map<int>*m_Consumed=new Map<int>();Map<float>*m_NextAttempt=new Map<float>();int m_ConvoySlot=-1;float m_NextTick=0;int m_State=0,m_Trip=0,m_Token=0,m_ReportId=0,m_TripDelivered=0;float m_Deadline=0,m_StockDeadline=-1;bool m_Stopped=false;
|
||||
Waypoint* m_MoveWaypoint=nullptr;Waypoint* m_HoldWaypoint=nullptr;string reason;bool Current(){return !m_Stopped&&Replication::IsServer()&&m_HQ->active;}void Audit(string why){reason=why;}
|
||||
'''+methods+r'''
|
||||
@@ -110,6 +112,17 @@
|
||||
void Unchanged(){assert(s.m_Trip==0&&s.m_Consumed->Get("recipient")==0&&s.m_NextAttempt->Get("recipient")==0&&recipient.LogisticsReservation==0&&truck.SupplyToken==0&&hq.downlinks==0);}
|
||||
};
|
||||
int main(){
|
||||
+ {Fixture f;for(int i=48;i<96;i++)f.inventory.items.Insert(&f.items[i]);f.s.Stock(0);
|
||||
+ ERR_InfantryAmmoTransfer::only=&f.items[95];ERR_InfantryAmmoTransfer::checks=0;
|
||||
+ assert(f.s.TryPlanLauncher(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0));
|
||||
+ assert(ERR_InfantryAmmoTransfer::checks==191);ERR_InfantryAmmoTransfer::only=nullptr;}
|
||||
+
|
||||
+ {Fixture f;f.s.Stock(0);ERR_InfantryAmmoTransfer::compatible=false;assert(!f.s.TryPlanLauncher(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0)&&f.truck.SupplyToken==0);ERR_InfantryAmmoTransfer::compatible=true;}
|
||||
+
|
||||
+ {Fixture f;f.s.Stock(0);f.ammo.SmallArmsRounds=999;f.ammo.LauncherMagazines=0;assert(!f.Plan());assert(f.s.TryPlanLauncher(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0));assert(f.s.m_LauncherTrip&&!f.s.m_MineTrip);}
|
||||
+ for(int bad=0;bad<7;bad++){Fixture f;f.s.Stock(0);f.ammo.SmallArmsRounds=999;if(bad==0)f.ammo.LauncherComplete=false;if(bad==1)f.ammo.LauncherMuzzles=0;if(bad==2)f.ammo.LauncherRounds=1;if(bad==3)f.ammo.LauncherChambers=1;if(bad==4)f.ammo.Members=1;if(bad==5)f.demand.ObservedAt=-1;if(bad==6)f.demand.ObservedAt=1;
|
||||
+ assert(!f.s.TryPlanLauncher(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0));assert(f.s.m_State==1&&f.truck.SupplyToken==0);}
|
||||
+
|
||||
// Additional mounted stock callbacks belong to the same initial freeze barrier.
|
||||
{Fixture f;Cargo mounted;mounted.Finished=false;f.convoy.supply=true;f.convoy.Cargo.Insert(&mounted);
|
||||
f.s.Stock(0);assert(f.s.m_State==0&&f.s.m_Stock.IsEmpty());
|
||||
--- a/tests/test_mounted_supply_integration.py
|
||||
+++ b/tests/test_mounted_supply_integration.py
|
||||
@@ -22,7 +22,7 @@
|
||||
bool Begin(ERR_HQUnit*,ERR_HQUnit*,ERR_HQWatchEntry*,ERR_HQWatchEntry*,float){begins++;busy=allow;return allow;}
|
||||
bool Busy(){return busy;}bool Unresolved(){return unresolved;}void Tick(float){ticks++;}
|
||||
};
|
||||
-struct Service{HQ*m_HQ=nullptr;Mode*m_Mode=nullptr;Convoy*m_Convoy=nullptr;int m_ConvoySlot=0,m_State=1;
|
||||
+struct Service{bool m_LauncherTrip=false;HQ*m_HQ=nullptr;Mode*m_Mode=nullptr;Convoy*m_Convoy=nullptr;int m_ConvoySlot=0,m_State=1;
|
||||
Stock*m_Stock=nullptr,*m_Spent=nullptr;Consumed*m_Consumed=nullptr;Cooldown*m_NextAttempt=nullptr;Leaf*m_Move=nullptr,*m_MineMove=nullptr;ERR_MountedResupply*m_Mounted=nullptr;
|
||||
void Finish(const char*){m_State=1;}bool current=true,valid=true,arrived=false;bool Current(){return current;}bool TruckValid(bool allowArrived=false){return valid&&(!arrived||allowArrived);}
|
||||
'''+methods+r'''
|
||||
@@ -0,0 +1,12 @@
|
||||
check: /tmp/err-policy-yv5hrq37/check.cpp:448: int main(): Assertion `ERR_InfantryAmmoTransfer::Compatible(&f.recipient,&f.magEntity,true)' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-launcher-resupply-1140/tests/test_infantry_ammo_transfer.py", line 323, in <module>
|
||||
run(pre+translated_reservation()+code+'\n'.join(callbacks)+main)
|
||||
~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
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-yv5hrq37/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1,19 @@
|
||||
check: /tmp/err-policy-gec2651y/check.cpp:66: int main(): Assertion `f.first.launcherAccepted==1&&f.first.mineCalls==0&&f.second.calls==0' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-launcher-resupply-1140/tests/test_hq_multiconvoy_supply.py", line 14, in <module>
|
||||
run(r'''
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<54 lines>...
|
||||
}
|
||||
^
|
||||
'''.replace('METHODS',methods).replace('Task*Task()','struct Task*Task()').replace('Task*task=','struct Task*task='))
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
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-gec2651y/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1,12 @@
|
||||
from pathlib import Path
|
||||
import difflib,json,hashlib,subprocess
|
||||
p=Path('/tmp/erri-launcher-resupply-1140');root=Path('/home/jakub/devel/escapistsrereintroduced');paths=sorted([*p.glob('addons/**/*.c'),*p.glob('tests/test_*.py')]);parts=[];manifest={}
|
||||
for f in paths:
|
||||
rel=str(f.relative_to(p));before=(p/'baseline'/rel).read_text();after=f.read_text()
|
||||
if before==after:continue
|
||||
assert (root/rel).read_text()==before,rel+' target changed since baseline'
|
||||
parts.extend(difflib.unified_diff(before.splitlines(True),after.splitlines(True),fromfile='a/'+rel,tofile='b/'+rel))
|
||||
manifest[rel]=hashlib.sha256(f.read_bytes()).hexdigest()
|
||||
(p/'fix.patch').write_text(''.join(parts));(p/'source-manifest.json').write_text(json.dumps(manifest,indent=2)+'\n')
|
||||
r=subprocess.run(['git','apply','--check',str(p/'fix.patch')],cwd=root,text=True,capture_output=True);(p/'apply-check.log').write_text(r.stdout+r.stderr);assert r.returncode==0,r.stderr
|
||||
print(len(manifest),'changed files; apply check passed')
|
||||
@@ -0,0 +1 @@
|
||||
PASS: actual convoy binding, finite identities, disjoint tokens, shared report replay/cooldown, original recipients and HQ cleanup
|
||||
@@ -0,0 +1,89 @@
|
||||
from pathlib import Path
|
||||
import hashlib,json,subprocess
|
||||
R=Path('/home/jakub/devel/escapistsrereintroduced'); P=Path('/tmp/erri-launcher-resupply-1140'); names=['ERR_InfantryResupply.c','ERR_InfantryAmmoTransfer.c','ERR_HQ.c'];baseline={}
|
||||
for name in names:
|
||||
rel='addons/core/Scripts/Game/ERR/10_AI/'+name;src=R/rel
|
||||
for dest in (P/rel,P/'baseline'/rel):dest.parent.mkdir(parents=True,exist_ok=True);dest.write_bytes(src.read_bytes())
|
||||
baseline[rel]={'working_sha256':hashlib.sha256(src.read_bytes()).hexdigest(),'index_sha256':hashlib.sha256(subprocess.check_output(['git','show',':'+rel],cwd=R)).hexdigest()}
|
||||
(P/'baseline.json').write_text(json.dumps(baseline,indent=2)+'\n')
|
||||
p=P/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoTransfer.c';s=p.read_text();s=s.replace('protected bool m_RecycleEmpty;','protected bool m_RecycleEmpty;\n protected bool m_LauncherMagazine;')
|
||||
s=s.replace('bool recycleEmpty = false)','bool recycleEmpty = false, bool launcherMagazine = false)').replace('m_RecycleEmpty = recycleEmpty;','m_RecycleEmpty = recycleEmpty; m_LauncherMagazine = launcherMagazine;')
|
||||
s=s.replace('static bool Compatible(IEntity actor, IEntity exactMagazine)','static bool Compatible(IEntity actor, IEntity exactMagazine, bool launcherMagazine = false)')
|
||||
s=s.replace('array<IEntity> weapons = {}; manager.GetWeaponsList(weapons);','if (launcherMagazine && magazine.IsUsed()) return false;\n array<IEntity> weapons = {}; manager.GetWeaponsList(weapons);')
|
||||
s=s.replace('if (kind != EWeaponType.WT_RIFLE && kind != EWeaponType.WT_SNIPERRIFLE && kind != EWeaponType.WT_MACHINEGUN && kind != EWeaponType.WT_HANDGUN) continue;', '''bool smallWeapon = kind == EWeaponType.WT_RIFLE || kind == EWeaponType.WT_SNIPERRIFLE || kind == EWeaponType.WT_MACHINEGUN || kind == EWeaponType.WT_HANDGUN;
|
||||
if (!launcherMagazine && !smallWeapon) continue;
|
||||
if (launcherMagazine && !smallWeapon && kind != EWeaponType.WT_ROCKETLAUNCHER && kind != EWeaponType.WT_GRENADELAUNCHER) continue;
|
||||
if (launcherMagazine && !StoredUnder(rifle, actor)) continue;''')
|
||||
s=s.replace('if (muzzleKind != EMuzzleType.MT_BaseMuzzle) continue;', '''if (!launcherMagazine && muzzleKind != EMuzzleType.MT_BaseMuzzle) continue;
|
||||
if (launcherMagazine)
|
||||
{
|
||||
if (muzzleKind != EMuzzleType.MT_RPGMuzzle && muzzleKind != EMuzzleType.MT_UGLMuzzle
|
||||
&& kind != EWeaponType.WT_ROCKETLAUNCHER && kind != EWeaponType.WT_GRENADELAUNCHER) continue;
|
||||
// Projectile-only/disposable and entirely missing magazine configurations
|
||||
// are outside this finite magazine slice; never synthesize a projectile.
|
||||
BaseMagazineComponent loaded = muzzle.GetMagazine();
|
||||
if (!loaded || !loaded.GetOwner() || !loaded.GetMagazineWell() || loaded.GetMaxAmmoCount() <= 0) continue;
|
||||
if (!StoredUnder(loaded.GetOwner(), rifle)) continue;
|
||||
if (loaded.GetMagazineWell().Type() != muzzle.GetMagazineWell().Type()) continue;
|
||||
if (magazine.GetMagazineWell().Type() != muzzle.GetMagazineWell().Type()) continue;
|
||||
}''')
|
||||
s=s.replace('if (!Compatible(m_Recipient, m_Magazine))','if (!Compatible(m_Recipient, m_Magazine, m_LauncherMagazine))')
|
||||
s=s.replace('if (m_ReturnAccepted &&', 'if ((m_ReturnAccepted || (m_LauncherMagazine && m_ReturnSubmitted)) &&').replace('if (!m_Accepted || !m_Magazine || m_Acknowledgement < 0) return false;', 'if (m_LauncherMagazine && m_Submitted) return m_Magazine && !PhysicalProof();\n if (!m_Accepted || !m_Magazine || m_Acknowledgement < 0) return false;')
|
||||
s=s.replace('if (PhysicalProof())\n {', '''// Native callbacks may synchronously cancel this leaf or revoke ownership.
|
||||
if (m_LauncherMagazine && m_State != ERR_InfantryAmmoTransferState.PENDING) return m_State;
|
||||
if (m_LauncherMagazine && !Context()) return Fail("launcher custody authority changed during native submission");
|
||||
if (PhysicalProof())
|
||||
{''')
|
||||
p.write_text(s)
|
||||
p=P/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c';s=p.read_text();anchor=' static bool MinePair('
|
||||
s=s.replace(anchor,''' // A zero aggregate without a supported physical magazine is not launcher evidence.
|
||||
static bool LauncherNeed(ERR_HQWatchEntry entry, float now)
|
||||
{
|
||||
return Fresh(entry, now) && entry.AmmoState && entry.AmmoState.Valid(entry.Count)
|
||||
&& entry.AmmoState.LauncherComplete && entry.AmmoState.Members == entry.Count
|
||||
&& entry.AmmoState.LauncherMagazines > 0
|
||||
&& entry.AmmoState.LauncherRounds == 0 && entry.AmmoState.LauncherChambers == 0;
|
||||
}
|
||||
static bool LauncherPair(ERR_HQWatchEntry recipient, ERR_HQWatchEntry donor, float now)
|
||||
{
|
||||
return LauncherNeed(recipient, now) && Fresh(donor, now) && recipient.SourceId != donor.SourceId
|
||||
&& vector.DistanceXZ(recipient.Position, donor.Position) <= 200;
|
||||
}
|
||||
'''+anchor)
|
||||
s=s.replace('protected bool m_MineTrip;','protected bool m_MineTrip;\n protected bool m_LauncherTrip;')
|
||||
s=s.replace('if (m_State <= 1 && m_Move && !m_Move.Unresolved()) m_Move = null;', '''if (m_Move && !m_Move.Unresolved() && (m_State <= 1 || (m_State == 7 && m_LauncherTrip)))
|
||||
{
|
||||
m_Move = null;
|
||||
if (m_State == 7) Finish("launcher custody settled; original claims released without retry or refund");
|
||||
}''')
|
||||
s=s.replace('if (m_MineTrip) reason = "mine top-up: " + reason;', 'if (m_MineTrip) reason = "mine top-up: " + reason;\n if (m_LauncherTrip) reason = "depleted launcher magazine: " + reason;')
|
||||
s=s.replace('float now, bool mineTrip = false)','float now, bool mineTrip = false, bool launcherTrip = false)')
|
||||
s=s.replace('if (m_Truck.Combat.Held || m_Truck.SupplyToken > 0 || !recipient || !donor)', 'if ((mineTrip && launcherTrip) || m_Truck.Combat.Held || m_Truck.SupplyToken > 0 || !recipient || !donor)')
|
||||
s=s.replace('else if (!ERR_InfantryResupplyRules.Pair(demand, donorStatus, now)) return false;', '''else if (launcherTrip)
|
||||
{
|
||||
if (!ERR_InfantryResupplyRules.LauncherPair(demand, donorStatus, now)) return false;
|
||||
}
|
||||
else if (!ERR_InfantryResupplyRules.Pair(demand, donorStatus, now)) return false;''')
|
||||
s=s.replace('m_MineTrip = mineTrip; m_MineRecipientSlot', 'm_MineTrip = mineTrip; m_LauncherTrip = launcherTrip; m_MineRecipientSlot')
|
||||
s=s.replace('if (mineTrip) purpose += "; one finite PMN4 top-up for an existing carrier";', 'if (mineTrip) purpose += "; one finite PMN4 top-up for an existing carrier";\n if (launcherTrip) purpose += "; finite compatible magazine for a reported depleted launcher";')
|
||||
s=s.replace(' bool TryPlanMines(',''' bool TryPlanLauncher(ERR_HQUnit recipient, ERR_HQUnit donor, ERR_HQWatchEntry demand, ERR_HQWatchEntry donorStatus, float now)
|
||||
{
|
||||
return TryPlan(recipient, donor, demand, donorStatus, now, false, true);
|
||||
}
|
||||
bool TryPlanMines(''')
|
||||
s=s.replace('if (m_MineMove && m_MineMove.Unresolved()) { m_State = 7; return; }', 'if ((m_MineMove && m_MineMove.Unresolved()) || (m_LauncherTrip && m_Move && m_Move.Unresolved())) { m_State = 7; return; }')
|
||||
s=s.replace('m_State = 1; m_MineTrip = false;', 'm_State = 1; m_MineTrip = false; m_LauncherTrip = false;')
|
||||
s=s.replace('if (!local.Complete || local.SmallArmsMuzzles <= 0 || local.SmallArmsRounds + local.SmallArmsChambers > 30) continue;', '''if (m_LauncherTrip)
|
||||
{
|
||||
if (!local.Valid(1) || !local.LauncherComplete || local.Members != 1 || local.LauncherMagazines <= 0
|
||||
|| local.LauncherRounds != 0 || local.LauncherChambers != 0) continue;
|
||||
}
|
||||
else if (!local.Complete || local.SmallArmsMuzzles <= 0 || local.SmallArmsRounds + local.SmallArmsChambers > 30) continue;''')
|
||||
s=s.replace('ERR_InfantryAmmoTransfer.Compatible(actor, magazine)', 'ERR_InfantryAmmoTransfer.Compatible(actor, magazine, m_LauncherTrip)')
|
||||
s=s.replace('actor, m_Recipient.Group, magazine, true);','actor, m_Recipient.Group, magazine, true, m_LauncherTrip);')
|
||||
p.write_text(s)
|
||||
p=P/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c';s=p.read_text().replace(' // Optional finite mines are last:', ''' // Supported depleted launcher magazines follow rifle/mounted demand and
|
||||
// share the same finite stock, radio replay and physical rendezvous service.
|
||||
foreach (ERR_HQUnit launcherRecipient : m_Units)
|
||||
if (supply.TryPlanLauncher(launcherRecipient, donor, m_Watch.Find(launcherRecipient.ForceSourceId), donorStatus, now)) return true;
|
||||
// Optional finite mines are last:''');p.write_text(s)
|
||||
@@ -0,0 +1,55 @@
|
||||
from pathlib import Path
|
||||
import hashlib,json,subprocess
|
||||
P=Path('/tmp/erri-launcher-resupply-1140');R=Path('/home/jakub/devel/escapistsrereintroduced');D=P/'addons/core/Scripts/Game/ERR/10_AI';rel='addons/core/Scripts/Game/ERR/10_AI/ERR_HQAmmoSampler.c';s=(R/rel).read_text();(P/'baseline'/rel).write_text(s)
|
||||
b=json.loads((P/'baseline.json').read_text());b[rel]={'working_sha256':hashlib.sha256((R/rel).read_bytes()).hexdigest(),'index_sha256':hashlib.sha256(subprocess.check_output(['git','show',':'+rel],cwd=R)).hexdigest()};(P/'baseline.json').write_text(json.dumps(b,indent=2)+'\n')
|
||||
s=s.replace(' int LauncherRounds;',' int LauncherMuzzles;\n int LauncherRounds;').replace('copy.LauncherRounds = LauncherRounds;', 'copy.LauncherMuzzles = LauncherMuzzles; copy.LauncherRounds = LauncherRounds;')
|
||||
s=s.replace(' if (LauncherRounds < 0', ' if (LauncherMuzzles < 0 || LauncherMuzzles > Members * 32) return false;\n if (LauncherRounds < 0')
|
||||
s=s.replace('else launchers.Insert(muzzle);', '''else
|
||||
{
|
||||
launchers.Insert(muzzle);
|
||||
if (muzzle.GetMagazineWell() && !muzzle.IsDisposable()) result.LauncherMuzzles++;
|
||||
else result.LauncherComplete = false;
|
||||
}''');(P/rel).write_text(s)
|
||||
p=D/'ERR_InfantryResupply.c';s=p.read_text().replace('A zero aggregate without a supported physical magazine is not launcher evidence.', 'Only positively sampled reloadable magazine-well muzzles establish launcher demand.')
|
||||
s=s.replace('entry.AmmoState.LauncherMagazines > 0','entry.AmmoState.LauncherMuzzles > 0').replace('local.LauncherMagazines <= 0','local.LauncherMuzzles <= 0');p.write_text(s)
|
||||
p=D/'ERR_InfantryAmmoTransfer.c';s=p.read_text();s=s.replace(''' // Projectile-only/disposable and entirely missing magazine configurations
|
||||
// are outside this finite magazine slice; never synthesize a projectile.
|
||||
BaseMagazineComponent loaded = muzzle.GetMagazine();
|
||||
if (!loaded || !loaded.GetOwner() || !loaded.GetMagazineWell() || loaded.GetMaxAmmoCount() <= 0) continue;
|
||||
InventoryItemComponent loadedItem = InventoryItemComponent.Cast(loaded.GetOwner().FindComponent(InventoryItemComponent));
|
||||
if (!loadedItem || !loadedItem.GetParentSlot()) continue;
|
||||
InventoryStorageSlot loadedSlot = loadedItem.GetParentSlot();
|
||||
if (loadedSlot.GetParentContainer() != muzzle || loadedSlot.GetAttachedEntity() != loaded.GetOwner()) continue;
|
||||
if (loaded.GetMagazineWell().Type() != muzzle.GetMagazineWell().Type()) continue;''', ''' // An expended single-round item may no longer exist. The real reloadable
|
||||
// muzzle/well proves capability; existing loaded items need exact custody.
|
||||
if (muzzle.IsDisposable()) continue;
|
||||
BaseMagazineComponent loaded = muzzle.GetMagazine();
|
||||
if (loaded)
|
||||
{
|
||||
if (!loaded.GetOwner() || !loaded.GetMagazineWell() || loaded.GetMaxAmmoCount() <= 0) continue;
|
||||
InventoryItemComponent loadedItem = InventoryItemComponent.Cast(loaded.GetOwner().FindComponent(InventoryItemComponent));
|
||||
if (!loadedItem || !loadedItem.GetParentSlot()) continue;
|
||||
InventoryStorageSlot loadedSlot = loadedItem.GetParentSlot();
|
||||
if (loadedSlot.GetParentContainer() != muzzle || loadedSlot.GetAttachedEntity() != loaded.GetOwner()) continue;
|
||||
if (loaded.GetMagazineWell().Type() != muzzle.GetMagazineWell().Type()) continue;
|
||||
}''');p.write_text(s)
|
||||
for name in ('test_infantry_ammo_transfer.py','test_infantry_resupply_planning.py'):
|
||||
p=P/'tests'/name;s=p.read_text().replace('LauncherMagazines=1,','LauncherMuzzles=1,LauncherMagazines=1,')
|
||||
if name=='test_infantry_ammo_transfer.py':
|
||||
s=s.replace('struct Muzzle{Magazine*', 'struct Muzzle{bool disposable=false;bool IsDisposable(){return disposable;}Magazine*')
|
||||
s=s.replace('if(bad==2)f.muzzle.loaded=nullptr;', 'if(bad==2)f.muzzle.disposable=true;')
|
||||
s=s.replace('int main(){', '''int main(){
|
||||
{LauncherFixture f;f.muzzle.loaded=nullptr;assert(ERR_InfantryAmmoTransfer::Compatible(&f.recipient,&f.magEntity,true));f.c.Transfer(0);assert(f.source.calls==1&&f.c.m_Move->Submitted());}
|
||||
''',1)
|
||||
else:s=s.replace('if(bad==1)f.ammo.LauncherMagazines=0;', 'if(bad==1)f.ammo.LauncherMuzzles=0;').replace('f.ammo.SmallArmsRounds=999;assert(!f.Plan());', 'f.ammo.SmallArmsRounds=999;f.ammo.LauncherMagazines=0;assert(!f.Plan());')
|
||||
p.write_text(s)
|
||||
name='test_hq_ammo_sampler.py';s=(R/'tests'/name).read_text();(P/'baseline/tests'/name).write_text(s)
|
||||
s=s.replace('struct Muzzle{Well*', 'struct Muzzle{bool disposable=false;bool IsDisposable(){return disposable;}Well*')
|
||||
s=s.replace('assert(mixed.LauncherMagazines==1&&mixed.KnownSmallArmsEmpty());', '''assert(mixed.LauncherMagazines==1&&mixed.LauncherMuzzles==1&&mixed.KnownSmallArmsEmpty());
|
||||
auto launcherWire=mixed.Snapshot();mixed.LauncherMuzzles=0;assert(launcherWire->LauncherMuzzles==1);
|
||||
launcher.magazine=nullptr;inv.items.clear();auto expended=Fresh();ERR_HQAmmoSampler::Inspect(&actor,&group,&expended);
|
||||
assert(expended.LauncherComplete&&expended.LauncherMuzzles==1&&expended.LauncherRounds==0&&expended.LauncherMagazines==0);
|
||||
launcher.disposable=true;auto disposable=Fresh();ERR_HQAmmoSampler::Inspect(&actor,&group,&disposable);assert(!disposable.LauncherComplete&&disposable.LauncherMuzzles==0);launcher.disposable=false;
|
||||
launcher.magazine=&unrelated;inv.items={&loadedEntity,&spareEntity,&unrelatedEntity};''')
|
||||
# names above depend on existing exact fixture names; use original variable spelling.
|
||||
print([l for l in s.splitlines() if 'Entity actor' in l or 'Entity loaded' in l]);(P/'tests'/name).write_text(s)
|
||||
@@ -0,0 +1,14 @@
|
||||
[
|
||||
{
|
||||
"control": "old-compatible",
|
||||
"returncode": 1,
|
||||
"runtime_assertion_failure": true,
|
||||
"compilation_error": false
|
||||
},
|
||||
{
|
||||
"control": "old-hq",
|
||||
"returncode": 1,
|
||||
"runtime_assertion_failure": true,
|
||||
"compilation_error": false
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,42 @@
|
||||
from pathlib import Path
|
||||
p=Path('/tmp/erri-launcher-resupply-1140');f=p/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoTransfer.c';s=f.read_text();(p/'before-refusal.c').write_text(s)
|
||||
s=s.replace(' protected BaseInventoryStorageComponent m_Storage;', ' protected BaseInventoryStorageComponent m_Storage;\n protected InventoryStorageSlot m_OriginalSlot;\n protected BaseInventoryStorageComponent m_OriginalStorage;')
|
||||
s=s.replace(' protected bool ReturnProof()',''' // Terminal negative callback is settled only with exact unchanged source
|
||||
// custody. Native false without a callback still retains all uncertainty.
|
||||
protected bool RefusedAtSource()
|
||||
{
|
||||
if (!m_Submitted || m_Acknowledgement != -1 || !m_Magazine || !m_Donor || !m_Recipient) return false;
|
||||
if (!m_OriginalSlot || !m_OriginalStorage) return false;
|
||||
if (InventoryStorageManagerComponent.Cast(m_Donor.FindComponent(InventoryStorageManagerComponent)) != m_Source) return false;
|
||||
if (InventoryStorageManagerComponent.Cast(m_Recipient.FindComponent(InventoryStorageManagerComponent)) != m_Destination) return false;
|
||||
if (Ownership(m_Source) != 1 || Ownership(m_Destination) != 0) return false;
|
||||
if (!ManagedStorage(m_Source, m_OriginalStorage, m_Donor)) return false;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(m_Magazine.FindComponent(InventoryItemComponent));
|
||||
if (!item || item.GetParentSlot() != m_OriginalSlot) return false;
|
||||
return m_OriginalSlot.GetStorage() == m_OriginalStorage && m_OriginalSlot.GetAttachedEntity() == m_Magazine;
|
||||
}
|
||||
protected bool ReturnProof()''')
|
||||
s=s.replace('return m_Magazine && !PhysicalProof();', 'return m_Magazine && !PhysicalProof() && !RefusedAtSource();')
|
||||
s=s.replace(' m_Callback = new ERR_InfantryAmmoCallback(); m_Callback.Transfer = this;', ''' if (m_LauncherMagazine)
|
||||
{
|
||||
m_OriginalSlot = item.GetParentSlot(); m_OriginalStorage = m_OriginalSlot.GetStorage();
|
||||
if (m_OriginalSlot.GetAttachedEntity() != m_Magazine) return Fail("launcher source slot lacks reciprocal item proof");
|
||||
}
|
||||
m_Callback = new ERR_InfantryAmmoCallback(); m_Callback.Transfer = this;''');f.write_text(s)
|
||||
f=p/'tests/test_infantry_ammo_transfer.py';s=f.read_text();(p/'before-refusal-test.py').write_text(s)
|
||||
s=s.replace("'m_Storage','m_Callback'", "'m_Storage','m_OriginalSlot','m_Callback'")
|
||||
s=s.replace('bool|int|Vehicle|IEntity|SCR_AIGroup|ERR_ForceRecord|InventoryStorageManagerComponent|', 'bool|int|Vehicle|IEntity|SCR_AIGroup|ERR_ForceRecord|InventoryStorageSlot|InventoryStorageManagerComponent|')
|
||||
s=s.replace('fromSlot.storage=&sourceStorage;', 'fromSlot.storage=&sourceStorage;fromSlot.attached=&magEntity;')
|
||||
s=s.replace('int main(){', '''int main(){
|
||||
// Failed callback settles only exact original unchanged custody; unknown never does.
|
||||
{LauncherFixture f;f.source.callback=2;f.c.Transfer(0);assert(f.c.m_Move->Submitted()&&!f.c.m_Move->Unresolved()&&f.c.m_Spent.Count()==1);f.c.Transfer(1000);assert(f.source.calls==1&&f.c.m_Spent.Count()==1);}
|
||||
for(int bad=0;bad<7;bad++){LauncherFixture f;f.c.Transfer(0);assert(f.c.m_Move->Unresolved());
|
||||
if(bad!=0)f.source.last->OnFailed();
|
||||
if(bad==1)f.item.slot=nullptr;
|
||||
if(bad==2)f.fromSlot.attached=nullptr;
|
||||
if(bad==3)f.sourceStorage.owner=&f.foreign;
|
||||
if(bad==4)f.destination.items.Insert(&f.magEntity);
|
||||
if(bad==5)f.truck.inventory=&f.destination;
|
||||
if(bad==6)f.source.items.RemoveItem(&f.magEntity);
|
||||
assert(f.c.m_Move->Unresolved());f.c.m_Move->Stop();assert(f.c.m_Move->Unresolved());}
|
||||
''',1);f.write_text(s)
|
||||
@@ -0,0 +1,20 @@
|
||||
from pathlib import Path
|
||||
P=Path('/tmp/erri-launcher-resupply-1140');R=Path('/home/jakub/devel/escapistsrereintroduced');d=P/'addons/core/Scripts/Game/ERR/10_AI'
|
||||
p=d/'ERR_InfantryAmmoTransfer.c';s=p.read_text().replace('if (muzzle.IsDisposable()) continue;', '''if (muzzle.IsDisposable()) continue;
|
||||
SCR_MuzzleInMagComponent reloadable = SCR_MuzzleInMagComponent.Cast(muzzle);
|
||||
if (reloadable && !reloadable.CanBeReloaded()) continue;''');p.write_text(s)
|
||||
p=d/'ERR_HQAmmoSampler.c';s=p.read_text().replace('if (muzzle.GetMagazineWell() && !muzzle.IsDisposable()) result.LauncherMuzzles++;', '''SCR_MuzzleInMagComponent reloadable = SCR_MuzzleInMagComponent.Cast(muzzle);
|
||||
bool supported = muzzle.GetMagazineWell() && !muzzle.IsDisposable();
|
||||
if (reloadable && !reloadable.CanBeReloaded()) supported = false;
|
||||
if (supported) result.LauncherMuzzles++;''');p.write_text(s)
|
||||
for n in ['test_infantry_ammo_transfer.py','test_hq_ammo_sampler.py']:
|
||||
p=P/'tests'/n;s=p.read_text().replace("'magazine','loaded','loadedItem'", "'magazine','reloadable','loaded','loadedItem'").replace("'weapon','muzzle','magazine','actor'", "'weapon','muzzle','reloadable','magazine','actor'")
|
||||
# Translate only the native cast boundary.
|
||||
marker="code=code.replace('array<'" if n=='test_hq_ammo_sampler.py' else "code=code.replace('ref ERR_InfantryAmmoCallback'"
|
||||
pos=s.index(marker);s=s[:pos]+"code=code.replace('SCR_MuzzleInMagComponent.Cast(muzzle)', '(muzzle->inMag ? muzzle : nullptr)')\n"+s[pos:]
|
||||
s=s.replace('using BaseMuzzleComponent=Muzzle*;', 'using BaseMuzzleComponent=Muzzle*;using SCR_MuzzleInMagComponent=Muzzle*;')
|
||||
s=s.replace('bool disposable=false;bool IsDisposable()', 'bool inMag=false,canReload=true;bool CanBeReloaded(){return canReload;}bool disposable=false;bool IsDisposable()')
|
||||
if n=='test_infantry_ammo_transfer.py':
|
||||
s=s.replace('bad<7;', 'bad<8;').replace('if(bad==6)f.empty.capacity=0;', 'if(bad==6)f.empty.capacity=0;if(bad==7){f.muzzle.inMag=true;f.muzzle.canReload=false;}')
|
||||
p.write_text(s)
|
||||
n='test_hq_ammo_reporting.py';s=(R/'tests'/n).read_text();(P/'baseline/tests'/n).write_text(s);s=s.replace('LauncherRounds=', 'LauncherMuzzles=0,LauncherRounds=',1);(P/'tests'/n).write_text(s)
|
||||
@@ -0,0 +1,13 @@
|
||||
import os,subprocess,json
|
||||
from pathlib import Path
|
||||
p=Path('/tmp/erri-launcher-resupply-1140');root=Path('/home/jakub/devel/escapistsrereintroduced');env=os.environ.copy();env.update(PYTHONDONTWRITEBYTECODE='1',PYTHONPATH=str(p/'tests')+':'+str(root/'tests'))
|
||||
for key,name in [('ERR_INFANTRY_TRANSFER_SOURCE','ERR_InfantryAmmoTransfer.c'),('ERR_INFANTRY_RESUPPLY_SOURCE','ERR_InfantryResupply.c'),('ERR_HQ_SOURCE','ERR_HQ.c'),('ERR_AMMO_SOURCE','ERR_HQAmmoSampler.c')]:env[key]=str(p/'addons/core/Scripts/Game/ERR/10_AI'/name)
|
||||
results={}
|
||||
for name in ['test_hq_ammo_sampler.py','test_infantry_ammo_transfer.py','test_infantry_resupply_planning.py','test_infantry_resupply_coordinator.py','test_mounted_supply_integration.py','test_hq_multiconvoy_supply.py','test_hq_ammo_reporting.py','test_hq_watch.py','test_infantry_mine_coordinator.py','test_infantry_supply_pool.py','test_hq_status_sampler.py']:
|
||||
f=p/'tests'/name
|
||||
if not f.exists():f=root/'tests'/name
|
||||
r=subprocess.run(['python3',str(f)],env=env,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True)
|
||||
(p/('final-'+name+'.log')).write_text(r.stdout);results[name]=r.returncode;print(name,r.returncode,flush=True)
|
||||
if r.returncode:print(r.stdout[-5000:])
|
||||
(p/'final-results.json').write_text(json.dumps(results,indent=2)+'\n')
|
||||
assert not any(results.values())
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"/tmp/erri-launcher-sdk-review/review.json": "e250680fc7258da65b9cf22bec2776130c23ef3c8d599c1ed81ae07debdbd77b",
|
||||
"/tmp/erri-launcher-sdk-review/files.json": "f1c411d434b04a19c7a8fb5c8bafec1a67a24c1cf5c602710b24e88c8f9be152"
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
[
|
||||
{
|
||||
"path": "Prefabs/Props/Military/Posters/PosterMilitary_USSR_01_RPG7_V1.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "7a83c7ccad975e2f8f88e4c950b34bc94b915a9d7b56afc83d4e7d390381d3c6"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Ammo/Ammo_Grenade_HEDP_M433.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "9f844c35ec648de2deecc9caa31f2c76d0d103ff5415bbeed1589216b9d44197"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Ammo/Ammo_Grenade_HEDP_M433_Penetrator.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "a980aa10985198f436dfdc5634f152006e72e7abe726d3c2d12c85af60039065"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Ammo/Ammo_Grenade_HEDP_M433_Spall.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "c305528da65ce2116711d29721a8adeda4da68e679d992e7a48f5678869ae1d7"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Ammo/Ammo_Grenade_HE_VOG25.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "f8d15fecd55338ef0bdbf1ac431b5cfc5fc97e0ea725c0a5865224eb521ec381"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Ammo/Ammo_Rocket_PG7VL.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "8cd7efec0e969a8a475249d8c9611bfacc4098d6ba3a83d8da01eba7e6132c32"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Ammo/Ammo_Rocket_PG7VL_Penetrator.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "ce50ffe77cf669a085216e908bb7925395a1e5cd1e6792ee4b3885a62cb3b6bc"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Ammo/Ammo_Rocket_PG7VL_Spall.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "d21ac8de32696747ed928c5ec7d4259c27abd3e6f2838481c05fa6cd09769386"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Ammo/Ammo_Rocket_PG7VM.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "de5d58fb7ea86233c991d8127401db301c15c08f67053116d6c497d1c78d4d15"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Ammo/Ammo_Rocket_PG7VM_Penetrator.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "f1c117a733311d33c15f1f75e488817b4abdc5b31ca65ab050543e14f866e496"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Ammo/Ammo_Rocket_PG7VM_Spall.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "4c0975fac8387adad4188b0138c02277c0f8efa89eadb62974cb5c6698a8e1e3"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Ammo/Ammo_Rocket_PG7VR.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "a920dbbe8bdecda239dcb4c9b51a1db612dc85d778e06bc51877ca55b7908792"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Ammo/Ammo_Rocket_PG7VR_Penetrator.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "97ac544c60327103b817dd123bc1959876a371a727290e5e430db3913b220344"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Ammo/Ammo_Rocket_PG7VR_Spall.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "0a0e024ff30df03264a7b56b16b9cfa14e0e1baebde7d8dd19be8cf33c7e4a07"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Ammo/Ammo_Rocket_RPG75.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "994840ab40805787f845daedf7e4374b7bf31e99db2cb8bc3bff53967ab5c0f3"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Ammo/Ammo_Rocket_RPG75_Penetrator.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "636a7afd57ebbdab54401b34171626265f9123f913f7d12fe67e4a80bf83c61c"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Ammo/Ammo_Rocket_RPG75_Spall.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "4c0975fac8387adad4188b0138c02277c0f8efa89eadb62974cb5c6698a8e1e3"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Attachments/Underbarrel/UGL_GP25.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "583085be9199a5400a22b8c8a00d6a4a551f61cca0dfe4ae045f30685e4c9869"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Attachments/Underbarrel/UGL_M203_base.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "8b8a5a23142ac2289f49ae74e866070f7557e299341d1eda47d322c69cdf6e16"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Attachments/Underbarrel/UGL_M203_long.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "85e3da02143360866b113eab303c3170f860e1db623054bcdecf7e8c6a7b3493"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Attachments/Underbarrel/UGL_M203_long_OliveGreen_Sand_Stripes.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "3eaffd9dce72d38762d1469ddbdf52fb13ad64bd6a0941b6d8f867f81890c1ac"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Attachments/Underbarrel/UGL_M203_long_OliveGreen_Solid.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "661eab68d3a80e0b7c00a369ab67ddee0c3d66171f63b81d233e4a22fcea4bf3"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Attachments/Underbarrel/UGL_M203_short.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "ca801708c4c9bb40228568207f4d15c3639cde5c03c43c88592752e3ef552cb9"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Attachments/Underbarrel/UGL_M203_short_OliveGreen_Sand_Stripes.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "1025526f51c01db81f504aec72bda45aad37db9d5dcc12b3c5baa43fcbb16575"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Attachments/Underbarrel/UGL_M203_short_OliveGreen_Solid.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "c56589d45387631eeff5da0ead2e9626ae462ff38c3ae76b02f196d4f9b4fd4f"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Attachments/Underbarrel/UGL_M203_short_Tutorial.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "8ef35b98f3d80925913f99fd4a8969253455af32221256bfa1e07341e0aabae5"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Core/Ammo_GrenadeLauncher_Base.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "74e7fea5f594e699734da2079456e553c0240820dde216fd78e4dab42f30b234"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Core/Ammo_Rocket_Base.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "12609566a239dce9f7419f58f40f9e9a0288aaba183e4515e4cae003b09bcba3"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Core/Launcher_Base.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "e0f9516cc84110973b4b4db04e884405cfc456edbfcd24eeec8aad1d754efba1"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Core/Magazine_Base.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "21807d11d5a91733f91eac8e507f36fe72caa3979355020768014a81745ad383"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Core/UGL_Base.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "0a66f3ea781dbfc6f20acf14206f323b2c4403201586d4b2dca408dde4b214ec"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Launchers/RPG7/Launcher_RPG7.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "ffbf0767dd922cbdf93255dac46f6a4baca6b62a3bc5ed691363b8caea64e0f0"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Launchers/RPG7/Launcher_RPG7_base.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "cb829d2032945964e418db5e6e752a1cd0c2e07777e976ef047a49a9bf06a8b6"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Launchers/RPG7/Launcher_RPG7_PGO7.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "820969cf1ccab107726af8d1b919cffbd545167134a0b4abaacfaf5054d6bee5"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Launchers/RPG75/Launcher_RPG75.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "8808768c102db6254adbdf5d3efaf9383dd5f2c1dba00628ddcbe0fd933c9443"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Launchers/RPG75/Launcher_RPG75_base.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "31d3a1bb7c4fb5aa45df8dcbe42a227eecfd6ea8b6ad47af32cc916d41dd36e5"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Rifles/AK74/Rifle_AK74N_GP25.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "5ef939f733d030c581f271d06b5e6f2dc62b16720c36dd46aad53d7ecd040df1"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Rifles/AK74/Rifle_AK74_GP25.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "2b188c394c09ab581cc5ee4912facac9ee098bfb2cab52d14cd71b0855205c2d"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Rifles/AK74/Variants/Rifle_AK74N_PBS4_GP25.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "f2a6bbc25fb1a18ff2653e7f2fc231a5059c5e65a0d8d8f65d724230126d5819"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Rifles/M16/Rifle_M16A2_carbine_M203.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "05bf78cfe60f5ed93db0b6dc4be416f1fbdc1ab73ae1f6318992418ccd5e6824"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Rifles/M16/Rifle_M16A2_carbine_M203_Tutorial.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "034e88a40e20f1de2d24948f2cf8887ae8d4bb93c912b2db59d2143d954484f6"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Rifles/M16/Rifle_M16A2_M203.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "316ca68a91ff83b7be3872f5d915d37fe2ce1a26cdd422957944a1cb5c26b458"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Rifles/M16/Variants/Rifle_M16A2_carbine_M203_OliveGreen_Sand_Stripes.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "dca6b2f987eb036f0e5545a7540ea7b2db8ebaee877bcf91560624d1ad4a9765"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Rifles/M16/Variants/Rifle_M16A2_carbine_M203_OliveGreen_Solid.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "0740f8339234e439524d2715970644dea1e32609e454ba9d67aea9da83f8ae37"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Rifles/M16/Variants/Rifle_M16A2_M203_OliveGreen_Sand_Stripes.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "08a4853da85da078b2527ee5ff16065062180d3292cb2ce84500498d99daeb51"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Rifles/M16/Variants/Rifle_M16A2_M203_OliveGreen_Solid.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "382ed88099fe66118569d5d2adb4794c1f10ce705f9e07a889d613efecb77293"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Rifles/M16/Variants/Rifle_M16A2_suppressor_M203_OliveGreen_Sand_Stripes.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "e0e1c3137d798a2c61ca887e7c83ab071b79a8d3e8921a99c05bfbc7fb8918f9"
|
||||
},
|
||||
{
|
||||
"path": "Prefabs/Weapons/Warheads/Warhead_HEAT_RPG75.et",
|
||||
"pak": "/home/jakub/.local/share/Steam/steamapps/common/Arma Reforger/addons/data/data007.pak",
|
||||
"sha256": "0dae3a61678ff5561f22472c440771f15946ba3eb919c8d81e4359a7d99303dc"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"scope": "Read-only installed vanilla source/prefab inspection, no engine launch or successor edits",
|
||||
"prefab_manifest": "files.json",
|
||||
"script_hashes": {
|
||||
"/tmp/erri-vanilla-stock/scripts/Game/generated/Weapon/BaseMuzzleComponent.c": "a5662f439b2169db923e90b09d5cfa3505db51a06b15d2ac6429b4113490fcd1",
|
||||
"/tmp/erri-vanilla-stock/scripts/Game/generated/Weapon/BaseMagazineComponent.c": "0162c6bf856e4daf19559d775ffb57b321ba494c32951923b09c1f255dca6bb6",
|
||||
"/tmp/erri-vanilla-stock/scripts/Game/generated/Weapon/MuzzleInMagComponent.c": "5cc80fc54f2bbebe528eafebd1635ff843bff7435895bbe277f0c791bdd7cff1",
|
||||
"/tmp/erri-vanilla-stock/scripts/Game/Components/Weapons/SCR_MuzzleInMagComponent.c": "25fdd35e07cb20d6de5f5b749e4c75511e7b62f507f3700d603f18e131b48665",
|
||||
"/tmp/erri-vanilla-stock/scripts/Game/Inventory/SCR_CharacterInventoryStorageComponent.c": "0df5169d5e3ea2dbbf78066ce77a0dc827a23ccf47a00693077ecab577042190",
|
||||
"/tmp/erri-vanilla-stock/scripts/Game/Inventory/SCR_InventoryStorageManagerComponent.c": "37799972155e2a460c1b9cf66d9be3a54ab7dc2fcfcc33b8d40672973c342023",
|
||||
"/tmp/erri-vanilla-stock/scripts/Game/AI/Components/SCR_AICombatComponent.c": "79919aaf106d2d79caa83c948586cf6c3d284bd4a1f8db2e92179a1921f25cee",
|
||||
"/tmp/erri-vanilla-stock/scripts/Game/UI/Inventory/SCR_InventorySlotUI.c": "3bcb4cbccfb5be7836c07d57bfcdcfff975a0511374fc7b1361722d346afcde5"
|
||||
},
|
||||
"findings": [
|
||||
{
|
||||
"fact": "RPG7 is a reloadable projectile-containing muzzle with authored magazine well independent of loaded item.",
|
||||
"references": [
|
||||
"Prefabs/Weapons/Launchers/RPG7/Launcher_RPG7_base.et:87 SCR_MuzzleInMagComponent",
|
||||
"Prefabs/Weapons/Launchers/RPG7/Launcher_RPG7_base.et:116 MagazineWellRPG7",
|
||||
"Prefabs/Weapons/Launchers/RPG7/Launcher_RPG7.et:27 AmmoTemplate Ammo_Rocket_PG7VM.et",
|
||||
"Components/Weapons/SCR_MuzzleInMagComponent.c:8 default CanBeReloaded=1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"fact": "GP25 and M203 similarly author separate wells and AmmoTemplate on MuzzleInMagComponent.",
|
||||
"references": [
|
||||
"Prefabs/Weapons/Attachments/Underbarrel/UGL_GP25.et:123 MagazineWellRU_GP;169 Ammo_Grenade_HE_VOG25",
|
||||
"Prefabs/Weapons/Attachments/Underbarrel/UGL_M203_base.et:101 MagazineWellUS_UGL;143 Ammo_Grenade_HEDP_M433"
|
||||
]
|
||||
},
|
||||
{
|
||||
"fact": "All three actual round prefabs are Projectile entities with MagazineComponent and corresponding well; RPG7 rocket base MaxAmmo1. A general rejection of Projectile entity type would reject legitimate resupply rounds.",
|
||||
"references": [
|
||||
"Prefabs/Weapons/Ammo/Ammo_Rocket_PG7VM.et:34",
|
||||
"Prefabs/Weapons/Core/Ammo_Rocket_Base.et:42",
|
||||
"Prefabs/Weapons/Ammo/Ammo_Grenade_HE_VOG25.et:35",
|
||||
"Prefabs/Weapons/Ammo/Ammo_Grenade_HEDP_M433.et:34"
|
||||
]
|
||||
},
|
||||
{
|
||||
"fact": "Supported reload and native resupply eligibility inspect muzzle well/resource without requiring GetMagazine nonnull.",
|
||||
"references": [
|
||||
"Inventory/SCR_CharacterInventoryStorageComponent.c:1125-1153",
|
||||
"Inventory/SCR_InventoryStorageManagerComponent.c:1505-1509",
|
||||
"generated/Weapon/BaseMuzzleComponent.c:40-43"
|
||||
]
|
||||
},
|
||||
{
|
||||
"fact": "Disposable weapons require exclusion: RPG75 has Disposable1 and CanBeReloaded0; native AI and resupply use respective gates.",
|
||||
"references": [
|
||||
"Prefabs/Weapons/Launchers/RPG75/Launcher_RPG75_base.et:169-175",
|
||||
"AI/Components/SCR_AICombatComponent.c:526-528",
|
||||
"Inventory/SCR_InventoryStorageManagerComponent.c:1505-1507"
|
||||
]
|
||||
}
|
||||
],
|
||||
"unproven": [
|
||||
"Exact fired-round entity deletion timing versus native projectile reuse and transient GetMagazine lifetime are engine internals, not established by these scripts. No native firing claim.",
|
||||
"SetAmmoCount(0) on an installed round is not proven equivalent to actual firing/chamber removal.",
|
||||
"Native UI uses IsCurrentBarrelChambered for MuzzleInMag current-magazine visibility (SCR_InventorySlotUI.c:958); this supports treating empty chamber independently, not a proof of entity destruction."
|
||||
],
|
||||
"implementation_constraints": [
|
||||
"Match existing finite stocked entity BaseMagazineComponent.GetMagazineWell to exact owned eligible muzzle well; no required empty-magazine identity for projectile-fed launcher admission.",
|
||||
"Refuse IsDisposable or SCR_MuzzleInMagComponent !CanBeReloaded.",
|
||||
"Never synthesize stock from default resource merely because default getter can return a projectile; validate existing physical stock identity, custody and positive ammo/capacity.",
|
||||
"Do not implement empty return for fired projectile rounds based on mock retained-empty assumptions."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c": "52e601a0571b4942c9d4d0ce820727df8498febd6726efbd7da0c5eff7041b58",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQAmmoSampler.c": "8bb0ec1422887feaa340307d3c60bae50fc82d424fce6390876e580ff8b75e8b",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoTransfer.c": "463f8a355aeac40e5ee80981b09e8561fae1b44e179facf911a0fecbfc7722fe",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c": "b01fa948f204d49acbfbdfc5fa641b1d914e0eb91081e92a93816ae52ae88b1e",
|
||||
"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": "7929033fe661de87f9ca72bc33d64fe996ac8b8e3630a17b4210a6f498b2930f",
|
||||
"tests/test_infantry_resupply_coordinator.py": "f9cf7d583a25a12cec9f5f80539b2590449560331f4ef34e33ea2db283c5d54a",
|
||||
"tests/test_infantry_resupply_planning.py": "9929ee0f5a3272c348ee329355b6ba67fdcb745fc9ce1f377f6c989eae91bf9f",
|
||||
"tests/test_mounted_supply_integration.py": "f52acf7a16c9d5273894eb5a4afe7657a7c97de2ed72ba06671d25e163eea5d7"
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
from pathlib import Path
|
||||
P=Path('/tmp/erri-launcher-resupply-1140');d=P/'addons/core/Scripts/Game/ERR/10_AI'
|
||||
p=d/'ERR_InfantryAmmoTransfer.c';s=p.read_text().replace('if (!StoredUnder(loaded.GetOwner(), rifle)) continue;', '''InventoryItemComponent loadedItem = InventoryItemComponent.Cast(loaded.GetOwner().FindComponent(InventoryItemComponent));
|
||||
if (!loadedItem || !loadedItem.GetParentSlot()) continue;
|
||||
InventoryStorageSlot loadedSlot = loadedItem.GetParentSlot();
|
||||
if (loadedSlot.GetParentContainer() != muzzle || loadedSlot.GetAttachedEntity() != loaded.GetOwner()) continue;''');p.write_text(s)
|
||||
p=d/'ERR_InfantryResupply.c';s=p.read_text().replace(' else foreach (IEntity magazine : m_Stock)', ' else if (launcherTrip) available = LauncherStock(recipient);\n else foreach (IEntity magazine : m_Stock)')
|
||||
s=s.replace(' bool TryPlanLauncher(',''' // Compatibility-only preflight of original own carriers; HQ still uses the
|
||||
// delivered report for demand and positions. No item is reserved or moved here.
|
||||
protected bool LauncherStock(ERR_HQUnit recipient)
|
||||
{
|
||||
if (!recipient || !recipient.StatusSampler || m_Stock.Count() > 96) return false;
|
||||
int strength = recipient.StatusSampler.InitialStrength();
|
||||
if (strength < 1 || strength > 32) return false;
|
||||
int checks;
|
||||
foreach (IEntity magazine : m_Stock)
|
||||
{
|
||||
if (!magazine || m_Spent.Contains(magazine) || !Contains(m_Truck.Entity, magazine)) continue;
|
||||
for (int slot = 0; slot < strength; slot++)
|
||||
{
|
||||
if (++checks > 128) return false;
|
||||
IEntity actor = recipient.StatusSampler.InitialMember(slot);
|
||||
if (!actor || !recipient.StatusSampler.CanAttribute(slot, actor) || !ERR_Actors.CanFight(actor)) continue;
|
||||
if (ERR_InfantryAmmoTransfer.Compatible(actor, magazine, true)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool TryPlanLauncher(''');p.write_text(s)
|
||||
p=P/'tests/test_infantry_ammo_transfer.py';s=p.read_text().replace("'BaseMagazineComponent.Cast(exactMagazine.FindComponent(BaseMagazineComponent))':", "'InventoryItemComponent.Cast(loaded.GetOwner().FindComponent(InventoryItemComponent))':'loaded->GetOwner()->item',\n 'BaseMagazineComponent.Cast(exactMagazine.FindComponent(BaseMagazineComponent))':")
|
||||
s=s.replace("'magazine','loaded','manager'", "'magazine','loaded','loadedItem','loadedSlot','manager'")
|
||||
s=s.replace('using InventoryStorageManagerComponent=Inventory*;', 'using InventoryStorageSlot=Slot*;using InventoryStorageManagerComponent=Inventory*;')
|
||||
s=s.replace('struct Slot{Storage*storage=nullptr;IEntity occupant=nullptr;', 'struct Slot{Storage*storage=nullptr;IEntity occupant=nullptr;Muzzle*container=nullptr;IEntity attached=nullptr;Muzzle*GetParentContainer(){return container;}IEntity GetAttachedEntity(){return attached;}')
|
||||
s=s.replace('struct LauncherFixture:SupplyFixture{Entity loaded;Magazine empty;', 'struct LauncherFixture:SupplyFixture{Entity loaded;Magazine empty;Item loadedItem;Slot loadedSlot;')
|
||||
s=s.replace('empty.rounds=0;muzzle.loaded=∅', 'empty.rounds=0;muzzle.loaded=∅loaded.item=&loadedItem;loadedItem.slot=&loadedSlot;loadedSlot.container=&muzzle;loadedSlot.attached=&loaded;')
|
||||
s=s.replace('if(bad==4)f.loaded.parent=nullptr;', 'if(bad==4)f.loadedSlot.attached=nullptr;')
|
||||
p.write_text(s)
|
||||
p=P/'tests/test_infantry_resupply_planning.py';s=p.read_text().replace("'bool TryPlanLauncher(',", "'protected bool LauncherStock(', 'bool TryPlanLauncher(',")
|
||||
s=s.replace("'ERR_InfantryMineTransfer','ERR_ConvoyRules'", "'ERR_InfantryMineTransfer','ERR_InfantryAmmoTransfer','ERR_ConvoyRules'")
|
||||
s=s.replace("s=s.replace('bool available;'", "s=s.replace('int checks;', 'int checks=0;')\n s=s.replace('bool available;'")
|
||||
s=s.replace('struct Service{', 'struct ERR_InfantryAmmoTransfer{static inline bool compatible=true;static bool Compatible(IEntity actor,IEntity item,bool launcher){return actor&&item&&launcher&&compatible;}};\nstruct Service{')
|
||||
s=s.replace('int main(){', '''int main(){
|
||||
{Fixture f;f.s.Stock(0);ERR_InfantryAmmoTransfer::compatible=false;assert(!f.s.TryPlanLauncher(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0)&&f.truck.SupplyToken==0);ERR_InfantryAmmoTransfer::compatible=true;}
|
||||
''')
|
||||
p.write_text(s)
|
||||
@@ -0,0 +1 @@
|
||||
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
|
||||
@@ -0,0 +1,23 @@
|
||||
/tmp/err-policy-h70ccic1/check.cpp: In static member function ‘static void ERR_HQAmmoSampler::Inspect(IEntity, SCR_AIGroup, ERR_HQAmmoSnapshot*)’:
|
||||
/tmp/err-policy-h70ccic1/check.cpp:178:70: error: ‘nullptrptr’ was not declared in this scope; did you mean ‘nullptr_t’?
|
||||
178 | SCR_MuzzleInMagComponent reloadable = (muzzle->inMag ? muzzle : nullptrptr);
|
||||
| ^~~~~~~~~~
|
||||
| nullptr_t
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-launcher-resupply-1140/tests/test_hq_ammo_sampler.py", line 24, in <module>
|
||||
run(r'''
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<140 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-h70ccic1/check.cpp', '-o', '/tmp/err-policy-h70ccic1/check']' returned non-zero exit status 1.
|
||||
@@ -0,0 +1 @@
|
||||
PASS: actual HQ rotates finite donors, skips unavailable/contacted trucks, uses delivered reports and dispatches at most one trip per planning cadence
|
||||
@@ -0,0 +1 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
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
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/tmp/err-policy-98sorxmd/check.cpp: In static member function ‘static bool ERR_InfantryAmmoTransfer::Compatible(IEntity, IEntity, bool)’:
|
||||
/tmp/err-policy-98sorxmd/check.cpp:181:70: error: ‘nullptrptr’ was not declared in this scope; did you mean ‘nullptr_t’?
|
||||
181 | SCR_MuzzleInMagComponent reloadable = (muzzle->inMag ? muzzle : nullptrptr);
|
||||
| ^~~~~~~~~~
|
||||
| nullptr_t
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-launcher-resupply-1140/tests/test_infantry_ammo_transfer.py", line 326, in <module>
|
||||
run(pre+translated_reservation()+code+'\n'.join(callbacks)+main)
|
||||
~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
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-98sorxmd/check.cpp', '-o', '/tmp/err-policy-98sorxmd/check']' returned non-zero exit status 1.
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
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
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
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
|
||||
+1
@@ -0,0 +1 @@
|
||||
PASS: actual convoy binding, finite identities, disjoint tokens, shared report replay/cooldown, original recipients and HQ cleanup
|
||||
@@ -0,0 +1 @@
|
||||
PASS: actual finite convoy supply pool; immutable slots, shared demand state, sparse/capped discovery and permanent shutdown
|
||||
+1
@@ -0,0 +1 @@
|
||||
PASS: actual shared mounted stock/history ownership, atomic exact HQ pair claim, and bounded stopped-owner proof polling
|
||||
@@ -0,0 +1,41 @@
|
||||
from pathlib import Path
|
||||
P=Path('/tmp/erri-launcher-resupply-1140');R=Path('/home/jakub/devel/escapistsrereintroduced');D=P/'tests';D.mkdir(exist_ok=True)
|
||||
for name in ['test_infantry_ammo_transfer.py','test_infantry_resupply_planning.py','test_infantry_resupply_coordinator.py']:
|
||||
s=(R/'tests'/name).read_text();bp=P/'baseline/tests'/name;bp.parent.mkdir(parents=True,exist_ok=True);bp.write_text(s)
|
||||
if name=='test_infantry_ammo_transfer.py':
|
||||
s=s.replace("'magazine','manager','weapon','muzzle'","'magazine','loaded','manager','weapon','muzzle'")
|
||||
s=s.replace('struct Magazine{Well*well=', 'struct Magazine{IEntity owner=nullptr;IEntity GetOwner(){return owner;}Well*well=')
|
||||
s=s.replace('struct Muzzle{Well*well=', 'struct Muzzle{Magazine*loaded=nullptr;Magazine*GetMagazine(){return loaded;}Well*well=')
|
||||
s=s.replace('int SmallArmsMuzzles=1,', 'int Members=1,LauncherMagazines=1,LauncherRounds=0,LauncherChambers=0;bool Valid(int n){return n==Members;}int SmallArmsMuzzles=1,')
|
||||
s=s.replace('struct Coordinator{ERR_GameMode*', 'struct Coordinator{bool m_LauncherTrip=false;ERR_GameMode*')
|
||||
s=s.replace('int main(){', '''struct LauncherFixture:SupplyFixture{Entity loaded;Magazine empty;
|
||||
LauncherFixture(){c.m_LauncherTrip=true;weapon.kind=EWeaponType::WT_GRENADELAUNCHER;muzzle.kind=EMuzzleType::MT_UGLMuzzle;
|
||||
rifle.parent=&recipient;loaded.parent=&rifle;loaded.magazine=∅empty.owner=&loaded;empty.well=&well;empty.rounds=0;muzzle.loaded=∅}
|
||||
};
|
||||
int main(){
|
||||
{LauncherFixture f;assert(!ERR_InfantryAmmoTransfer::Compatible(&f.recipient,&f.magEntity));assert(ERR_InfantryAmmoTransfer::Compatible(&f.recipient,&f.magEntity,true));
|
||||
f.c.Transfer(0);assert(f.c.m_Move&&f.c.m_Move->Submitted()&&f.c.m_Spent.Count()==1&&f.source.calls==1);
|
||||
f.Move();f.source.last->OnComplete();f.c.Transfer(1000);f.c.Transfer(2000);assert(f.c.m_Delivered==1&&f.source.calls==1&&f.c.m_Served.Count()==1);}
|
||||
for(int bad=0;bad<7;bad++){LauncherFixture f;if(bad==0)f.magazine.used=true;if(bad==1)f.muzzle.well=nullptr;if(bad==2)f.muzzle.loaded=nullptr;if(bad==3)f.empty.well=nullptr;if(bad==4)f.loaded.parent=nullptr;if(bad==5)f.rifle.parent=nullptr;if(bad==6)f.empty.capacity=0;
|
||||
f.c.Transfer(0);assert(!f.c.m_Move&&f.source.calls==0&&f.c.m_Spent.IsEmpty());}
|
||||
{LauncherFixture f;f.source.accept=false;f.c.Transfer(0);assert(f.c.m_Move&&f.c.m_Move->Submitted()&&f.c.m_Move->Unresolved()&&f.c.m_Spent.Count()==1);f.c.Transfer(1000);assert(f.source.calls==1);f.Move();f.source.last->OnComplete();assert(!f.c.m_Move->Unresolved());}
|
||||
{LauncherFixture f;auto t=ERR_InfantryAmmoTransfer(&f.mode,&f.truck,&f.donorGroup,&f.recipient,&f.recipientGroup,&f.magEntity,false,true);
|
||||
f.source.effect=[&](){t.Stop();f.Move();};f.source.callback=1;assert(t.Tick(0)==FAILED&&!t.Unresolved()&&f.source.calls==1);assert(t.Tick(1)==FAILED);}
|
||||
''')
|
||||
elif name=='test_infantry_resupply_planning.py':
|
||||
s=s.replace("'static bool MinePair(')","'static bool MinePair(', 'static bool LauncherNeed(', 'static bool LauncherPair(')")
|
||||
s=s.replace("'bool TryPlanMines(',", "'bool TryPlanLauncher(', 'bool TryPlanMines(',")
|
||||
s=s.replace('struct Ammo{', 'struct Ammo{bool LauncherComplete=true;int LauncherMagazines=1,LauncherRounds=0,LauncherChambers=0;')
|
||||
s=s.replace('bool m_MineTrip=false,','bool m_LauncherTrip=false,m_MineTrip=false,')
|
||||
s=s.replace('int main(){','''int main(){
|
||||
{Fixture f;f.s.Stock(0);f.ammo.SmallArmsRounds=999;assert(!f.Plan());assert(f.s.TryPlanLauncher(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0));assert(f.s.m_LauncherTrip&&!f.s.m_MineTrip);}
|
||||
for(int bad=0;bad<7;bad++){Fixture f;f.s.Stock(0);f.ammo.SmallArmsRounds=999;if(bad==0)f.ammo.LauncherComplete=false;if(bad==1)f.ammo.LauncherMagazines=0;if(bad==2)f.ammo.LauncherRounds=1;if(bad==3)f.ammo.LauncherChambers=1;if(bad==4)f.ammo.Members=1;if(bad==5)f.demand.ObservedAt=-1;if(bad==6)f.demand.ObservedAt=1;
|
||||
assert(!f.s.TryPlanLauncher(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0));assert(f.s.m_State==1&&f.truck.SupplyToken==0);}
|
||||
''')
|
||||
else:
|
||||
s=s.replace('m_MineTrip=false','m_MineTrip=false,m_LauncherTrip=false')
|
||||
s=s.replace('int main(){','''int main(){
|
||||
{Fixture f;Leaf leaf;f.s.m_Move=&leaf;f.s.m_LauncherTrip=true;f.truck.SupplyHeld=true;f.s.Stop();assert(f.s.m_State==7&&f.hq.token==7&&f.truck.SupplyHeld);
|
||||
f.s.Tick(200000);assert(f.s.m_State==7&&f.hq.token==7&&f.s.transfers==0);leaf.unresolved=false;f.s.Tick(201000);assert(f.s.m_State==1&&f.hq.token==0&&!f.truck.SupplyHeld&&!f.s.m_Move);}
|
||||
''')
|
||||
(D/name).write_text(s)
|
||||
@@ -0,0 +1 @@
|
||||
PASS: actual bounded empty-return and finite delivery; phase-isolated callbacks, exact ownership proof, stop quarantine and original actor/vehicle boundaries
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"status": "Design proposal only. No production change, prototype, or native run.",
|
||||
"feature": "A finite original armed ground vehicle can receive a radio order to hold a road ahead of remembered player travel, then resume its prior route.",
|
||||
"demonstrated_gap": {
|
||||
"owned_original": "escapists-ai/Scripts/Game/EscapistsAI/EAI_HQRoadblocks.c:103-109 Remember snapshots delivered movement;260-317 Plan projects ahead onto roads and orders physical vehicles;320-345 UpdateUnit boards, drives and holds. EAI_HQ.c:937 forwards received reports.",
|
||||
"successor": "ERR_HQRoadIntercept.Select only provides an infantry task destination. ERR_HQUnit.Eligible excludes seated actors. ERR_HQ.PlanConvoyTask only emits hold/resume-authored-route combat orders; ERR_ConvoyService.ApplyCombatOrder contains no predictive destination. Existing infantry/mine road posts therefore do not implement motorized interception.",
|
||||
"documentation": "docs/ai-feature-migration.md:547 onward explicitly calls infantry interception partial roadblock migration; stale earlier missing row is not relied upon."
|
||||
},
|
||||
"first_slice": [
|
||||
"One simultaneous temporary detour using one already-registered original armed escort of a live ready convoy; no new vehicles, crew, reserve stock, spawning, BMP escalation or engineer deployment. Never use supply/medical trucks. If no admissible original escort exists, do nothing.",
|
||||
"Consume delivered ERR_HQBelief snapshot plus fresh complete own-vehicle status; plan from those coordinates, not target.GetOrigin or global player enumeration. Pin belief ID/revision, original observation time and detached destination in downlink.",
|
||||
"Choose bounded native road segment ahead of credible ground travel; require vehicle-sized placement/road-width clearance and settlement protection. The infantry Standable box is not an adequate vehicle preflight. Do not silently adopt original 700-2500m or live-player exclusions.",
|
||||
"Use actual native move/hold waypoints only after existing physical radio downlink completes; recheck same belief revision/freshness and original vehicle/crew/ledger ownership at delivery. Position verification at arrival concerns own vehicle only.",
|
||||
"Preserve RouteIndex and route ownership. Cancel/expire safely and resume the untouched authored route only while exact original ownership remains eligible. Fixed deadline and one attempt per original delivered revision; no indefinite replenishment/retry."
|
||||
],
|
||||
"required_integration_constraints": [
|
||||
"Convoy Tick currently automatically advances/recreates Waypoint unless Combat.Held or SupplyHeld. A dedicated owned intercept state must gate that path; adding a second independent waypoint controller would conflict. Do not impersonate SupplyHeld or alter Combat.Held semantics.",
|
||||
"Reserve after existing convoy combat and infantry/mounted/mine logistics planning. Atomically exclude LogisticsReservation, SupplyToken, SupplyHeld, pending downlinks, active combat, captured or changed crew, outstanding inventory transactions and depot claims. Existing claim APIs must also reject intercept ownership in the reverse direction.",
|
||||
"ReserveLogistics rejects seated support crews; ReserveMountedLogistics is an atomic donor/recipient pair with a restricted token range. Neither can be reused unchanged for a single-escort interception. Add an exact single-vehicle ownership reservation with a distinct reviewed token domain and conditional release.",
|
||||
"Do not select an Arrived vehicle on the assumption it is idle: ConvoyService.Tick stops/releases the task once all vehicles arrived. First slice must stay within a live original task. Later post-route reuse requires a separate ledger handoff.",
|
||||
"Original report-based expiry, global force cap and active/pending destination deconfliction still apply. The original squad is counted once and cannot simultaneously receive infantry pursuit."
|
||||
],
|
||||
"proposed_interfaces": [
|
||||
"New ERR_HQMountedInterceptRules for bounded report eligibility, road-ahead selection and finite lifetime policy; reuse scalar memory data, not entity telemetry.",
|
||||
"New ERR_HQMountedIntercept controller owned by HQ: Plan(now), Deliver(original unit, now), Tick(now), Stop(); one retained original vehicle/task/receipt/group and own waypoint/token.",
|
||||
"Narrow ERR_ConvoyService hook for atomic claim, temporary waypoint suppression and conditional authored-route restoration; HQ dispatches through ordinary pending downlink with an explicit order kind."
|
||||
],
|
||||
"validation": [
|
||||
"Actual methods: no order from local/undelivered evidence; frozen report does not change when target moves; old revision/stale delivery refuses.",
|
||||
"Actual planning/admission: excludes supply vehicles, altered original crew, released/arrived task, combat, any inventory/depot token or unresolved transaction; exact count and destination cap controls.",
|
||||
"Actual lifecycle: pending-radio loss leaves route untouched; delivered detour suppresses ordinary RouteIndex advancement; expiry and Stop remove only owned waypoint and claim; foreign/replaced claim never cleared; no force creation/refund.",
|
||||
"One opt-in native test, separately authorized later: existing armed original escort physically receives radio order, moves to road, holds, then resumes preserved route; observe native crew, route index and resource identities. Until that passes, source tests alone do not qualify driving or interception effectiveness."
|
||||
],
|
||||
"source_sha256": {
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c": "1f129b979abcb86ee10622e2866aa987807f070f2ab2d818af015e12a8bcfd8c",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQRoadIntercept.c": "0897e82d7805ef56f05f2b9c23903280285ea6bfa51796a2df86bb564ece78f7",
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c": "e1d5dff5584f1bf12c146df510c860c3e0e046c989ff758def88ecf6b49c6392"
|
||||
},
|
||||
"original_sha256": "128776f0b448162ee36cfc71b2a38d2cd06d6d6e4679253f215896aa3fcabb98"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c": {
|
||||
"working_sha256": "4acbd2b23803f1fde3701fbddffd56edd83693196f0be2fe99f3be9b79a12b3d",
|
||||
"index_sha256": "31d08123fa9f49dde380354bd839d1a9deb175cdb940311b4a64dd56eeb04ed7"
|
||||
},
|
||||
"tests/test_hint_delivery.py": {
|
||||
"working_sha256": "1ec462cea5890379ad33ea3f16591896e085d1a30fd1af4a3411b14e5dc8acc3",
|
||||
"index_sha256": "1ec462cea5890379ad33ea3f16591896e085d1a30fd1af4a3411b14e5dc8acc3"
|
||||
},
|
||||
"tests/test_observer_ownership.py": {
|
||||
"working_sha256": "879c6185d834c03f8d62e7e35359c8bb5f6821f6dde03c73957be2ba614947c6",
|
||||
"index_sha256": "879c6185d834c03f8d62e7e35359c8bb5f6821f6dde03c73957be2ba614947c6"
|
||||
},
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_UnconsciousCameraProbe.c": {
|
||||
"working_sha256": "7d5ff3389ee806ac213db5be3a68288b3399142ccc29806df0d65b34a0b2fc3c",
|
||||
"index_sha256": "7b9308c9c35413fa76a7bdc8923f99785fb393ad9d06ba4b36df4d8aa29e574b"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
--- a/addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c
|
||||
+++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c
|
||||
@@ -228,6 +228,7 @@
|
||||
protected bool m_ERRObserverReady;
|
||||
protected ERR_ObserverReason m_ERRShownObserverReason;
|
||||
protected bool m_ERRShownObserverAvailable;
|
||||
+ protected string m_ERRShownRecoveryStatus;
|
||||
protected float m_ERRLastCycle = -1000;
|
||||
protected IEntity m_ERRRecoveryBody;
|
||||
protected float m_ERRRecoveryDeadline = -1;
|
||||
@@ -385,14 +386,18 @@
|
||||
// The server-selected replicated ID distinguishes no target from a target
|
||||
// whose local entity is still streaming. Never imply no survivors from delay.
|
||||
bool survivorAvailable = m_ERRObservedId != RplId.Invalid();
|
||||
- if (ERR_ObserverGuidance.Refresh(m_ERRObserverReady, m_ERRShownObserverReason, m_ERRObserverReason, m_ERRShownObserverAvailable, survivorAvailable))
|
||||
+ string recovery = mode.RecoveryStatus();
|
||||
+ if (ERR_ObserverGuidance.Refresh(m_ERRObserverReady, m_ERRShownObserverReason, m_ERRObserverReason, m_ERRShownObserverAvailable, survivorAvailable) || m_ERRShownRecoveryStatus != recovery)
|
||||
{
|
||||
SCR_HintManagerComponent hints = SCR_HintManagerComponent.GetInstance();
|
||||
if (!hints) return; // retry on the existing monitor; do not lose the first hint
|
||||
- if (!hints.ShowCustom(ERR_ObserverGuidance.Message(m_ERRObserverReason, survivorAvailable), "Spectating", 12)) return;
|
||||
+ string message = ERR_ObserverGuidance.Message(m_ERRObserverReason, survivorAvailable);
|
||||
+ if (!recovery.IsEmpty()) message += "\n" + recovery;
|
||||
+ if (!hints.ShowCustom(message, "Spectating", 12)) return;
|
||||
m_ERRObserverReady = true;
|
||||
m_ERRShownObserverReason = m_ERRObserverReason;
|
||||
m_ERRShownObserverAvailable = survivorAvailable;
|
||||
+ m_ERRShownRecoveryStatus = recovery;
|
||||
}
|
||||
}
|
||||
// Track the actual local body even when an editor/other camera owns the view.
|
||||
@@ -423,6 +428,7 @@
|
||||
protected void ERR_CloseObserverCamera()
|
||||
{
|
||||
m_ERRObserverReady = false;
|
||||
+ m_ERRShownRecoveryStatus = "";
|
||||
if (!m_ERRObserverCamera) return;
|
||||
if (GetGame().GetPlayerController() == this) m_ERRObserverCamera.TrySwitchToControlledEntityCamera();
|
||||
m_ERRObserverCamera.Terminate();
|
||||
--- a/tests/test_hint_delivery.py
|
||||
+++ b/tests/test_hint_delivery.py
|
||||
@@ -4,6 +4,7 @@
|
||||
import os
|
||||
GUIDE=Path(os.environ.get('ERR_GUIDE_SOURCE', ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerGuide.c'))
|
||||
OBSERVER=Path(os.environ.get('ERR_OBSERVER_SOURCE', ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c'))
|
||||
+PROBE=Path(os.environ.get('ERR_OBSERVER_PROBE_SOURCE', ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_UnconsciousCameraProbe.c'))
|
||||
def cpp(text):
|
||||
text=text.replace(' = null;', ' = nullptr;').replace('protected ', '').replace('override ', '').replace('super.OnControlledEntityChanged(from, to);', 'nativeChanges++;')
|
||||
for name in ('ERR_GameMode','ERR_Phase','ERR_ObserverReason','ERR_Actors','ERR_ObjectiveText','SCR_HintManagerComponent','ERR_ObserverGuidance','ERR_ObserverRules','Resource','ETransformMode','SCR_ManualCamera','SCR_RespawnSystemComponent','RplId'):
|
||||
@@ -16,6 +17,7 @@
|
||||
text=text.replace('GetGame().','GetGame()->').replace('EntitySpawnParams params = new EntitySpawnParams();','EntitySpawnParams params;')
|
||||
return text
|
||||
methods=cpp(body(GUIDE,'protected void ERR_StartGuide(')).replace('ERR_StartGuide()', 'StartActual()')+'\n'+cpp(body(GUIDE,'override void OnControlledEntityChanged('))+'\n'+cpp(body(GUIDE,'protected void ERR_UpdateGuide('))+'\n'+cpp(body(OBSERVER,'protected void ERR_UpdateObserverClient('))
|
||||
+methods+='\n'+cpp(body(PROBE,'string ERR_ProbeAcceptedRecoveryStatus('))
|
||||
guidance=cpp(body(OBSERVER,'static string Message('))+'\n'+cpp(body(OBSERVER,'static bool Refresh('))
|
||||
run(r'''#include <cassert>
|
||||
#include <string>
|
||||
@@ -25,7 +27,7 @@
|
||||
namespace ERR_ObserverReason {enum {NONE,SPENT_LIFE,LATE_EXTRACTION,SPAWN_UNAVAILABLE,UNCONSCIOUS};}
|
||||
struct Actor {bool alive=true;bool unconscious=false;} actor;using IEntity=Actor*;
|
||||
struct ERR_Actors {static bool Alive(Actor* a){return a&&a->alive;}static bool CanFight(Actor* a){return Alive(a)&&!a->unconscious;}};
|
||||
-struct ERR_GameMode {int phase=ERR_Phase::PRISON;bool active=true;string message="prison";static ERR_GameMode* current;static ERR_GameMode* Get(){return current;}int Phase(){return phase;}bool Active(){return active;}} mode;
|
||||
+struct ERR_GameMode {string recovery;string RecoveryStatus(){return recovery;}int phase=ERR_Phase::PRISON;bool active=true;string message="prison";static ERR_GameMode* current;static ERR_GameMode* Get(){return current;}int Phase(){return phase;}bool Active(){return active;}} mode;
|
||||
ERR_GameMode* ERR_GameMode::current=&mode;
|
||||
struct ERR_ObjectiveText {static string Description(ERR_GameMode* m,bool){return m->message;}static string Title(int){return "objective";}};
|
||||
struct SCR_HintManagerComponent {static SCR_HintManagerComponent* current;bool accepts=true;int attempts=0,shown=0;string last;static SCR_HintManagerComponent* GetInstance(){return current;}bool ShowCustom(string text,string,int){attempts++;if(!accepts)return false;shown++;last=text;return true;}} hints;
|
||||
@@ -57,9 +59,10 @@
|
||||
Actor* controlled=&actor;int m_ERRObserverReason=ERR_ObserverReason::NONE;
|
||||
SCR_ManualCamera* m_ERRObserverCamera=nullptr;Vec m_ERRObserverAnchor;
|
||||
int m_ERRObservedId=1;bool m_ERRObserverReady=false;
|
||||
+ string m_ERRShownRecoveryStatus;
|
||||
int m_ERRShownObserverReason=0;bool m_ERRShownObserverAvailable=false;int closes=0;
|
||||
Actor* GetControlledEntity(){return controlled;}int ERR_ObserverState(){return m_ERRObserverReason;}
|
||||
- void ERR_CloseObserverCamera(){closes++;m_ERRObserverReady=false;m_ERRObserverCamera=nullptr;}
|
||||
+ void ERR_CloseObserverCamera(){closes++;m_ERRObserverReady=false;m_ERRShownRecoveryStatus="";m_ERRObserverCamera=nullptr;}
|
||||
'''+methods+r'''
|
||||
};
|
||||
void reset(){queue=Queue();gameAvailable=true;mode=ERR_GameMode();ERR_GameMode::current=&mode;hints=SCR_HintManagerComponent();SCR_HintManagerComponent::current=&hints;actor=Actor();Resource::current=&resource;manager.current=&camera;}
|
||||
@@ -135,7 +138,30 @@
|
||||
reset();Controller casualty;actor.unconscious=true;casualty.m_ERRObserverReason=ERR_ObserverReason::UNCONSCIOUS;
|
||||
casualty.ERR_UpdateObserverClient();assert(casualty.m_ERRObserverCamera==&camera&&casualty.controlled==&actor&&hints.last.find("You are unconscious")!=string::npos);
|
||||
casualty.ERR_UpdateObserverClient();assert(hints.shown==1);
|
||||
- actor.unconscious=false;casualty.ERR_UpdateObserverClient();assert(!casualty.m_ERRObserverCamera&&casualty.closes==1&&hints.shown==1);
|
||||
+ // The phase-guide explicitly excludes this server-designated unconscious
|
||||
+ // observer; the existing observer monitor must deliver recovery/defeat facts.
|
||||
+ int attemptsBeforeGuide=hints.attempts;casualty.ERR_UpdateGuide();assert(hints.attempts==attemptsBeforeGuide);
|
||||
+ mode.recovery="Everyone is unconscious with no treatment or recovery progressing. The mission fails after 30 uninterrupted seconds in this state.";
|
||||
+ casualty.ERR_UpdateObserverClient();assert(hints.shown==2&&hints.last.find(mode.recovery)!=string::npos&&casualty.ERR_ProbeAcceptedRecoveryStatus()==mode.recovery);
|
||||
+ casualty.ERR_UpdateObserverClient();assert(hints.shown==2); // stable status never repeats
|
||||
+ mode.recovery="Treatment or natural recovery is possible; the round continues.";hints.accepts=false;
|
||||
+ casualty.ERR_UpdateObserverClient();assert(hints.shown==2&&casualty.m_ERRShownRecoveryStatus.find("30 uninterrupted")!=string::npos);
|
||||
+ hints.accepts=true;casualty.ERR_UpdateObserverClient();assert(hints.shown==3&&hints.last.find(mode.recovery)!=string::npos);
|
||||
+ mode.recovery="A conscious prisoner's reconnection may provide help.";SCR_HintManagerComponent::current=nullptr;
|
||||
+ casualty.ERR_UpdateObserverClient();assert(hints.shown==3&&casualty.m_ERRShownRecoveryStatus.find("natural recovery")!=string::npos);
|
||||
+ SCR_HintManagerComponent::current=&hints;casualty.ERR_UpdateObserverClient();assert(hints.shown==4&&hints.last.find(mode.recovery)!=string::npos);
|
||||
+ mode.recovery="";casualty.ERR_UpdateObserverClient();assert(hints.shown==5&&casualty.m_ERRShownRecoveryStatus.empty()&&hints.last.find("reconnection")==string::npos);
|
||||
+ casualty.ERR_UpdateObserverClient();assert(hints.shown==5);
|
||||
+ // Waking closes the view; a new status cannot keep a recovered observer hint alive.
|
||||
+ mode.recovery="remaining casualty warning";actor.unconscious=false;casualty.ERR_UpdateObserverClient();assert(!casualty.m_ERRObserverCamera&&casualty.closes==1&&hints.shown==5&&casualty.ERR_ProbeAcceptedRecoveryStatus().empty());
|
||||
+ // An old controller cannot consume the replacement's recovery-status update.
|
||||
+ reset();Controller oldCasualty;actor.unconscious=true;oldCasualty.m_ERRObserverReason=ERR_ObserverReason::UNCONSCIOUS;oldCasualty.ERR_UpdateObserverClient();
|
||||
+ Controller newCasualty;newCasualty.m_ERRObserverReason=ERR_ObserverReason::UNCONSCIOUS;mode.recovery="recovery warning after reconnect";int beforeOld=hints.shown;
|
||||
+ oldCasualty.ERR_UpdateObserverClient();assert(hints.shown==beforeOld&&oldCasualty.m_ERRShownRecoveryStatus.empty());
|
||||
+ newCasualty.ERR_UpdateObserverClient();assert(hints.shown==beforeOld+1&&hints.last.find(mode.recovery)!=string::npos);
|
||||
+ mode.active=false;mode.recovery="old round recovery";newCasualty.ERR_UpdateObserverClient();assert(hints.shown==beforeOld+1&&!newCasualty.m_ERRObserverReady&&newCasualty.m_ERRShownRecoveryStatus.empty());
|
||||
+ // A later active round's first observer hint contains only its current fact.
|
||||
+ mode.active=true;mode.recovery="new round recovery";newCasualty.ERR_UpdateObserverClient();assert(hints.shown==beforeOld+2&&hints.last.find("new round recovery")!=string::npos&&hints.last.find("old round recovery")==string::npos);
|
||||
}
|
||||
''')
|
||||
for path,signature in ((GUIDE,'protected void ERR_UpdateGuide('),(OBSERVER,'protected void ERR_UpdateObserverClient(')):
|
||||
--- a/tests/test_observer_ownership.py
|
||||
+++ b/tests/test_observer_ownership.py
|
||||
@@ -23,10 +23,10 @@
|
||||
run(r'''#include <cassert>
|
||||
#include <set>
|
||||
#include <string>
|
||||
-using string=std::string;
|
||||
+struct string:std::string {using std::string::string;using std::string::operator=;string(std::string s):std::string(s){}bool IsEmpty()const{return empty();}};
|
||||
struct Actor {bool alive=true,conscious=true;};using IEntity=Actor*;
|
||||
struct ERR_Actors {static bool Alive(IEntity a){return a&&a->alive;}static bool CanFight(IEntity a){return Alive(a)&&a->conscious;}};
|
||||
-struct ERR_GameMode {bool active=true;static ERR_GameMode*current;static ERR_GameMode*Get(){return current;}bool Active(){return active;}} mode;
|
||||
+struct ERR_GameMode {string RecoveryStatus(){return "";}bool active=true;static ERR_GameMode*current;static ERR_GameMode*Get(){return current;}bool Active(){return active;}} mode;
|
||||
ERR_GameMode*ERR_GameMode::current=&mode;
|
||||
struct ERR_ObserverRules {static bool View(int reason,bool active,bool alive,bool conscious){return active&&((reason==4&&alive&&!conscious)||(reason!=0&&!alive));}};
|
||||
struct ERR_ObserverGuidance {static bool Refresh(bool ready,int oldReason,int reason,bool oldAvailable,bool available){return !ready||oldReason!=reason||oldAvailable!=available;}static string Message(int,bool){return "spectating";}};
|
||||
@@ -58,6 +58,7 @@
|
||||
struct Controller{
|
||||
IEntity actor=nullptr,m_ERRRecoveryBody=nullptr;float m_ERRRecoveryDeadline=-1;
|
||||
bool m_ERRObserverMonitor=false,m_ERRObserverReady=false,m_ERRShownObserverAvailable=false;
|
||||
+ string m_ERRShownRecoveryStatus;
|
||||
int m_ERRObserverReason=0,m_ERRShownObserverReason=0,m_ERRObservedId=1,starts=0;
|
||||
Vec m_ERRObserverAnchor;SCR_ManualCamera*m_ERRObserverCamera=nullptr;CameraBase*playerCamera=nullptr;
|
||||
IEntity GetControlledEntity(){return actor;}CameraBase*GetPlayerCamera(){return playerCamera;}
|
||||
--- a/addons/core/Scripts/Game/ERR/00_Core/ERR_UnconsciousCameraProbe.c
|
||||
+++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_UnconsciousCameraProbe.c
|
||||
@@ -4,6 +4,7 @@
|
||||
modded class SCR_PlayerController
|
||||
{
|
||||
protected IEntity m_ERRCameraProbeTarget;
|
||||
+ string ERR_ProbeAcceptedRecoveryStatus() { return m_ERRShownRecoveryStatus; }
|
||||
void ERR_ProbeCameraTarget(IEntity target) { m_ERRCameraProbeTarget = target; }
|
||||
override IEntity ERR_ObservedEntity()
|
||||
{
|
||||
@@ -91,6 +92,7 @@
|
||||
if (!ERR_CameraCheck(pc.GetControlledEntity() == m_ERRCameraBody && ERR_Actors.Alive(m_ERRCameraBody) && !ERR_Actors.CanFight(m_ERRCameraBody), "possession and living unconscious body unchanged")) return;
|
||||
if (!ERR_CameraCheck(!pc.ERR_ObservedEntity() && vector.DistanceXZ(pc.ERR_ObserverAnchor(), m_ERRCameraBody.GetOrigin()) < 1, "no teammate falls back to the casualty body")) return;
|
||||
if (!ERR_CameraCheck(playerInventory.Contains(m_ERRCameraWeapon), "enabled retention keeps the exact rifle with the unconscious player")) return;
|
||||
+ if (!ERR_CameraCheck(!RecoveryStatus().IsEmpty() && RecoveryStatus().Contains("Treatment or natural recovery") && pc.ERR_ProbeAcceptedRecoveryStatus() == RecoveryStatus(), "accepted observer hint contains the current ongoing recovery status")) return;
|
||||
vector point = ERR_Actors.Ground(m_ERRCameraBody.GetOrigin() + "25 0 25");
|
||||
m_ERRCameraTarget = ERR_Actors.Spawn(Factions().PrisonerPrefab, point); Own(m_ERRCameraTarget);
|
||||
if (!ERR_CameraCheck(m_ERRCameraTarget != null, "physical follow-target stand-in created")) return;
|
||||
@@ -165,6 +167,7 @@
|
||||
{
|
||||
if (!ERR_CameraCheck(ERR_Actors.CanFight(m_ERRCameraBody) && pc.GetControlledEntity() == m_ERRCameraBody && pc.ERR_ObserverState() == ERR_ObserverReason.NONE && !pc.ERR_HasObserverCamera(), "native healing closes spectator and retains the original conscious player")) return;
|
||||
if (!ERR_CameraCheck(GetGame().GetCameraManager().CurrentCamera() == pc.GetPlayerCamera(), "normal player camera restored")) return;
|
||||
+ if (!ERR_CameraCheck(pc.ERR_ProbeAcceptedRecoveryStatus().IsEmpty(), "awakening clears the accepted observer recovery-status cache")) return;
|
||||
if (m_ERRCameraOther) { m_ERRCameraOther.Terminate(); delete m_ERRCameraOther; m_ERRCameraOther = null; }
|
||||
if (!ERR_CameraCheck(character.GetWeaponManagerComponent().GetCurrentWeapon() && character.GetWeaponManagerComponent().GetCurrentWeapon().GetOwner() == m_ERRCameraWeapon, "revival equips the same rifle without ground pickup")) return;
|
||||
if (!ERR_CameraCheck(Settings().Set("keepWeaponOnUnconscious", 0), "retention disabled through persistent setting")) return;
|
||||
@@ -0,0 +1 @@
|
||||
PASS: actual guide ownership and scoped timer cancellation, current-controller preservation/reacquisition, hint retry/dedup and observer boundaries; native UI not exercised
|
||||
+1
@@ -0,0 +1 @@
|
||||
PASS: actual guide ownership and scoped timer cancellation, current-controller preservation/reacquisition, hint retry/dedup and observer boundaries; native UI not exercised
|
||||
+1
@@ -0,0 +1 @@
|
||||
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
|
||||
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"verdict": "PASS for bounded native accepted-hint lifecycle proof; retained engine errors prevent error-free-engine claim",
|
||||
"evidence": "/home/jakub/devel/escapistsrereintroduced/.build/recovery-warning-native-1130/docs/evidence/native-2026-10-02-recovery-warning",
|
||||
"native_report": {
|
||||
"log": "docs/evidence/native-2026-10-02-recovery-warning/console.log.txt",
|
||||
"smoke_completions": 1,
|
||||
"assertions_passed": 29,
|
||||
"script_or_runtime_failures": [],
|
||||
"engine_errors": [
|
||||
"11:40:50.703 RESOURCES (E): Wrong GUID/name for resource @\"{0AFDD005CD669892}Language/rhs_localization.st\" in property \"StringTableSource\"",
|
||||
"11:40:50.703 RESOURCES (E): Wrong GUID/name for resource @\"{41FA2A08B6971D4C}Language/wcs_armament_localization.st\" in property \"StringTableSource\"",
|
||||
"11:40:50.703 RESOURCES (E): Wrong GUID/name for resource @\"{84270E09F7A4A7A0}Language/wcs_spacecore_localization.st\" in property \"StringTableSource\"",
|
||||
"11:40:50.704 RESOURCES (E): Wrong GUID/name for resource @\"{E50E557B468C7073}Language/wcs_bmp1_localization.st\" in property \"StringTableSource\"",
|
||||
"11:41:29.086 WORLD (E): Unknown keyword/data 'Parent' at offset 35955(0x8c73)",
|
||||
"11:41:29.086 WORLD (E): Unknown keyword/data 'Parent' at offset 36915(0x9033)",
|
||||
"11:41:29.087 WORLD (E): Unknown keyword/data 'Parent' at offset 42646(0xa696)",
|
||||
"11:41:29.087 WORLD (E): Unknown keyword/data 'Parent' at offset 43645(0xaa7d)",
|
||||
"11:41:29.087 WORLD (E): Unknown keyword/data 'Parent' at offset 44617(0xae49)",
|
||||
"11:41:29.087 WORLD (E): Unknown keyword/data 'Parent' at offset 45590(0xb216)",
|
||||
"11:41:29.181 WORLD (E): Unknown keyword/data 'SlidingTrackMaterial' at offset 19441(0x4bf1)",
|
||||
"11:41:35.590 WORLD (E): Unknown keyword/data 'm_HardLandingVelocity' at offset 133(0x85)",
|
||||
"11:41:35.590 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"
|
||||
},
|
||||
"assertion_witnesses": [
|
||||
{
|
||||
"line": 578,
|
||||
"text": "11:41:52.276 SCRIPT : ERR SMOKE PASS: unconscious camera accepted observer hint contains the current ongoing recovery status"
|
||||
},
|
||||
{
|
||||
"line": 599,
|
||||
"text": "11:42:03.883 SCRIPT : ERR SMOKE PASS: unconscious camera awakening clears the accepted observer recovery-status cache"
|
||||
}
|
||||
],
|
||||
"source": {
|
||||
"manifest_entries": 407,
|
||||
"launch_end_manifests_equal": true,
|
||||
"frozen_manifest_mismatches": [],
|
||||
"proposal_observer_and_probe_match_frozen_and_current": true
|
||||
},
|
||||
"cleanup": {
|
||||
"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
|
||||
}
|
||||
}
|
||||
},
|
||||
"source_unchanged_after_run": true,
|
||||
"external_addons_unchanged_after_run": true,
|
||||
"runner_error": null
|
||||
},
|
||||
"focused_tests": {
|
||||
"test_observer_ownership.py": {
|
||||
"exit_code": 0,
|
||||
"log": "/tmp/erri-observer-recovery-warning/native-review-test_observer_ownership.py.log"
|
||||
},
|
||||
"test_hint_delivery.py": {
|
||||
"exit_code": 0,
|
||||
"log": "/tmp/erri-observer-recovery-warning/native-review-test_hint_delivery.py.log"
|
||||
}
|
||||
},
|
||||
"ownership_test_review": "Only string IsEmpty adapter, empty RecoveryStatus mode stub and cache field added. Existing ownership cases/assertions unchanged. Actual StartMonitor/UpdateObserverClient/UpdateRecoveryCamera/CloseObserverCamera and binder still exercised.",
|
||||
"limitations": [
|
||||
"Local Workbench scripted fixture only; no multiplayer proof.",
|
||||
"Accepted ShowCustom path and current replicated recovery status verified, not full-resolution readability.",
|
||||
"Thirteen retained external resource/world engine errors remain in report.",
|
||||
"Snapshot predates subsequent launcher changes; no claim those changes were covered."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
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
|
||||
@@ -0,0 +1,19 @@
|
||||
check: /tmp/err-policy-59mygi6e/check.cpp:239: int main(): Assertion `hints.shown==2&&hints.last.find(mode.recovery)!=string::npos&&casualty.ERR_ProbeAcceptedRecoveryStatus()==mode.recovery' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-observer-recovery-warning/tests/test_hint_delivery.py", line 22, in <module>
|
||||
run(r'''#include <cassert>
|
||||
~~~^^^^^^^^^^^^^^^^^^^^^^^
|
||||
#include <string>
|
||||
^^^^^^^^^^^^^^^^^
|
||||
...<141 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-59mygi6e/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"verdict": "Concrete source presentation defect reproduced; minimal proposal fixes it",
|
||||
"before": "All-unconscious status already replicated from RecoveryService contains30s defeat/recovery/reconnect facts. PlayerGuide skips server-designated observers; Observer ignores RecoveryStatus and deduplicates only reason/target availability, so no automatic warning reaches affected unconscious players.",
|
||||
"after": "Existing observer hint appends current replicated recovery status and refreshes only when reason/availability/status changed. Cache advances only on accepted ShowCustom; close clears status; existing ownership and active/conscious gates remain unchanged.",
|
||||
"production_files": [
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c"
|
||||
],
|
||||
"test_files": [
|
||||
"tests/test_hint_delivery.py",
|
||||
"tests/test_observer_ownership.py"
|
||||
],
|
||||
"baseline_red": "red.log:actual method compiles, fails assertion requiring updated medical warning while observer reason/target unchanged",
|
||||
"proposal_green": [
|
||||
"green.log",
|
||||
"ownership-green.log"
|
||||
],
|
||||
"coverage": [
|
||||
"stable message dedup",
|
||||
"status changes stalled\u2192recovering\u2192reserved help\u2192clear",
|
||||
"rejected hint/missing manager retry without cache advance",
|
||||
"ordinary guide cannot deliver this unconscious state",
|
||||
"wake closes presentation",
|
||||
"old controller cannot display replacement status",
|
||||
"inactive round stops hints and clears cache",
|
||||
"new round uses only current status"
|
||||
],
|
||||
"medical_policy_changed": false,
|
||||
"native_launched": false,
|
||||
"target_changed": false,
|
||||
"native_limits": "Getter proves accepted native hint path/state only; no claim of full-resolution readability, manual UI or multiplayer. Existing fixture itself arranges treatment/knockout, unchanged.",
|
||||
"manifest": {
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c": {
|
||||
"working_sha256": "4acbd2b23803f1fde3701fbddffd56edd83693196f0be2fe99f3be9b79a12b3d",
|
||||
"index_sha256": "31d08123fa9f49dde380354bd839d1a9deb175cdb940311b4a64dd56eeb04ed7",
|
||||
"proposal_sha256": "682aa5549470c69fc72d261fbfcf9f7a04a41a66e0101b269ccc00bc282ad05d"
|
||||
},
|
||||
"tests/test_hint_delivery.py": {
|
||||
"working_sha256": "1ec462cea5890379ad33ea3f16591896e085d1a30fd1af4a3411b14e5dc8acc3",
|
||||
"index_sha256": "1ec462cea5890379ad33ea3f16591896e085d1a30fd1af4a3411b14e5dc8acc3",
|
||||
"proposal_sha256": "6066e2b00e54553ac298a3f9b37060a23851f5ca5d4f98eb25e502b2c44240a6"
|
||||
},
|
||||
"tests/test_observer_ownership.py": {
|
||||
"working_sha256": "879c6185d834c03f8d62e7e35359c8bb5f6821f6dde03c73957be2ba614947c6",
|
||||
"index_sha256": "879c6185d834c03f8d62e7e35359c8bb5f6821f6dde03c73957be2ba614947c6",
|
||||
"proposal_sha256": "8e485c3d2e6ae90df82cbc7829996138db8e9a5a4a42a5e2bcaa3bffa61368de"
|
||||
},
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_UnconsciousCameraProbe.c": {
|
||||
"working_sha256": "7d5ff3389ee806ac213db5be3a68288b3399142ccc29806df0d65b34a0b2fc3c",
|
||||
"index_sha256": "7b9308c9c35413fa76a7bdc8923f99785fb393ad9d06ba4b36df4d8aa29e574b",
|
||||
"proposal_sha256": "66b1c4591b0ecc67cb08418a115bb02ed2b0168b7ae970f1e0a1ea335c59dc5b"
|
||||
}
|
||||
},
|
||||
"apply_check": true,
|
||||
"workbench_only_fixture": "ERR_UnconsciousCameraProbe adds accepted-cache getter and2assertions: stage1 nonempty actual RecoveryStatus containing treatment/natural-recovery branch equals accepted hint cache; stage4 awakening clears cache. No synthetic policy/timing change."
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c": {
|
||||
"working_sha256": "4acbd2b23803f1fde3701fbddffd56edd83693196f0be2fe99f3be9b79a12b3d",
|
||||
"index_sha256": "31d08123fa9f49dde380354bd839d1a9deb175cdb940311b4a64dd56eeb04ed7",
|
||||
"proposal_sha256": "682aa5549470c69fc72d261fbfcf9f7a04a41a66e0101b269ccc00bc282ad05d"
|
||||
},
|
||||
"tests/test_hint_delivery.py": {
|
||||
"working_sha256": "1ec462cea5890379ad33ea3f16591896e085d1a30fd1af4a3411b14e5dc8acc3",
|
||||
"index_sha256": "1ec462cea5890379ad33ea3f16591896e085d1a30fd1af4a3411b14e5dc8acc3",
|
||||
"proposal_sha256": "6066e2b00e54553ac298a3f9b37060a23851f5ca5d4f98eb25e502b2c44240a6"
|
||||
},
|
||||
"tests/test_observer_ownership.py": {
|
||||
"working_sha256": "879c6185d834c03f8d62e7e35359c8bb5f6821f6dde03c73957be2ba614947c6",
|
||||
"index_sha256": "879c6185d834c03f8d62e7e35359c8bb5f6821f6dde03c73957be2ba614947c6",
|
||||
"proposal_sha256": "8e485c3d2e6ae90df82cbc7829996138db8e9a5a4a42a5e2bcaa3bffa61368de"
|
||||
},
|
||||
"addons/core/Scripts/Game/ERR/00_Core/ERR_UnconsciousCameraProbe.c": {
|
||||
"working_sha256": "7d5ff3389ee806ac213db5be3a68288b3399142ccc29806df0d65b34a0b2fc3c",
|
||||
"index_sha256": "7b9308c9c35413fa76a7bdc8923f99785fb393ad9d06ba4b36df4d8aa29e574b",
|
||||
"proposal_sha256": "66b1c4591b0ecc67cb08418a115bb02ed2b0168b7ae970f1e0a1ea335c59dc5b"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"attempt": "baseline capture", "failed_output": "index-before.diff", "partial_bytes": 36691968, "removed_only_new_incomplete_file": true, "replacement": "index-before-sha256.json"}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user