Spaces:
Running
Running
Download src/galactic-operations.js from CREATORJD/massfront-playtest: direct link, hf CLI and curl.
- Browser
- Download file 86.3 kB
-
https://huggingface.co/spaces/CREATORJD/massfront-playtest/resolve/main/src/galactic-operations.js
- Command line
-
hf download hf://spaces/CREATORJD/massfront-playtest/src/galactic-operations.js
-
curl -L -o galactic-operations.js https://huggingface.co/spaces/CREATORJD/massfront-playtest/resolve/main/src/galactic-operations.js
86.3 kB
| ; | |
| ; | |
| /* ============================================================================ | |
| GALACTIC OPERATIONS — isolated NEXUS-VII -> base RTS tactical bridge | |
| ---------------------------------------------------------------------------- | |
| The exploration shell and the base game are separate documents so their | |
| WebGL renderers never compete. They exchange only short-lived same-tab | |
| session records addressed by opaque nonces. The base career remains live for | |
| menus, but match setup runs against a throwaway META and every career reward | |
| seam is closed while an integrated operation is active. | |
| ============================================================================ */ | |
| (function(){ | |
| const ENTRY_KEY='massfront.galactic.entry.v1'; | |
| const REQUEST_PREFIX='massfront.galactic.request.v1.'; | |
| const RESULT_PREFIX='massfront.galactic.result.v1.'; | |
| const ROUTE_PREFIX='massfront.galactic.route.v1.'; | |
| const CLASSIC_FALLBACK_KEY='massfront.galactic.classic-fallback.v1'; | |
| const CLASSIC_FALLBACK_SEARCH='?galacticFallback=classic'; | |
| const CONTENT_VERSION='catalog-8'; | |
| const LEGACY_CONTENT_VERSION='catalog-7'; | |
| const NONCE_RE=/^[A-Za-z0-9_-]{16,128}$/; | |
| const BASE_ROUTE_IDS=new Set(['operations','development','armory','orders','intel', | |
| 'profile','inbox','social','settings','game-version','war-room','mode-training','mode-standard', | |
| 'mode-campaign','mode-weekly','new-career-faction']); | |
| const PROXY_MAP={nova:'nova',dominion:'legion',syndicate:'syndicate'}; | |
| /* Secured menu routes carry only their UGA origin. They must not rewrite the | |
| base War Table to a similarly themed homeworld: Veyra is not Nordhall and | |
| Karak is not Vespera. Standard mode therefore opens at its real galaxy | |
| stage, while authored operations use the separate battlefield authority | |
| below after the player explicitly chooses a UGA map. */ | |
| const GALACTIC_EXPEDITION_LOCATION_CONTRACT={ | |
| aelos:new Set(['nexus_vii','aelos_caldris','aelos_ithara','aelos_zephyros','aelos_embassy_spindle','aelos_logistics_array','aelos_veyra_gate','aelos_heliograph','aelos_freeport']), | |
| veyra:new Set(['nexus_vii','veyra_orison','veyra_nacre','veyra_archive_hulk','veyra_aelos_gate','veyra_karak_gate','veyra_lens','veyra_ossuary']), | |
| karak:new Set(['nexus_vii','karak_meridian','karak_tethys','karak_colony_spine','karak_lifeboat_field','karak_veyra_gate','karak_spine','karak_hive']) | |
| }; | |
| function expeditionLocationAllowed(location){ | |
| const targets=GALACTIC_EXPEDITION_LOCATION_CONTRACT[location&&location.systemId]; | |
| const targetId=location&&location.targetId; | |
| if(!targets||(targetId!==null&&targetId!==undefined&&!targets.has(targetId)))return null; | |
| return {systemId:location.systemId,targetId:targetId==null?null:targetId}; | |
| } | |
| const UGA_GROUND_LOCATION_KIND='UgaGroundLocationV1'; | |
| function ugaGroundArea(id,systemId,planetId,planetName,siteId,name,missionId,runtimeRegionId,mapNames){ | |
| return {schemaVersion:1,id,systemId,planetId,planetName,siteId,name,missionId,recommendedMapId:id+'_standard',maps:[ | |
| {id:id+'_compact',size:'compact',name:mapNames[0],runtimeTemplateMapId:runtimeRegionId+'_small'}, | |
| {id:id+'_standard',size:'standard',name:mapNames[1],runtimeTemplateMapId:runtimeRegionId+'_medium'}, | |
| {id:id+'_large',size:'large',name:mapNames[2],runtimeTemplateMapId:runtimeRegionId+'_large'} | |
| ]}; | |
| } | |
| /* Difficulty layering for a region's three maps, expressed ONLY in values the | |
| War Table Standard setup already offers — timeRow 0/300/600/900/1500, | |
| paceRow 0.7/1/1.6, crRow 1/0, defFocusRow 0/1. Nothing new is invented here; | |
| these are the same functions a Standard match configures, finally driven by | |
| which map of the region you dropped on instead of by constants. | |
| Before this, every UGA operation ran timeLimit=1200, resPace=1, crateRate=1, | |
| defenseFocus=0 no matter the region or the map — so compact and large played | |
| identically and the only thing that moved was AI difficulty. 1200 was not | |
| even one of the authored timer options. | |
| The ladder: compact is a short rich scramble, standard is the neutral | |
| reference match, large is a long lean grind that rewards fortifying. */ | |
| const UGA_GROUND_TACTICAL_PROFILES={ | |
| compact: {tier:1,timeLimit:600, resPace:1.6,crateRate:1,defenseFocus:0,wildcards:0,enemies:1}, | |
| standard:{tier:2,timeLimit:900, resPace:1, crateRate:1,defenseFocus:0,wildcards:1,enemies:1}, | |
| large: {tier:3,timeLimit:1500,resPace:0.7,crateRate:1,defenseFocus:1,wildcards:2,enemies:2} | |
| }; | |
| /* Enemies never take sw or se — those are the two HQ landing zones a player | |
| can choose, and an enemy sharing the player's spawn is not a difficulty | |
| setting, it is a broken match. */ | |
| const UGA_ENEMY_ZONES=['ne','nw','c']; | |
| function ugaGroundTacticalProfile(size){ | |
| return UGA_GROUND_TACTICAL_PROFILES[size]||UGA_GROUND_TACTICAL_PROFILES.standard; | |
| } | |
| /* The HQ Ship Landing Zone the player actually chose in the deployment planner. | |
| Every mission authors two, the operation carries the choice as landingZoneId, | |
| and the bridge used to discard it and spawn at 'sw' every single time — so | |
| one of the two real decisions on the deployment screen did nothing. | |
| The primary zone keeps 'sw', opposite the enemy at 'ne'; the alternate takes | |
| 'se', which starts closer to them. That is the tactical difference the choice | |
| is offering, expressed in the same spawn zones Standard already uses. | |
| tools/test-uga-deployment-bridge.mjs asserts this covers exactly the catalog's | |
| landing zones, so adding one without a spawn fails the build. */ | |
| const UGA_LANDING_ZONE_SPAWNS={ | |
| relay_shadow:'sw', maintenance_spar:'se', | |
| customs_ring:'sw', cargo_lock:'se', | |
| service_lock:'sw', freight_shadow:'se', | |
| broken_spine:'sw', aft_lattice:'se', | |
| umbra_platform:'sw', coolant_trench:'se', | |
| vault_aperture:'sw', collapsed_gallery:'se', | |
| clinic_roof:'sw', transit_court:'se', | |
| maintenance_shaft:'sw', sealed_platform:'se', | |
| vascular_breach:'sw', thermal_vent:'se' | |
| }; | |
| function ugaLandingSpawn(landingZoneId){ | |
| return UGA_LANDING_ZONE_SPAWNS[landingZoneId]||'sw'; | |
| } | |
| /* Doctrine decides the landing package, using the two Standard already ships. | |
| 'expedition' was hardcoded, so a methodical or containment plan landed with | |
| the same HQ-and-Constructor opening as a covert raid. prepared brings HQ, | |
| Reactor, Factory and Constructor together; expedition is the build-from-zero | |
| opening — which is what rapid and covert are choosing. */ | |
| const UGA_DOCTRINE_PACKAGES={methodical:'prepared',containment:'prepared',rapid:'expedition',covert:'expedition'}; | |
| function ugaDoctrinePackage(doctrineId){ | |
| return UGA_DOCTRINE_PACKAGES[doctrineId]||'expedition'; | |
| } | |
| /* Player identity and internal terrain-template identity are deliberately | |
| separate. Runtime map IDs never cross back into the UGA operation copy. */ | |
| const GALACTIC_GROUND_AREA_AUTHORITY={ | |
| nova_heliograph_wake:ugaGroundArea('aelos_heliograph','aelos','aelos_caldris','Caldris','aelos_heliograph','Heliograph High Shelf','nova_heliograph_wake','aelos_ridge',['Relay Shadow','Control Spine','Great Divide Array']), | |
| dominion_caldris_claim:ugaGroundArea('aelos_caldris_customs','aelos','aelos_caldris','Caldris','aelos_caldris','Caldris Customs Zone','dominion_caldris_claim','aelos_north',['Cargo Lock','Customs Ring','Orbital Apron']), | |
| syndicate_black_manifest:ugaGroundArea('aelos_morrow_freeport','aelos','aelos_ithara','Ithara','aelos_freeport','Morrow Freeport','syndicate_black_manifest','aelos_coast',['Service Lock','Freight Shadow','Freeport Concourse']), | |
| nova_orison_recovery:ugaGroundArea('veyra_orison_derelict','veyra','veyra_orison','Orison','veyra_orison','Orison Derelict','nova_orison_recovery','nordhall_isles',['Aft Lattice','Broken Spine','Derelict Superstructure']), | |
| dominion_lens_perimeter:ugaGroundArea('veyra_lensing_observatory','veyra','veyra_nacre','Nacre','veyra_lens','Lensing Observatory','dominion_lens_perimeter','nordhall_peaks',['Coolant Trench','Calibration Core','Umbra Platform']), | |
| syndicate_ossuary_dividend:ugaGroundArea('veyra_ossuary_vault','veyra','veyra_nacre','Nacre','veyra_ossuary','Ossuary Vault','syndicate_ossuary_dividend','nordhall_frost',['Vault Aperture','Phase Engine Gallery','Collapsed Gallery']), | |
| uga_pale_bloom:ugaGroundArea('karak_meridian_quarantine','karak','karak_meridian','Meridian K-4','karak_meridian','Meridian Quarantine','uga_pale_bloom','vespera_plateau',['Clinic Roof','Transit Court','Breeder Zone']), | |
| uga_silent_spine:ugaGroundArea('karak_transit_spine','karak','karak_meridian','Meridian K-4','karak_spine','Colony Transit Spine','uga_silent_spine','vespera_dunes',['Maintenance Shaft','Sealed Platform','Gestation Junction']), | |
| uga_hive_heart:ugaGroundArea('karak_primary_hive','karak','karak_meridian','Meridian K-4','karak_hive','Karak Primary Hive','uga_hive_heart','vespera_spire',['Vascular Breach','Thermal Vent','Hive Core']) | |
| }; | |
| function playerGroundLocation(area,map){ | |
| return {schemaVersion:1,kind:UGA_GROUND_LOCATION_KIND,systemId:area.systemId,planetId:area.planetId, | |
| areaId:area.id,siteId:area.siteId,mapId:map.id,size:map.size,display:{ | |
| systemName:{aelos:'Aelos',veyra:'Veyra',karak:'Karak'}[area.systemId],planetName:area.planetName, | |
| areaName:area.name,mapName:map.name}}; | |
| } | |
| function operationBattlefield(operation,contentVersion){ | |
| const area=GALACTIC_GROUND_AREA_AUTHORITY[operation&&operation.missionId]; | |
| if(!area||operation.systemId!==area.systemId||operation.siteId!==area.siteId)return null; | |
| const supplied=operation.battlefield&&operation.battlefield.location; | |
| const legacyRecovered=!supplied&&contentVersion===LEGACY_CONTENT_VERSION; | |
| const mapId=legacyRecovered?area.recommendedMapId:supplied&&supplied.mapId; | |
| const map=area.maps.find(entry=>entry.id===mapId); | |
| if(!map)return null; | |
| const playerLocation=playerGroundLocation(area,map); | |
| if(!legacyRecovered&&!sameJson(supplied,playerLocation))return null; | |
| return {playerLocation,runtimeMapId:map.runtimeTemplateMapId,runtimeRegionId:map.runtimeTemplateMapId.replace(/_(?:small|medium|large)$/,''),legacyRecovered}; | |
| } | |
| const OPPONENT_MAP={brood:'horde',nova:'nova',dominion:'legion',syndicate:'syndicate'}; | |
| const COMMANDER_ROSTER_FINGERPRINT='fnv1a32:0aadcd2d'; | |
| const COMMANDER1_BY_FACTION={nova:'nova_kai',dominion:'legion_vex',syndicate:'syndicate_renn'}; | |
| const COMMANDER_AUTHORITY=[ | |
| ['nova_kai','nova','nova','Captain Elara Kai','Captain','Kai','LANTERN','VANGUARD','kai'], | |
| ['nova_holt','nova','nova','Major Rowan Holt','Major','Holt','ANVIL','ENGINEER','holt'], | |
| ['nova_vale','nova','nova','Cmdr. Sera Vale','Commander','Vale','LONGSIGHT','TACTICIAN','vale'], | |
| ['legion_vex','legion','dominion','Lord Darion Vex','Lord','Vex','ASCENDANT','JUGGERNAUT','vex'], | |
| ['legion_korr','legion','dominion','Marshal Rhea Korr','Marshal','Korr','CADENCE','WARMASTER','korr'], | |
| ['legion_dravik','legion','dominion','Prefect Amon Dravik','Prefect','Dravik','REDOUBT','FORTIFIER','dravik'], | |
| ['syndicate_renn','syndicate','syndicate','Broker Lys Renn','Broker','Renn','LEDGER','BROKER','renn'], | |
| ['syndicate_nyx','syndicate','syndicate','Operative Nyx Calder','Operative','Calder','GHOST','INFILTRATOR','nyx'], | |
| ['syndicate_voss','syndicate','syndicate','Director Oren Voss','Director','Voss','CORE','CONTROLLER','voss'] | |
| ]; | |
| const COMMANDER_BY_ID=Object.fromEntries(COMMANDER_AUTHORITY.map(row=>[row[0],row])); | |
| const DEPLOY_UNIT_SPEC={ | |
| recon_team:{slotCost:1,type:'Striker',perGroup:1}, | |
| line_section:{slotCost:2,type:'Striker',perGroup:2}, | |
| support_vehicle:{slotCost:2,type:'Warden',perGroup:1}, | |
| armored_element:{slotCost:3,type:'Rhino',perGroup:1} | |
| }; | |
| const DEPLOY_STRUCTURE_SPEC={ | |
| field_relay:{slotCost:1,type:'uplink'}, | |
| resource_processor:{slotCost:2,type:'pgen'}, | |
| defensive_emplacement:{slotCost:2,type:'turret'}, | |
| forward_command:{slotCost:4,type:'fac'} | |
| }; | |
| /* This receiver cannot consume the exploration ES modules: every root src file | |
| is a classic script in one global scope. Keep the small authored mission | |
| authority here and lock its parity with tools/test-stage9-galactic-bridge. | |
| Accepting only a general shape would let edited sessionStorage invent an | |
| operation that the expedition catalog never authored. */ | |
| const GALACTIC_MISSION_AUTHORITY={ | |
| nova_heliograph_wake:{missionType:'faction_conflict',systemId:'aelos',siteId:'aelos_heliograph',contractFactionId:'nova',opponentFactionId:'dominion',accessFactionId:'nova',objective:{type:'secure_relay',targetIds:['heliograph_control_spine']},landingZoneIds:['relay_shadow','maintenance_spar'],supportIds:['survey_drones','field_lab'],doctrineIds:['methodical','rapid']}, | |
| dominion_caldris_claim:{missionType:'faction_conflict',systemId:'aelos',siteId:'aelos_caldris',contractFactionId:'dominion',opponentFactionId:'syndicate',accessFactionId:'dominion',objective:{type:'hold_infrastructure',targetIds:['caldris_customs_core']},landingZoneIds:['customs_ring','cargo_lock'],supportIds:['field_lab','heavy_lift'],doctrineIds:['methodical','rapid']}, | |
| syndicate_black_manifest:{missionType:'faction_conflict',systemId:'aelos',siteId:'aelos_freeport',contractFactionId:'syndicate',opponentFactionId:'nova',accessFactionId:'syndicate',objective:{type:'recover_manifest',targetIds:['morrow_archive_stack']},landingZoneIds:['service_lock','freight_shadow'],supportIds:['survey_drones','field_lab'],doctrineIds:['covert','rapid']}, | |
| nova_orison_recovery:{missionType:'faction_conflict',systemId:'veyra',siteId:'veyra_orison',contractFactionId:'nova',opponentFactionId:'syndicate',accessFactionId:'nova',objective:{type:'recover_archive',targetIds:['orison_memory_vault']},landingZoneIds:['broken_spine','aft_lattice'],supportIds:['survey_drones','field_lab','medevac'],doctrineIds:['methodical','covert']}, | |
| dominion_lens_perimeter:{missionType:'faction_conflict',systemId:'veyra',siteId:'veyra_lens',contractFactionId:'dominion',opponentFactionId:'nova',accessFactionId:'dominion',objective:{type:'secure_observatory',targetIds:['lensing_calibration_core']},landingZoneIds:['umbra_platform','coolant_trench'],supportIds:['field_lab','heavy_lift','medevac'],doctrineIds:['methodical','rapid']}, | |
| syndicate_ossuary_dividend:{missionType:'faction_conflict',systemId:'veyra',siteId:'veyra_ossuary',contractFactionId:'syndicate',opponentFactionId:'dominion',accessFactionId:'syndicate',objective:{type:'extract_artifact',targetIds:['ossuary_phase_engine']},landingZoneIds:['vault_aperture','collapsed_gallery'],supportIds:['survey_drones','field_lab','medevac'],doctrineIds:['covert','methodical']}, | |
| uga_pale_bloom:{missionType:'uga_brood_purge',systemId:'karak',siteId:'karak_meridian',contractFactionId:null,opponentFactionId:'brood',accessFactionId:null,objective:{type:'purge_brood',infestation:true,hiveTargetIds:['meridian_breeder_nest'],nestCount:1},landingZoneIds:['clinic_roof','transit_court'],supportIds:['survey_drones','field_lab','medevac','heavy_lift'],doctrineIds:['containment','methodical','rapid']}, | |
| uga_silent_spine:{missionType:'uga_brood_purge',systemId:'karak',siteId:'karak_spine',contractFactionId:null,opponentFactionId:'brood',accessFactionId:null,objective:{type:'purge_brood',infestation:true,hiveTargetIds:['spine_gestation_cluster','spine_feeder_root'],nestCount:2},landingZoneIds:['maintenance_shaft','sealed_platform'],supportIds:['field_lab','medevac','heavy_lift'],doctrineIds:['containment','methodical']}, | |
| uga_hive_heart:{missionType:'uga_brood_purge',systemId:'karak',siteId:'karak_hive',contractFactionId:null,opponentFactionId:'brood',accessFactionId:null,objective:{type:'purge_brood',infestation:true,hiveTargetIds:['karak_hive_heart'],nestCount:1},landingZoneIds:['vascular_breach','thermal_vent'],supportIds:['field_lab','medevac','heavy_lift'],doctrineIds:['containment','methodical']} | |
| }; | |
| /* UGA objective names are more specific than the base RTS's four proven | |
| victory rules. Faction contracts are all battles for control of a named | |
| asset, so Domination is the honest production rule: control the theatre | |
| when the clock expires, or break hostile command early. Brood operations | |
| retain the dedicated hive rule. Never invent a fifth goal ID here: the old | |
| `destroy` value was not registered by GOALS, so goalDef silently displayed | |
| and executed Annihilation instead of the operation the player accepted. */ | |
| const TACTICAL_OBJECTIVE_RULES={ | |
| secure_relay:{id:'domination',em:'\u2691',hud:'RELAY',nm:'Relay Security',ds:'Control the most battlefield infrastructure when the clock ends to secure the relay.'}, | |
| hold_infrastructure:{id:'domination',em:'\u2691',hud:'HOLD',nm:'Infrastructure Hold',ds:'Hold the most battlefield infrastructure when the clock ends.'}, | |
| recover_manifest:{id:'domination',em:'\u25c7',hud:'MANIFEST',nm:'Manifest Recovery',ds:'Control the field when the clock ends so recovery teams can secure the manifest.'}, | |
| recover_archive:{id:'domination',em:'\u25c7',hud:'ARCHIVE',nm:'Archive Recovery',ds:'Control the field when the clock ends so recovery teams can secure the archive.'}, | |
| secure_observatory:{id:'domination',em:'\u2691',hud:'LENS',nm:'Observatory Security',ds:'Control the most battlefield infrastructure when the clock ends to secure the observatory.'}, | |
| extract_artifact:{id:'domination',em:'\u25c7',hud:'ARTIFACT',nm:'Artifact Extraction',ds:'Control the field when the clock ends so the artifact can be extracted.'}, | |
| purge_brood:{id:'purge',em:'\ud83d\udc1b',hud:'HIVES',nm:'Brood Purge',ds:'Destroy every active Brood hive before time runs out.'} | |
| }; | |
| function resolveTacticalObjective(operation){ | |
| const objective=operation&&operation.objective,rule=TACTICAL_OBJECTIVE_RULES[objective&&objective.type]; | |
| if(!rule)throw new Error('GALACTIC_OPERATION_OBJECTIVE_UNMAPPED'); | |
| return Object.freeze({id:rule.id,em:rule.em,hud:rule.hud,nm:rule.nm,ds:rule.ds,objectiveType:objective.type}); | |
| } | |
| const GROUND_OPERATION_V3_FIELDS=['schemaVersion','kind','profileId','sequence','launchRevision','missionId','missionType','systemId','siteId','sponsorId','contractFactionId','proxyFactionId','playerFactionId','opponentFactionId','commanderId','specialistIds','doctrineId','supportId','landingZoneId','configuration','objective','difficulty','intelligence','battlefield','scanTierAtLaunch','threatAtLaunch','factionSnapshot','personnelSnapshot','deploymentManifest','deploymentCost','rewardModifiers','rewardPlan','returnRoute','commanderRosterFingerprint','commanderIdentity','operationId','resultSeed','returnToken']; | |
| const OPERATION_MOD_IDS=new Set(['survey_link','repair_nanites','medical_cache']); | |
| const DOCTRINE_SCORE_DELTA={containment:8,methodical:7,rapid:2,covert:5}; | |
| const SUPPORT_SCORE_DELTA={survey_drones:4,field_lab:2,medevac:1,heavy_lift:5}; | |
| const PLAIN_OBJECT=Object.prototype; | |
| const bridge={active:false,status:'idle',reason:'',nonce:'',request:null,report:null, | |
| sandboxMeta:null,returning:false,isolated:false,packageApplied:false, | |
| packageSummary:null,reportCandidate:null,reportCandidateBytes:'', | |
| operationEffects:null,tacticalGoal:null,playerLocation:null,runtimeMapId:'',naniteUnits:[],suppressedPersistentCrates:0, | |
| suppressedPostMatchAds:0,suppressedBillboardImpressions:0,menuRouteActive:false}; | |
| function clone(value){ | |
| return value===undefined?undefined:JSON.parse(JSON.stringify(value)); | |
| } | |
| function freezeJson(value){ | |
| if(value&&typeof value==='object'&&!Object.isFrozen(value)){ | |
| for(const key of Object.keys(value))freezeJson(value[key]); | |
| Object.freeze(value); | |
| } | |
| return value; | |
| } | |
| function normalizeStable(value){ | |
| if(value===null||typeof value==='string'||typeof value==='boolean')return value; | |
| if(typeof value==='number'){ | |
| if(!Number.isFinite(value))throw new TypeError('Deterministic values require finite numbers'); | |
| return Object.is(value,-0)?0:value; | |
| } | |
| if(Array.isArray(value))return value.map(normalizeStable); | |
| if(typeof value==='object'&&(Object.getPrototypeOf(value)===PLAIN_OBJECT | |
| ||Object.prototype.toString.call(value)==='[object Object]')){ | |
| const normalized={}; | |
| for(const key of Object.keys(value).sort())if(value[key]!==undefined)normalized[key]=normalizeStable(value[key]); | |
| return normalized; | |
| } | |
| throw new TypeError('Deterministic values require JSON-compatible plain data'); | |
| } | |
| function stableStringify(value){return JSON.stringify(normalizeStable(value));} | |
| function hash32(value){ | |
| const text=typeof value==='string'?value:stableStringify(value); | |
| let hash=0x811c9dc5; | |
| for(let i=0;i<text.length;i++){ | |
| hash^=text.charCodeAt(i);hash=Math.imul(hash,0x01000193); | |
| } | |
| return (hash>>>0).toString(16).padStart(8,'0'); | |
| } | |
| function envelopeChecksum(value){ | |
| const copy=clone(value);delete copy.checksum;return hash32(copy); | |
| } | |
| function issue(issues,code){issues.push(code);} | |
| function result(ok,issues){return Object.freeze({ok,issues:Object.freeze(issues.slice())});} | |
| function text(value){return typeof value==='string'?value.trim():'';} | |
| function exactKeys(value,keys){ | |
| return !!value&&typeof value==='object'&&!Array.isArray(value) | |
| &&stableStringify(Object.keys(value).sort())===stableStringify(keys.slice().sort()); | |
| } | |
| function sameJson(left,right){ | |
| try{return stableStringify(left)===stableStringify(right);}catch(e){return false;} | |
| } | |
| function commanderIdentityFromRoster(entry){ | |
| return entry&&{ | |
| id:entry.id,sourceFactionId:entry.sourceFactionId,campaignFactionId:entry.campaignFactionId, | |
| name:entry.name,rank:entry.rank,shortName:entry.shortName,callsign:entry.callsign, | |
| role:entry.role,trait:entry.passive&&entry.passive.perk | |
| }; | |
| } | |
| function validateCommanderRosterSnapshot(snapshot){ | |
| const issues=[]; | |
| if(!snapshot||typeof snapshot!=='object'||Array.isArray(snapshot))return result(false,['COMMANDER_ROSTER_NOT_OBJECT']); | |
| if(!exactKeys(snapshot,['schemaVersion','kind','source','sourceVersion','commanderCount','commander1ByCampaignFaction','commanders','fingerprint']))issue(issues,'COMMANDER_ROSTER_SCHEMA_INVALID'); | |
| if(snapshot.schemaVersion!==1||snapshot.kind!=='CommanderRosterSnapshotV1'||snapshot.source!=='massfront-base'||!Number.isInteger(snapshot.sourceVersion)||snapshot.sourceVersion<1)issue(issues,'COMMANDER_ROSTER_VERSION_INVALID'); | |
| if(stableStringify(snapshot.commander1ByCampaignFaction)!==stableStringify(COMMANDER1_BY_FACTION))issue(issues,'COMMANDER_ROSTER_COMMANDER1_INVALID'); | |
| if(!Array.isArray(snapshot.commanders)||snapshot.commanders.length!==9||snapshot.commanderCount!==9)issue(issues,'COMMANDER_ROSTER_COUNT_INVALID'); | |
| else snapshot.commanders.forEach((entry,index)=>{ | |
| const row=COMMANDER_AUTHORITY[index]; | |
| if(!entry||entry.id!==row[0]||entry.sourceFactionId!==row[1]||entry.campaignFactionId!==row[2] | |
| ||entry.name!==row[3]||entry.rank!==row[4]||entry.shortName!==row[5]||entry.callsign!==row[6] | |
| ||entry.role!==row[7]||entry.passive?.perk!==row[8])issue(issues,'COMMANDER_ROSTER_AUTHORITY_MISMATCH'); | |
| }); | |
| let computed=''; | |
| try{const payload=clone(snapshot);delete payload.fingerprint;computed='fnv1a32:'+hash32(payload);}catch(e){} | |
| if(snapshot.fingerprint!==COMMANDER_ROSTER_FINGERPRINT||computed!==COMMANDER_ROSTER_FINGERPRINT)issue(issues,'COMMANDER_ROSTER_FINGERPRINT_INVALID'); | |
| return result(!issues.length,issues); | |
| } | |
| function describeOperationEffects(operation){ | |
| const doctrineId=operation&&operation.doctrineId,supportId=operation&&operation.supportId; | |
| const modIds=Array.isArray(operation?.deploymentManifest?.modIds)?operation.deploymentManifest.modIds:[]; | |
| const matchApplied=[]; | |
| if(modIds.includes('survey_link'))matchApplied.push({layer:'match',source:'mod',id:'survey_link', | |
| effect:'deployment-scan',seconds:24,radius:15}); | |
| if(modIds.includes('repair_nanites'))matchApplied.push({layer:'match',source:'mod',id:'repair_nanites', | |
| effect:'starting-unit-repair-reserve',reserveMaxHpPct:20,repairMaxHpPctPerSecond:1}); | |
| const scoreApplied=[ | |
| {layer:'score',source:'doctrine',id:doctrineId,effect:'tactical-score',delta:DOCTRINE_SCORE_DELTA[doctrineId]||0}, | |
| {layer:'score',source:'support',id:supportId,effect:'tactical-score',delta:SUPPORT_SCORE_DELTA[supportId]||0} | |
| ]; | |
| const moduleResultApplied=[]; | |
| if(modIds.includes('medical_cache'))moduleResultApplied.push({layer:'module-result',source:'mod',id:'medical_cache', | |
| effect:'injury-severity-minus-one'}); | |
| return freezeJson({matchApplied,scoreApplied,moduleResultApplied, | |
| tacticalScoreDelta:scoreApplied.reduce((sum,item)=>sum+item.delta,0)}); | |
| } | |
| function validateEntryTicket(ticket,now,profileId){ | |
| const issues=[],at=Math.max(0,Math.floor(Number(now)||Date.now())); | |
| if(!ticket||typeof ticket!=='object'||Array.isArray(ticket))return result(false,['ENTRY_NOT_OBJECT']); | |
| if(!exactKeys(ticket,['schemaVersion','kind','profileId','issuedAt','expiresAt','source','entryView','introRequired','commanderRosterSnapshot','commanderRosterFingerprint','commissioning']))issue(issues,'ENTRY_FIELDS_INVALID'); | |
| if(ticket.schemaVersion!==2||ticket.kind!=='MassfrontGalacticEntryV2'||ticket.source!=='massfront-base')issue(issues,'ENTRY_SCHEMA_INVALID'); | |
| if(ticket.entryView!=='system'&&ticket.entryView!=='campaign_hub')issue(issues,'ENTRY_VIEW_INVALID'); | |
| if(typeof ticket.introRequired!=='boolean')issue(issues,'ENTRY_INTRO_INVALID'); | |
| if(!text(ticket.profileId)||ticket.profileId!==profileId)issue(issues,'ENTRY_PROFILE_MISMATCH'); | |
| if(!Number.isInteger(ticket.issuedAt)||!Number.isInteger(ticket.expiresAt) | |
| ||ticket.issuedAt<0||ticket.expiresAt<=ticket.issuedAt | |
| ||ticket.expiresAt-ticket.issuedAt>7*24*60*60*1000 | |
| ||ticket.expiresAt<=at||ticket.issuedAt>at)issue(issues,'ENTRY_EXPIRED'); | |
| issues.push(...validateCommanderRosterSnapshot(ticket.commanderRosterSnapshot).issues); | |
| if(ticket.commanderRosterFingerprint!==COMMANDER_ROSTER_FINGERPRINT | |
| ||ticket.commanderRosterFingerprint!==ticket.commanderRosterSnapshot?.fingerprint)issue(issues,'ENTRY_COMMANDER_ROSTER_STALE'); | |
| const commissioning=ticket.commissioning; | |
| if(!exactKeys(commissioning,['factionId','commanderId']))issue(issues,'ENTRY_COMMISSIONING_INVALID'); | |
| else if(commissioning.factionId===null&&commissioning.commanderId===null){} | |
| else if(!Object.prototype.hasOwnProperty.call(COMMANDER1_BY_FACTION,commissioning.factionId) | |
| ||!Object.prototype.hasOwnProperty.call(COMMANDER_BY_ID,commissioning.commanderId) | |
| ||COMMANDER1_BY_FACTION[commissioning.factionId]!==commissioning.commanderId)issue(issues,'ENTRY_COMMISSIONING_INVALID'); | |
| return result(!issues.length,issues); | |
| } | |
| function validateRouteRequest(request,nonce,profileId,now){ | |
| const issues=[],at=Math.max(0,Math.floor(Number(now)||Date.now())); | |
| if(!request||typeof request!=='object'||Array.isArray(request))return result(false,['ROUTE_NOT_OBJECT']); | |
| if(!exactKeys(request,['schemaVersion','kind','nonce','profileId','routeId','issuedAt','expiresAt','source','location','checksum']))issue(issues,'ROUTE_FIELDS_INVALID'); | |
| if(request.schemaVersion!==2||request.kind!=='MassfrontGalacticRouteRequestV2' | |
| ||request.source!=='massfront-exploration')issue(issues,'ROUTE_SCHEMA_INVALID'); | |
| if(!NONCE_RE.test(text(nonce))||request.nonce!==nonce)issue(issues,'ROUTE_NONCE_INVALID'); | |
| if(!text(profileId)||request.profileId!==profileId)issue(issues,'ROUTE_PROFILE_MISMATCH'); | |
| if(!BASE_ROUTE_IDS.has(request.routeId))issue(issues,'ROUTE_TARGET_INVALID'); | |
| if(!exactKeys(request.location,['systemId','targetId'])||!expeditionLocationAllowed(request.location))issue(issues,'ROUTE_LOCATION_INVALID'); | |
| if(!Number.isInteger(request.issuedAt)||!Number.isInteger(request.expiresAt) | |
| ||request.expiresAt<=request.issuedAt||request.expiresAt-request.issuedAt>2*60*1000 | |
| ||request.expiresAt<=at||request.issuedAt>at+30000)issue(issues,'ROUTE_EXPIRED'); | |
| try{if(request.checksum!==envelopeChecksum(request))issue(issues,'ROUTE_CHECKSUM_INVALID');} | |
| catch(e){issue(issues,'ROUTE_CHECKSUM_INVALID');} | |
| return result(!issues.length,issues); | |
| } | |
| function validateTacticalReport(reportValue,operation){ | |
| const issues=[],team=new Set([operation&&operation.commanderId,...((operation&&operation.specialistIds)||[])]); | |
| if(!reportValue||typeof reportValue!=='object'||Array.isArray(reportValue))return result(false,['REPORT_NOT_OBJECT']); | |
| if(reportValue.outcome!=='victory'&&reportValue.outcome!=='setback')issue(issues,'REPORT_OUTCOME_INVALID'); | |
| if(!Number.isInteger(reportValue.score)||reportValue.score<0||reportValue.score>100)issue(issues,'REPORT_SCORE_INVALID'); | |
| if(typeof reportValue.primaryObjectiveComplete!=='boolean')issue(issues,'REPORT_OBJECTIVE_INVALID'); | |
| if(!Number.isInteger(reportValue.secondaryObjectivesComplete)||reportValue.secondaryObjectivesComplete<0||reportValue.secondaryObjectivesComplete>3)issue(issues,'REPORT_SECONDARY_INVALID'); | |
| if(!['none','light','moderate','severe'].includes(reportValue.injuryBand))issue(issues,'REPORT_INJURY_BAND_INVALID'); | |
| if(!Array.isArray(reportValue.injuredPersonnelIds) | |
| ||new Set(reportValue.injuredPersonnelIds).size!==reportValue.injuredPersonnelIds.length | |
| ||reportValue.injuredPersonnelIds.some(id=>!team.has(id)))issue(issues,'REPORT_TEAM_INVALID'); | |
| else if(reportValue.injuryBand==='none'&&reportValue.injuredPersonnelIds.length)issue(issues,'REPORT_INJURY_CONFLICT'); | |
| if(reportValue.outcome==='victory'&&reportValue.primaryObjectiveComplete!==true)issue(issues,'REPORT_VICTORY_OBJECTIVE_INVALID'); | |
| if(reportValue.outcome==='setback'&&reportValue.primaryObjectiveComplete!==false)issue(issues,'REPORT_SETBACK_OBJECTIVE_INVALID'); | |
| return result(!issues.length,issues); | |
| } | |
| function validateResultMirror(mirror,nonce,profileId,request,now){ | |
| const issues=[],at=Math.max(0,Math.floor(Number(now)||Date.now())); | |
| if(!mirror||typeof mirror!=='object'||Array.isArray(mirror))return result(false,['RESULT_NOT_OBJECT']); | |
| if(mirror.schemaVersion!==1||mirror.kind!=='MassfrontGalacticTacticalReportV1')issue(issues,'RESULT_SCHEMA_INVALID'); | |
| if(mirror.nonce!==nonce||mirror.accountId!==profileId | |
| ||mirror.operationId!==request?.operation?.operationId)issue(issues,'RESULT_IDENTITY_INVALID'); | |
| if(!Number.isInteger(mirror.issuedAt)||mirror.issuedAt<request?.issuedAt | |
| ||mirror.issuedAt>request?.expiresAt||mirror.issuedAt>at||at>request?.expiresAt)issue(issues,'RESULT_TIME_INVALID'); | |
| try{if(mirror.checksum!==envelopeChecksum(mirror))issue(issues,'RESULT_CHECKSUM_INVALID');} | |
| catch(e){issue(issues,'RESULT_CHECKSUM_INVALID');} | |
| issues.push(...validateTacticalReport(mirror.report,request&&request.operation).issues); | |
| return result(!issues.length,issues); | |
| } | |
| function validateDeploymentContract(operation){ | |
| const issues=[],mission=GALACTIC_MISSION_AUTHORITY[operation&&operation.missionId],manifest=operation&&operation.deploymentManifest,configuration=operation&&operation.configuration; | |
| if(!mission||!mission.doctrineIds.includes(operation&&operation.doctrineId) | |
| ||configuration?.doctrineId!==operation.doctrineId||configuration?.approach!==operation.doctrineId)issue(issues,'OPERATION_DOCTRINE_INVALID'); | |
| if(!mission||!mission.supportIds.includes(operation&&operation.supportId) | |
| ||configuration?.supportId!==operation.supportId||configuration?.support!==operation.supportId)issue(issues,'OPERATION_SUPPORT_INVALID'); | |
| if(!mission||!mission.landingZoneIds.includes(operation&&operation.landingZoneId) | |
| ||configuration?.landingZoneId!==operation.landingZoneId | |
| ||operation?.battlefield?.landingZoneId!==operation.landingZoneId)issue(issues,'OPERATION_LANDING_ZONE_INVALID'); | |
| if(!manifest||typeof manifest!=='object'||Array.isArray(manifest))return result(false,issues.concat('OPERATION_MANIFEST_INVALID')); | |
| if(!Array.isArray(manifest.units)||!Array.isArray(manifest.structures)||!Array.isArray(manifest.modIds))issue(issues,'OPERATION_MANIFEST_SHAPE_INVALID'); | |
| const unitIds=new Set(),structureIds=new Set();let unitGroups=0,structureCount=0,slotsUsed=0; | |
| for(const entry of Array.isArray(manifest.units)?manifest.units:[]){ | |
| const spec=entry&&DEPLOY_UNIT_SPEC[entry.id]; | |
| if(!spec||unitIds.has(entry.id)||!Number.isInteger(entry.count)||entry.count<1||entry.count>8 | |
| ||entry.slotCost!==spec.slotCost){issue(issues,'OPERATION_MANIFEST_UNIT_INVALID');continue;} | |
| unitIds.add(entry.id);unitGroups+=entry.count;slotsUsed+=entry.count*spec.slotCost; | |
| } | |
| for(const entry of Array.isArray(manifest.structures)?manifest.structures:[]){ | |
| const spec=entry&&DEPLOY_STRUCTURE_SPEC[entry.id]; | |
| if(!spec||structureIds.has(entry.id)||!Number.isInteger(entry.count)||entry.count<1||entry.count>8 | |
| ||entry.slotCost!==spec.slotCost){issue(issues,'OPERATION_MANIFEST_STRUCTURE_INVALID');continue;} | |
| structureIds.add(entry.id);structureCount+=entry.count;slotsUsed+=entry.count*spec.slotCost; | |
| } | |
| const modIds=Array.isArray(manifest.modIds)?manifest.modIds:[]; | |
| if(new Set(modIds).size!==modIds.length||modIds.some(id=>!OPERATION_MOD_IDS.has(id)))issue(issues,'OPERATION_MANIFEST_MOD_INVALID'); | |
| if(manifest.slotCapacity<8||manifest.slotCapacity>16||!Number.isInteger(manifest.slotCapacity) | |
| ||manifest.slotsUsed!==slotsUsed||slotsUsed>manifest.slotCapacity)issue(issues,'OPERATION_MANIFEST_CAPACITY_INVALID'); | |
| if(manifest.unitLimit!==4||unitGroups<1||unitGroups>manifest.unitLimit||!unitIds.has('line_section'))issue(issues,'OPERATION_MANIFEST_UNIT_LIMIT_INVALID'); | |
| if(manifest.structureLimit!==2||structureCount>manifest.structureLimit)issue(issues,'OPERATION_MANIFEST_STRUCTURE_LIMIT_INVALID'); | |
| if(!Number.isInteger(manifest.modLimit)||manifest.modLimit<2||manifest.modLimit>3||modIds.length>manifest.modLimit)issue(issues,'OPERATION_MANIFEST_MOD_LIMIT_INVALID'); | |
| try{if(stableStringify(clone(configuration?.deploymentManifest))!==stableStringify(clone(manifest)))issue(issues,'OPERATION_MANIFEST_CONFIGURATION_MISMATCH');} | |
| catch(e){issue(issues,'OPERATION_MANIFEST_CONFIGURATION_MISMATCH');} | |
| return result(!issues.length,issues); | |
| } | |
| function validateRequest(envelope,nonce,profileId,now,ticket){ | |
| const issues=[],at=Math.max(0,Math.floor(Number(now)||Date.now())); | |
| if(!envelope||typeof envelope!=='object'||Array.isArray(envelope))return result(false,['REQUEST_NOT_OBJECT']); | |
| const operation=envelope.operation; | |
| if(!exactKeys(envelope,['schemaVersion','kind','nonce','accountId','contentVersion','issuedAt','expiresAt','adapter','commanderRosterFingerprint','operation','checksum']))issue(issues,'REQUEST_FIELDS_INVALID'); | |
| if(envelope.schemaVersion!==2||envelope.kind!=='GroundOperationRequestV2')issue(issues,'REQUEST_SCHEMA_INVALID'); | |
| if(envelope.adapter!=='massfront-solo-v2')issue(issues,'REQUEST_ADAPTER_INVALID'); | |
| if(envelope.commanderRosterFingerprint!==COMMANDER_ROSTER_FINGERPRINT)issue(issues,'REQUEST_COMMANDER_ROSTER_INVALID'); | |
| if(!NONCE_RE.test(text(nonce))||envelope.nonce!==nonce)issue(issues,'REQUEST_NONCE_INVALID'); | |
| if(!text(profileId)||envelope.accountId!==profileId||operation?.profileId!==profileId)issue(issues,'REQUEST_PROFILE_MISMATCH'); | |
| if(envelope.contentVersion!==CONTENT_VERSION&&envelope.contentVersion!==LEGACY_CONTENT_VERSION)issue(issues,'REQUEST_CONTENT_VERSION_INVALID'); | |
| if(!Number.isInteger(envelope.issuedAt)||!Number.isInteger(envelope.expiresAt) | |
| ||envelope.expiresAt<=envelope.issuedAt||at>envelope.expiresAt||envelope.issuedAt>at+30000)issue(issues,'REQUEST_EXPIRED'); | |
| try{if(envelope.checksum!==envelopeChecksum(envelope))issue(issues,'REQUEST_CHECKSUM_INVALID');} | |
| catch(e){issue(issues,'REQUEST_CHECKSUM_INVALID');} | |
| if(!operation||typeof operation!=='object'||Array.isArray(operation))issue(issues,'OPERATION_NOT_OBJECT'); | |
| else { | |
| if(!exactKeys(operation,GROUND_OPERATION_V3_FIELDS))issue(issues,'OPERATION_FIELDS_INVALID'); | |
| const modifiers=operation.rewardModifiers; | |
| if(!exactKeys(modifiers,['frontBountyPct','replayScalePct']) | |
| ||!Number.isInteger(modifiers.frontBountyPct)||modifiers.frontBountyPct<0||modifiers.frontBountyPct>40 | |
| ||!Number.isInteger(modifiers.replayScalePct)||modifiers.replayScalePct<35||modifiers.replayScalePct>100) | |
| issue(issues,'OPERATION_REWARD_MODIFIERS_INVALID'); | |
| if(operation.schemaVersion!==3||operation.kind!=='GroundOperationV3')issue(issues,'OPERATION_SCHEMA_INVALID'); | |
| if(operation.commanderRosterFingerprint!==COMMANDER_ROSTER_FINGERPRINT | |
| ||operation.commanderRosterFingerprint!==envelope.commanderRosterFingerprint)issue(issues,'OPERATION_COMMANDER_ROSTER_INVALID'); | |
| const mission=GALACTIC_MISSION_AUTHORITY[operation.missionId]; | |
| if(!mission||operation.missionType!==mission.missionType||operation.systemId!==mission.systemId||operation.siteId!==mission.siteId)issue(issues,'OPERATION_MISSION_INVALID'); | |
| if(operation.sponsorId!=='uga'||operation.contractFactionId!==mission?.contractFactionId)issue(issues,'OPERATION_SPONSOR_INVALID'); | |
| if(operation.opponentFactionId!==mission?.opponentFactionId)issue(issues,'OPERATION_OPPONENT_INVALID'); | |
| if(!Object.prototype.hasOwnProperty.call(PROXY_MAP,operation.proxyFactionId) | |
| ||operation.playerFactionId!==operation.proxyFactionId)issue(issues,'OPERATION_PROXY_INVALID'); | |
| if(!mission||!sameJson(operation.objective,mission.objective))issue(issues,'OPERATION_OBJECTIVE_INVALID'); | |
| const purge=mission?.missionType==='uga_brood_purge'; | |
| if(operation.battlefield?.infestationActive!==purge | |
| ||!sameJson(operation.battlefield?.hiveTargetIds||[],purge?operation.objective?.hiveTargetIds||[]:[]))issue(issues,'OPERATION_INFESTATION_INVALID'); | |
| if(!operationBattlefield(operation,envelope.contentVersion))issue(issues,'OPERATION_BATTLEFIELD_INVALID'); | |
| if(mission&&((mission.accessFactionId===null&&operation.contractFactionId!==null) | |
| ||(mission.accessFactionId!==null&&(operation.proxyFactionId!==mission.accessFactionId||operation.contractFactionId!==mission.accessFactionId))))issue(issues,'OPERATION_ACCESS_INVALID'); | |
| if(!text(operation.operationId)||!text(operation.resultSeed)||!text(operation.returnToken))issue(issues,'OPERATION_IDENTITY_INVALID'); | |
| const commanderRow=COMMANDER_BY_ID[operation.commanderId]; | |
| const rosterCommander=Array.isArray(ticket?.commanderRosterSnapshot?.commanders) | |
| ?ticket.commanderRosterSnapshot.commanders.find(entry=>entry.id===operation.commanderId):null; | |
| const expectedIdentity=commanderIdentityFromRoster(rosterCommander); | |
| if(!commanderRow||commanderRow[2]!==operation.proxyFactionId | |
| ||!expectedIdentity||!sameJson(operation.commanderIdentity,expectedIdentity) | |
| ||operation.personnelSnapshot?.commander?.id!==operation.commanderId)issue(issues,'OPERATION_COMMANDER_INVALID'); | |
| if(!text(operation.commanderId)||!Array.isArray(operation.specialistIds) | |
| ||operation.specialistIds.length!==3||new Set(operation.specialistIds).size!==3)issue(issues,'OPERATION_TEAM_INVALID'); | |
| if(operation.playerCount!==undefined&&operation.playerCount!==1)issue(issues,'OPERATION_PLAYER_COUNT_INVALID'); | |
| if(operation.allyCount!==undefined&&operation.allyCount!==0)issue(issues,'OPERATION_ALLY_COUNT_INVALID'); | |
| if(Array.isArray(operation.allies)&&operation.allies.length)issue(issues,'OPERATION_ALLIES_INVALID'); | |
| issues.push(...validateDeploymentContract(operation).issues); | |
| } | |
| const commissioning=ticket&&ticket.commissioning; | |
| issues.push(...validateEntryTicket(ticket,now,profileId).issues); | |
| if(!commissioning||commissioning.factionId===null||commissioning.commanderId===null | |
| ||COMMANDER1_BY_FACTION[commissioning.factionId]!==commissioning.commanderId)issue(issues,'REQUEST_COMMISSIONING_REQUIRED'); | |
| if(ticket?.commanderRosterFingerprint!==COMMANDER_ROSTER_FINGERPRINT)issue(issues,'REQUEST_ENTRY_ROSTER_INVALID'); | |
| return result(!issues.length,issues); | |
| } | |
| function validateRequestMirror(mirror,nonce,profileId,now,ticket){ | |
| const issues=[]; | |
| if(!mirror||typeof mirror!=='object'||Array.isArray(mirror))return result(false,['REQUEST_MIRROR_NOT_OBJECT']); | |
| if(!exactKeys(mirror,['schemaVersion','kind','adapter','commanderRosterFingerprint','nonce','accountId','operationId','request']))issue(issues,'REQUEST_MIRROR_FIELDS_INVALID'); | |
| if(mirror.schemaVersion!==2||mirror.kind!=='MassfrontGalacticRequestMirrorV2')issue(issues,'REQUEST_MIRROR_SCHEMA_INVALID'); | |
| if(mirror.adapter!=='massfront-solo-v2')issue(issues,'REQUEST_MIRROR_ADAPTER_INVALID'); | |
| if(mirror.commanderRosterFingerprint!==COMMANDER_ROSTER_FINGERPRINT)issue(issues,'REQUEST_MIRROR_ROSTER_INVALID'); | |
| if(mirror.nonce!==nonce||mirror.accountId!==profileId | |
| ||mirror.operationId!==mirror.request?.operation?.operationId)issue(issues,'REQUEST_MIRROR_IDENTITY_INVALID'); | |
| const requestValidation=validateRequest(mirror.request,nonce,profileId,now,ticket); | |
| issues.push(...requestValidation.issues); | |
| return result(!issues.length,issues); | |
| } | |
| function readSessionJson(key){ | |
| try{const raw=sessionStorage.getItem(key);return raw?JSON.parse(raw):null;} | |
| catch(e){return null;} | |
| } | |
| function readSessionRecord(key){ | |
| try{ | |
| const serialized=sessionStorage.getItem(key); | |
| if(serialized===null)return {present:false,readable:true,serialized:null,value:null}; | |
| try{return {present:true,readable:true,serialized,value:JSON.parse(serialized)};} | |
| catch(e){return {present:true,readable:false,serialized,value:null};} | |
| }catch(e){return {present:true,readable:false,serialized:null,value:null};} | |
| } | |
| function currentProfileId(){ | |
| return typeof PROFILES!=='undefined'&&PROFILES&&text(PROFILES.active)?PROFILES.active:''; | |
| } | |
| function currentFlagOn(){ | |
| return window.__MF_BUILD_HAS_GALACTIC_EXPLORATION===true | |
| ||window.__MF_OTA_HAS_GALACTIC_DELIVERY===true; | |
| } | |
| let classicFallbackActive=String(location.search||'')===CLASSIC_FALLBACK_SEARCH; | |
| try{classicFallbackActive=classicFallbackActive||sessionStorage.getItem(CLASSIC_FALLBACK_KEY)==='1';}catch(e){} | |
| function classicFallbackOn(){ | |
| if(classicFallbackActive)return true; | |
| try{return sessionStorage.getItem(CLASSIC_FALLBACK_KEY)==='1';}catch(e){return false;} | |
| } | |
| function armClassicFallback(){ | |
| classicFallbackActive=true; | |
| try{sessionStorage.setItem(CLASSIC_FALLBACK_KEY,'1');}catch(e){} | |
| return true; | |
| } | |
| function clearClassicFallback(){ | |
| classicFallbackActive=false; | |
| try{sessionStorage.removeItem(CLASSIC_FALLBACK_KEY);}catch(e){} | |
| return !classicFallbackOn(); | |
| } | |
| function explorationReturnTarget(query=''){ | |
| const target='./modules/space_exploration/index.html'+query; | |
| return typeof mfContentExplorationReturnUrl==='function'?mfContentExplorationReturnUrl(target):target; | |
| } | |
| function stripBridgeQuery(){ | |
| if(typeof history==='undefined'||typeof history.replaceState!=='function')return; | |
| try{history.replaceState(history.state,'',(location.pathname||'./index.html')+(location.hash||''));}catch(e){} | |
| } | |
| function consumeRouteRecord(nonce){ | |
| try{ | |
| sessionStorage.removeItem(ROUTE_PREFIX+nonce); | |
| return sessionStorage.getItem(ROUTE_PREFIX+nonce)===null; | |
| }catch(e){return false;} | |
| } | |
| function openBaseRoute(routeId,location){ | |
| if(!expeditionLocationAllowed(location))return false; | |
| const show=id=>typeof showFrontScreen==='function'&&showFrontScreen(id); | |
| if(typeof initAudio==='function')initAudio(); | |
| if(typeof sfx==='function')sfx('ui'); | |
| bridge.menuRouteActive=true; | |
| if(routeId==='operations'||routeId==='mode-weekly'||routeId==='mode-campaign'){ | |
| if(typeof MF_TAB_STATE!=='undefined')MF_TAB_STATE.opsScr=routeId==='mode-campaign'?'campaign':'weekly'; | |
| if(typeof renderOps==='function')renderOps(); | |
| return show('opsScr'); | |
| } | |
| if(routeId==='development'){ | |
| if(typeof renderDevelop==='function')renderDevelop(); | |
| return show('devScr'); | |
| } | |
| if(routeId==='armory'){ | |
| if(typeof renderMetaHead==='function')renderMetaHead(); | |
| if(typeof renderArmory==='function')renderArmory(); | |
| return show('armory'); | |
| } | |
| if(routeId==='orders'){ | |
| if(typeof renderDaily==='function')renderDaily(); | |
| return show('dailyScr'); | |
| } | |
| if(routeId==='intel'){ | |
| if(typeof renderCodex==='function')renderCodex(); | |
| return show('dossierScr'); | |
| } | |
| if(routeId==='profile'){ | |
| if(typeof renderProfile==='function')renderProfile(); | |
| return show('profileScr'); | |
| } | |
| if(routeId==='inbox'){ | |
| if(typeof renderInbox==='function')renderInbox(); | |
| return show('inboxScr'); | |
| } | |
| if(routeId==='social'){ | |
| if(typeof MFSocialUI!=='undefined'&&MFSocialUI&&typeof MFSocialUI.open==='function'){ | |
| MFSocialUI.open();return true; | |
| } | |
| bridge.menuRouteActive=false; | |
| if(typeof toast==='function')toast('Social Command is not available in this build'); | |
| return show('startScreen'); | |
| } | |
| if(routeId==='settings'){ | |
| if(typeof openSettings==='function'){openSettings('menu');return true;} | |
| return false; | |
| } | |
| if(routeId==='game-version'){ | |
| if(typeof updOpen!=='undefined')updOpen=true; | |
| if(typeof renderUpdatePanel==='function')renderUpdatePanel(); | |
| return show('updScr'); | |
| } | |
| if(routeId==='war-room'){ | |
| if(typeof window.openWarRoom!=='function')return false; | |
| /* The global strategic-home guard normally sends legacy warScr exits | |
| back to UGA. This secured host route is the one deliberate exception: | |
| allow the public owner to reveal the real Classic-mode surface once, | |
| then restore interception so its Back control returns to UGA. */ | |
| warRoomRouteOpening=true; | |
| try{return window.openWarRoom()!==false;} | |
| finally{warRoomRouteOpening=false;} | |
| } | |
| if(routeId==='mode-standard'){ | |
| if(typeof openSkirmishSetup==='function'){ | |
| openSkirmishSetup(); | |
| /* The base owner resets to galaxy and renders its own | |
| galaxy -> system -> planet -> region -> map -> deploy sequence. | |
| The UGA origin is navigation context, never permission to skip it or | |
| to relabel a base homeworld as an exploration planet. */ | |
| return true; | |
| } | |
| return false; | |
| } | |
| if(routeId==='mode-training'){ | |
| if(window.MFNewCareerFactionGate&&typeof window.MFNewCareerFactionGate.afterOnboardingChoice==='function'){ | |
| window.MFNewCareerFactionGate.afterOnboardingChoice({choice:'training',source:'secured-base-route'}); | |
| } | |
| if(typeof resumeTrainingMission==='function'){resumeTrainingMission();return true;} | |
| return false; | |
| } | |
| if(routeId==='new-career-faction'){ | |
| /* Both the tutorial-complete and tutorial-skip paths converge here. The | |
| career gate owns the real faction/Commander 1 grant and continuation; | |
| this bridge only authenticates and delivers the same-tab destination. */ | |
| bridge.menuRouteActive=false; | |
| if(window.MFNewCareerFactionGate&&typeof window.MFNewCareerFactionGate.openFromRoute==='function'){ | |
| return window.MFNewCareerFactionGate.openFromRoute({choice:'skipped'})!==false; | |
| } | |
| return false; | |
| } | |
| return false; | |
| } | |
| function baseRouteTargetReady(routeId){ | |
| /* galactic-operations.js is intentionally loaded before the new-career | |
| takeover. A secured same-tab return can reach bootConfirmed in that | |
| narrow gap, so do not consume its one-time nonce until the late owner is | |
| actually mounted. Otherwise a valid tutorial/skip return is rejected | |
| before career-faction-gate.js has a chance to receive it. */ | |
| if(routeId==='new-career-faction') | |
| return !!(window.MFNewCareerFactionGate | |
| &&typeof window.MFNewCareerFactionGate.openFromRoute==='function'); | |
| if(routeId==='mode-training') | |
| return !!(window.MFNewCareerFactionGate | |
| &&typeof window.MFNewCareerFactionGate.afterOnboardingChoice==='function' | |
| &&typeof resumeTrainingMission==='function'); | |
| if(routeId==='war-room')return typeof window.openWarRoom==='function'; | |
| /* main.js installs the public Standard entry during async boot. Do not | |
| consume the one-shot route record in the narrow gap before it exists; | |
| otherwise the module reports a secured deployment while the base game | |
| can only reject it after the nonce is already gone. */ | |
| if(routeId==='mode-standard')return typeof openSkirmishSetup==='function'; | |
| return true; | |
| } | |
| function rejectMenuRoute(code,nonce){ | |
| bridge.status='menu-route-rejected';bridge.reason=code||'ROUTE_REJECTED';bridge.menuRouteActive=false; | |
| stripBridgeQuery(); | |
| if(NONCE_RE.test(nonce||''))consumeRouteRecord(nonce); | |
| if(typeof toast==='function')toast('Galactic destination rejected — returned to MASSFRONT home'); | |
| if(typeof renderMetaHead==='function')renderMetaHead(); | |
| if(typeof showFrontScreen==='function')showFrontScreen('startScreen'); | |
| } | |
| function rejectBridge(code){ | |
| /* Isolation is a property of the bridge match, not of the session. It was | |
| set beside active in beginBattle and must be released beside it, or the | |
| next ordinary match silently loses every reward: metaGrant, | |
| developRecord, endgameRecord, loot and session snapshots all short out | |
| on bridge.isolated, and the debrief then renders with no payout. */ | |
| bridge.status='rejected';bridge.reason=code||'REJECTED';bridge.active=false;bridge.isolated=false; | |
| const say=()=>{if(typeof toast==='function')toast('Galactic operation rejected — return to NEXUS-VII and relaunch');}; | |
| if(typeof toast==='function')say();else setTimeout(say,120); | |
| /* A rejected deep link must never leave the player at Standard as if the | |
| operation had succeeded. Return to the still-pending module state, where | |
| the authored abandon action can refund the deployment. A malformed nonce | |
| is never reflected into a URL. */ | |
| setTimeout(()=>{ | |
| const target=explorationReturnTarget(NONCE_RE.test(bridge.nonce)?'?groundRejected='+encodeURIComponent(bridge.nonce):''); | |
| try{if(!target)throw new Error('Content return unavailable');location.href=target;} | |
| catch(e){bridge.status='return-error';bridge.reason='REJECTION_RETURN_FAILED';} | |
| },900); | |
| } | |
| function sandboxMetaFrom(live){ | |
| let temp; | |
| try{temp=typeof metaFresh==='function'?metaFresh():{};}catch(e){temp={};} | |
| temp.settings=clone(live&&live.settings||{}); | |
| temp.owned={};temp.opmods={};temp.wcPref=0;temp.res={};temp.resQueue=[]; | |
| temp.mats={alloy:0,circuit:0,isotope:0,relic:0};temp.mods={};temp.equip=[]; | |
| temp.inventory={gear:{},consumables:{},equipped:{weapon:'',armor:'',utility:''},ready:[]}; | |
| temp.campaign={missions:{}};temp.coreGrantPending=[];temp.standardMatches=99; | |
| return temp; | |
| } | |
| function withSandboxMeta(fn){ | |
| const liveMeta=META,liveSave=metaSave; | |
| META=bridge.sandboxMeta;metaSave=function(){return true;}; | |
| try{return fn();} | |
| finally{META=liveMeta;metaSave=liveSave;} | |
| } | |
| function deploymentPlan(operation){ | |
| const validation=validateDeploymentContract(operation); | |
| if(!validation.ok)throw new Error(validation.issues.join(',')); | |
| const units=[],structures=[],manifest=operation.deploymentManifest; | |
| let groupsLeft=4,structuresLeft=2; | |
| for(const entry of manifest.units){ | |
| const spec=DEPLOY_UNIT_SPEC[entry.id],groups=Math.min(entry.count,groupsLeft); | |
| groupsLeft-=groups; | |
| for(let group=0;group<groups;group++)for(let member=0;member<spec.perGroup;member++)units.push({id:entry.id,type:spec.type}); | |
| } | |
| for(const entry of manifest.structures){ | |
| const spec=DEPLOY_STRUCTURE_SPEC[entry.id],count=Math.min(entry.count,structuresLeft); | |
| structuresLeft-=count; | |
| for(let index=0;index<count;index++)structures.push({id:entry.id,type:spec.type}); | |
| } | |
| return {units,structures,landingZoneId:operation.landingZoneId,doctrineId:operation.doctrineId, | |
| supportId:operation.supportId,modIds:manifest.modIds.slice()}; | |
| } | |
| function countPackageTypes(items){ | |
| const counts={};for(const item of items)counts[item.type]=(counts[item.type]||0)+1;return counts; | |
| } | |
| function armStartingUnitNanites(){ | |
| if(typeof unitHigh!=='number'||typeof ualive==='undefined'||typeof uteam==='undefined' | |
| ||typeof ugen==='undefined'||typeof uhp==='undefined'||typeof uhpm==='undefined')throw new Error('REPAIR_NANITES_RUNTIME_UNAVAILABLE'); | |
| const units=[]; | |
| for(let index=0;index<unitHigh;index++)if(ualive[index]&&uteam[index]===0){ | |
| const maxHp=Math.max(0,Number(uhpm[index])||0); | |
| if(maxHp>0)units.push({index,generation:ugen[index],maxHp,reserve:maxHp*.20}); | |
| } | |
| bridge.naniteUnits=units; | |
| return {eligibleUnitCount:units.length,totalReserveHp:Math.round(units.reduce((sum,item)=>sum+item.reserve,0)*100)/100}; | |
| } | |
| function applyOperationMatchEffects(cx,cy){ | |
| const applied=[]; | |
| for(const effect of bridge.operationEffects.matchApplied){ | |
| if(effect.effect==='deployment-scan'){ | |
| if(typeof fogStartScan!=='function')throw new Error('DEPLOYMENT_SCAN_RUNTIME_UNAVAILABLE'); | |
| fogStartScan(cx,cy,effect.seconds,effect.radius);applied.push(clone(effect)); | |
| }else if(effect.effect==='starting-unit-repair-reserve'){ | |
| applied.push(Object.assign(clone(effect),armStartingUnitNanites())); | |
| } | |
| } | |
| return applied; | |
| } | |
| function repairNanitesTick(dt){ | |
| if(!bridge.active||!bridge.naniteUnits.length)return; | |
| const seconds=Math.max(0,Number(dt)||0); | |
| if(seconds<=0)return; | |
| for(const unit of bridge.naniteUnits){ | |
| const index=unit.index; | |
| if(unit.reserve<=0||index<0||index>=unitHigh||!ualive[index]||uteam[index]!==0||ugen[index]!==unit.generation)continue; | |
| const ceiling=Math.min(unit.maxHp,Math.max(0,Number(uhpm[index])||0)); | |
| const missing=Math.max(0,ceiling-(Number(uhp[index])||0)); | |
| const healed=Math.min(missing,unit.reserve,unit.maxHp*.01*seconds); | |
| if(healed>0){uhp[index]+=healed;unit.reserve-=healed;} | |
| } | |
| } | |
| function applyDeploymentPackage(){ | |
| if(!bridge.active||bridge.packageApplied||carrier.phase!==2)return false; | |
| const plan=deploymentPlan(bridge.request.operation),cx=carrier.x,cy=carrier.y; | |
| /* Clinic Roof is a compact casualty-collection perimeter; Transit Court is | |
| a wider curbside column. Landing-zone choice therefore changes the real | |
| opening layout without moving the authored southwest player start. */ | |
| const unitOffsets=plan.landingZoneId==='transit_court' | |
| ?[[-132,88],[-88,88],[-44,88],[0,88],[44,88],[88,88],[132,88],[-110,126],[110,126]] | |
| :[[-86,96],[-44,120],[0,130],[44,120],[86,96],[-120,68],[120,68],[-138,24],[138,24]]; | |
| const structureOffsets=plan.landingZoneId==='transit_court'?[[-145,-58],[145,-58]]:[[-132,-18],[132,-18]]; | |
| const spawnedUnits=[],spawnedStructures=[]; | |
| for(let index=0;index<plan.units.length;index++){ | |
| const item=plan.units[index],type=TYPES.findIndex(entry=>entry&&entry.name===item.type),offset=unitOffsets[index]; | |
| if(type<0||!offset)throw new Error('DEPLOYMENT_UNIT_MAPPING_FAILED'); | |
| const unit=spawnUnit(type,0,cx+offset[0],cy+offset[1]); | |
| if(unit<0)throw new Error('DEPLOYMENT_UNIT_SPAWN_FAILED'); | |
| spawnedUnits.push(item); | |
| } | |
| for(let index=0;index<plan.structures.length;index++){ | |
| const item=plan.structures[index],offset=structureOffsets[index]; | |
| if(!offset)throw new Error('DEPLOYMENT_STRUCTURE_MAPPING_FAILED'); | |
| const x=Math.round((cx+offset[0])/SNAP_GRID)*SNAP_GRID,y=Math.round((cy+offset[1])/SNAP_GRID)*SNAP_GRID; | |
| const building=addBld(item.type,0,x,y,true); | |
| if(!building)throw new Error('DEPLOYMENT_STRUCTURE_SPAWN_FAILED'); | |
| building.deployT=performance.now()/1000+0.14+index*.05; | |
| spawnedStructures.push(item); | |
| } | |
| const matchApplied=applyOperationMatchEffects(cx,cy); | |
| bridge.packageSummary={landingZoneId:plan.landingZoneId, | |
| requested:{unitGroups:clone(bridge.request.operation.deploymentManifest.units),structures:clone(bridge.request.operation.deploymentManifest.structures)}, | |
| spawned:{units:spawnedUnits.length,structures:spawnedStructures.length, | |
| unitTypes:countPackageTypes(spawnedUnits),structureTypes:countPackageTypes(spawnedStructures)}, | |
| appliedEffects:{matchApplied,scoreApplied:clone(bridge.operationEffects.scoreApplied), | |
| moduleResultApplied:clone(bridge.operationEffects.moduleResultApplied)}}; | |
| bridge.packageApplied=true; | |
| if(typeof toast==='function')toast('UGA DEPLOYMENT MANIFEST · '+spawnedUnits.length+' UNITS · '+spawnedStructures.length+' STRUCTURES'); | |
| return true; | |
| } | |
| function configureBattle(){ | |
| const operation=bridge.request.operation,proxy=operation.proxyFactionId; | |
| activeWarMode='galactic'; | |
| playerFaction=PROXY_MAP[proxy];playerCommanderId=operation.commanderId; | |
| const ground=operationBattlefield(operation,bridge.request.contentVersion); | |
| if(!ground)throw new Error('GALACTIC_OPERATION_LOCATION_UNMAPPED'); | |
| const mapDef=typeof MAPDEFS!=='undefined'&&MAPDEFS[ground.runtimeMapId]; | |
| if(!mapDef||mapDef.region!==ground.runtimeRegionId||mapDef.size!==ground.playerLocation.size)throw new Error('GALACTIC_OPERATION_TEMPLATE_UNAVAILABLE'); | |
| bridge.playerLocation=freezeJson(clone(ground.playerLocation));bridge.runtimeMapId=ground.runtimeMapId; | |
| curMap=ground.runtimeMapId;curTheme=mapDef.theme;curRegionId=mapDef.region; | |
| /* The player's own deployment choices drive the match: their landing zone | |
| sets the spawn, their doctrine sets the landing package. Both were | |
| constants, which made two of the deployment screen's decisions cosmetic. */ | |
| battlefieldPreset=ground.playerLocation.size; | |
| deploymentPackage=ugaDoctrinePackage(operation.doctrineId); | |
| playerStartZone=ugaLandingSpawn(operation.landingZoneId);spawnPick='player'; | |
| /* The mission's own opponent and objective, not a permanent Brood purge. | |
| A faction contract against the Dominion used to spawn the Horde. */ | |
| const tacticalGoal=resolveTacticalObjective(operation),purge=operation.missionType==='uga_brood_purge'; | |
| const enemy=OPPONENT_MAP[operation.opponentFactionId]||(purge?'horde':'legion'); | |
| const infested=purge&&operation.battlefield?.infestationActive!==false; | |
| bridge.tacticalGoal=tacticalGoal;goalSel=tacticalGoal.id;infestationOn=infested?1:0; | |
| difficulty=Math.max(0,Math.min(2,Number.isFinite(operation.difficulty)?operation.difficulty-1:2)); | |
| /* The region's map decides the rules, not a constant. */ | |
| const tac=ugaGroundTacticalProfile(ground.playerLocation.size); | |
| bridge.tacticalProfile=freezeJson(clone(tac)); | |
| defenseFocus=tac.defenseFocus;timeLimit=tac.timeLimit; | |
| /* Danger modifiers scale with the region's tier. wcChoice is the count | |
| pickWildcards() draws, and it was pinned to 0, so a large region carried | |
| no more hazard than a compact one. Note pickWildcards() still lets a | |
| player's explicitly chosen Operations modifiers win over the draw — that | |
| is their choice and this only sets how many are rolled otherwise. */ | |
| resPace=tac.resPace;crateRate=crateRateBase=tac.crateRate;wcChoice=tac.wildcards;matchSetupArmed=false; | |
| aiFactionSel=enemy; | |
| if(typeof AI!=='undefined'&&AI){AI.fac=enemy;if(typeof aiFacPicked!=='undefined')aiFacPicked=true;} | |
| /* Enemy count comes from the region tier, not a fixed single opponent, so a | |
| large region is a harder fight and not just a longer one. The old loop | |
| also assigned diff=2 and immediately overwrote it with difficulty, and | |
| parked an inactive slot on 'se' — which is now a player landing zone. */ | |
| for(let i=0;i<aiSlots.length;i++){ | |
| aiSlots[i].on=i<tac.enemies;aiSlots[i].ally=false; | |
| aiSlots[i].zone=UGA_ENEMY_ZONES[i]||'c';aiSlots[i].behavior='balanced'; | |
| aiSlots[i].diff=difficulty; | |
| } | |
| if(typeof normalizeAiSlotsForBattlefield==='function')normalizeAiSlotsForBattlefield(); | |
| /* Report what was actually applied. These used to be literals that could | |
| silently disagree with the globals set above; a setup record that lies | |
| about the match it configured is worse than no record. */ | |
| bridge.sandboxMeta.setup={d:difficulty,t:curTheme,m:curMap,f:enemy,pf:playerFaction,pc:playerCommanderId, | |
| bs:battlefieldPreset,pkg:deploymentPackage,g:goalSel,tl:timeLimit,rp:resPace,cr:crateRate,ps:playerStartZone,tier:tac.tier,wc:wcChoice,en:tac.enemies, | |
| ais:aiSlots.map(A=>({on:!!A.on,diff:A.diff|0,zone:A.zone,ally:false,behavior:'balanced'})),df:defenseFocus,inf:infestationOn?1:0}; | |
| } | |
| function dismissEntryOverlays(){ | |
| for(const id of ['mfIntro','apOverlay','apConfirmOverlay','accDlg','dispatch']){ | |
| const el=document.getElementById(id);if(el)el.style.display='none'; | |
| } | |
| try{if(typeof apGateSatisfied==='function')apGateSatisfied();}catch(e){} | |
| } | |
| function operationLoadScreenModel(operation,location){ | |
| if(!operation||!location||!location.display)return null; | |
| const display=location.display,planet=display.planetName||display.systemName||'UGA FRONT'; | |
| return {title:display.mapName||display.areaName||'UGA BATTLEFIELD', | |
| eyebrow:'DEPLOYING TO · '+planet,poi:display.areaName||'', | |
| hook:operation.missionType==='uga_brood_purge'?'UGA CONTAINMENT OPERATION':'UGA PROXY OPERATION', | |
| chips:[{key:'SYSTEM',value:display.systemName||location.systemId}, | |
| {key:'SCALE',value:location.size},{key:'THREAT',value:'T'+operation.difficulty}].filter(entry=>entry.value)}; | |
| } | |
| function fillOperationLoadScreen(){ | |
| const operation=bridge.request&&bridge.request.operation; | |
| const model=operationLoadScreenModel(operation,bridge.playerLocation); | |
| if(!model)return false; | |
| const setText=(id,value)=>{const el=document.getElementById(id);if(el)el.textContent=value||'';}; | |
| setText('loadTitle',model.title);setText('loadEyebrow',model.eyebrow); | |
| setText('loadPoi',model.poi);setText('loadHook',model.hook); | |
| const host=document.getElementById('loadStats'); | |
| if(host){ | |
| host.textContent=''; | |
| for(const entry of model.chips){ | |
| const chip=document.createElement('div'),key=document.createElement('span'),value=document.createElement('span'); | |
| chip.className='lsChip';key.className='lsK';value.className='lsV'; | |
| key.textContent=entry.key;value.textContent=String(entry.value).toUpperCase(); | |
| chip.appendChild(key);chip.appendChild(value);host.appendChild(chip); | |
| } | |
| } | |
| return true; | |
| } | |
| function beginBattle(){ | |
| bridge.active=true;bridge.isolated=true;bridge.status='launching';bridge.reason=''; | |
| bridge.sandboxMeta=sandboxMetaFrom(META); | |
| const resume=document.getElementById('sessResume');if(resume)resume.remove(); | |
| try{withSandboxMeta(configureBattle);} | |
| catch(e){rejectBridge('SETUP_FAILED');return;} | |
| dismissEntryOverlays(); | |
| if(typeof hideFrontScreens==='function')hideFrontScreens(); | |
| if(typeof mfLoadScreenFill==='function')mfLoadScreenFill(); | |
| fillOperationLoadScreen(); | |
| const load=document.getElementById('loadScr');if(load)load.style.display='flex'; | |
| requestAnimationFrame(()=>requestAnimationFrame(()=>{ | |
| try{ | |
| withSandboxMeta(()=>{applyTheme();newSkirmish();}); | |
| bridge.status='battle'; | |
| if(load)load.style.display='none'; | |
| if(typeof stopAttract==='function')stopAttract(); | |
| if(typeof mfFlowLayout==='function')mfFlowLayout(); | |
| if(typeof toast==='function'){ | |
| const op=bridge.request&&bridge.request.operation; | |
| const where=bridge.playerLocation?.display?.mapName||bridge.playerLocation?.display?.areaName||'selected battlefield'; | |
| toast(op&&op.missionType==='uga_brood_purge' | |
| ?('UGA OPERATION · '+where+' — purge the active Brood infestation') | |
| :('UGA OPERATION · '+where+' — break the '+String((op&&op.opponentFactionId)||'hostile').toUpperCase()+' hold')); | |
| } | |
| }catch(e){ | |
| if(load)load.style.display='none'; | |
| console.error('Galactic operation launch failed',e); | |
| rejectBridge('BATTLE_START_FAILED'); | |
| } | |
| })); | |
| } | |
| function scoreReport(win,abandoned){ | |
| const operation=bridge.request.operation; | |
| const elapsed=Math.max(0,Math.floor(Number(stats&&stats.t)||0)); | |
| const kills=Math.max(0,Math.floor(Number(stats&&stats.kills&&stats.kills[0])||0)); | |
| const losses=Math.max(0,Math.floor(Number(stats&&stats.kills&&stats.kills[1])||0)); | |
| const nests=Math.max(0,Math.floor(Number(stats&&stats.nests)||0)); | |
| let score=(win?68:14)+Math.min(16,nests*4)+Math.min(12,Math.floor(kills/6)) | |
| -Math.min(24,Math.floor(losses/4))-Math.min(8,Math.floor(elapsed/300)); | |
| score+=(bridge.operationEffects||describeOperationEffects(operation)).tacticalScoreDelta; | |
| score=Math.max(0,Math.min(100,Math.round(score))); | |
| if(win)score=Math.max(51,score);else score=Math.min(64,score); | |
| let secondary=abandoned?0:0; | |
| if(!abandoned&&typeof heroIdx!=='undefined'&&heroIdx>=0)secondary++; | |
| if(!abandoned&&losses<=Math.max(4,Math.floor(kills*.45)))secondary++; | |
| if(!abandoned&&elapsed>0&&elapsed<=20*60)secondary++; | |
| let injuryBand; | |
| if(abandoned)injuryBand='moderate'; | |
| else if(typeof heroIdx!=='undefined'&&heroIdx<0)injuryBand='severe'; | |
| else if(losses===0)injuryBand='none'; | |
| else if(losses/Math.max(1,kills+losses)<=.25)injuryBand='light'; | |
| else if(losses/Math.max(1,kills+losses)<=.65)injuryBand='moderate'; | |
| else injuryBand='severe'; | |
| const count={none:0,light:1,moderate:2,severe:3}[injuryBand]; | |
| const team=[operation.commanderId,...operation.specialistIds].sort((a,b)=>{ | |
| const ah=hash32(operation.resultSeed+':injury:'+a),bh=hash32(operation.resultSeed+':injury:'+b); | |
| return ah===bh?String(a).localeCompare(String(b)):ah.localeCompare(bh); | |
| }); | |
| return {outcome:win?'victory':'setback',score,primaryObjectiveComplete:!!win, | |
| secondaryObjectivesComplete:Math.max(0,Math.min(3,secondary)),injuryBand, | |
| injuredPersonnelIds:team.slice(0,count)}; | |
| } | |
| function persistReport(win,abandoned){ | |
| if(bridge.report)return true; | |
| if(Date.now()>bridge.request.expiresAt){ | |
| bridge.status='result-expired';bridge.reason='REQUEST_EXPIRED'; | |
| if(typeof toast==='function')toast('Operation link expired — return to NEXUS-VII to abandon or relaunch'); | |
| return false; | |
| } | |
| if(!bridge.reportCandidate){ | |
| const reportValue=scoreReport(!!win,!!abandoned); | |
| const validation=validateTacticalReport(reportValue,bridge.request.operation); | |
| if(!validation.ok){bridge.status='result-error';bridge.reason=validation.issues.join(',');return false;} | |
| const candidate={schemaVersion:1,kind:'MassfrontGalacticTacticalReportV1',nonce:bridge.nonce, | |
| accountId:bridge.request.accountId,operationId:bridge.request.operation.operationId, | |
| issuedAt:Date.now(),report:reportValue}; | |
| candidate.checksum=envelopeChecksum(candidate); | |
| /* The serialized candidate is the authority after the terminal edge. | |
| A denied first write must not let a later RETURN click re-score the | |
| same victory as an abandoned setback with a different timestamp. */ | |
| bridge.reportCandidateBytes=JSON.stringify(candidate); | |
| bridge.reportCandidate=freezeJson(JSON.parse(bridge.reportCandidateBytes)); | |
| } | |
| const record=bridge.reportCandidate,serialized=bridge.reportCandidateBytes; | |
| try{ | |
| const key=RESULT_PREFIX+bridge.nonce; | |
| sessionStorage.setItem(key,serialized); | |
| const stored=sessionStorage.getItem(key),readback=stored&&JSON.parse(stored); | |
| if(stored!==serialized||!readback||readback.checksum!==envelopeChecksum(readback) | |
| ||stableStringify(readback)!==stableStringify(record))throw new Error('result readback'); | |
| }catch(e){ | |
| bridge.status='result-storage-error';bridge.reason='RESULT_STORAGE_FAILED'; | |
| if(typeof toast==='function')toast('Tactical report could not be secured — remain on this screen and retry'); | |
| return false; | |
| } | |
| bridge.report=clone(record);bridge.status='terminal';bridge.reason=''; | |
| return true; | |
| } | |
| function paintReturnControls(){ | |
| if(!bridge.active||(!bridge.report&&bridge.status!=='result-expired'))return; | |
| const menu=document.getElementById('restartBtn'),cont=document.getElementById('goContinueBtn'); | |
| if(menu)menu.textContent=bridge.report?.report.outcome==='setback'?'↻ RETURN TO RETRY':'← RETURN TO NEXUS-VII'; | |
| if(cont){cont.textContent='▶ RETURN TO NEXUS-VII';cont.style.display='none';cont.disabled=false;} | |
| const rewards=document.getElementById('goRewards'); | |
| if(rewards&&!rewards.querySelector('.mfGalacticReport')){ | |
| const notice=document.createElement('div'); | |
| notice.className='goNotice mfGalacticReport '+(bridge.report?.report.outcome==='victory'?'good':'bad'); | |
| notice.textContent=bridge.report | |
| ?'UGA TACTICAL REPORT · '+bridge.report.report.score+'/100 · ' | |
| +bridge.report.report.secondaryObjectivesComplete+' SECONDARY OBJECTIVES · RETURN ROUTE VERIFIED' | |
| :'UGA LINK EXPIRED · RETURN TO NEXUS-VII TO ABANDON OR RELAUNCH'; | |
| rewards.appendChild(notice); | |
| } | |
| } | |
| function returnRejectedToNexus(){ | |
| if(bridge.returning)return false; | |
| bridge.returning=true;bridge.status='returning-rejected'; | |
| const target=explorationReturnTarget('?groundRejected='+encodeURIComponent(bridge.nonce)); | |
| try{if(!target)throw new Error('Content return unavailable');location.href=target;return true;} | |
| catch(e){bridge.returning=false;bridge.status='return-error';bridge.reason='RETURN_NAVIGATION_FAILED';return false;} | |
| } | |
| function returnToNexus(){ | |
| if(!bridge.active||bridge.returning)return false; | |
| if(!bridge.report&&!persistReport(false,true)){ | |
| if(bridge.status==='result-expired')return returnRejectedToNexus(); | |
| return false; | |
| } | |
| bridge.returning=true;bridge.status='returning'; | |
| const target=explorationReturnTarget('?groundResult='+encodeURIComponent(bridge.nonce)); | |
| try{if(!target)throw new Error('Content return unavailable');location.href=target;return true;} | |
| catch(e){bridge.returning=false;bridge.status='return-error';bridge.reason='RETURN_NAVIGATION_FAILED';if(typeof toast==='function')toast('NEXUS-VII return route could not be opened');return false;} | |
| } | |
| function returnExistingReportToNexus(record){ | |
| bridge.active=false;bridge.isolated=false;bridge.report=clone(record);bridge.returning=true;bridge.status='returning-existing';bridge.reason=''; | |
| const target=explorationReturnTarget('?groundResult='+encodeURIComponent(bridge.nonce)); | |
| try{if(!target)throw new Error('Content return unavailable');location.href=target;return true;} | |
| catch(e){bridge.returning=false;bridge.status='return-error';bridge.reason='RETURN_NAVIGATION_FAILED';return false;} | |
| } | |
| /* Close every base-career reward seam. The originals remain byte-for-byte in | |
| force for Standard, Campaign, Weekly and Training when no validated Galactic | |
| operation is active. */ | |
| if(typeof metaGrant==='function'){ | |
| const base=metaGrant;metaGrant=function(){if(bridge.isolated)return null;return base.apply(this,arguments);}; | |
| } | |
| if(typeof developRecord==='function'){ | |
| const base=developRecord;developRecord=function(){if(bridge.isolated)return null;return base.apply(this,arguments);}; | |
| } | |
| if(typeof endgameRecord==='function'){ | |
| const base=endgameRecord;endgameRecord=function(){if(bridge.isolated)return null;return base.apply(this,arguments);}; | |
| } | |
| if(typeof dailyRecord==='function'){ | |
| const base=dailyRecord;dailyRecord=function(){if(bridge.isolated)return null;return base.apply(this,arguments);}; | |
| } | |
| if(typeof mfConquestReward==='function'){ | |
| const base=mfConquestReward;mfConquestReward=function(){if(bridge.isolated)return null;return base.apply(this,arguments);}; | |
| } | |
| if(typeof invGrantMatchLoot==='function'){ | |
| const base=invGrantMatchLoot;invGrantMatchLoot=function(){if(bridge.isolated)return null;return base.apply(this,arguments);}; | |
| } | |
| if(typeof invGrantModeReward==='function'){ | |
| const base=invGrantModeReward;invGrantModeReward=function(){if(bridge.isolated)return null;return base.apply(this,arguments);}; | |
| } | |
| if(typeof sessCanSnapshot==='function'){ | |
| const base=sessCanSnapshot;sessCanSnapshot=function(){if(bridge.isolated)return false;return base.apply(this,arguments);}; | |
| } | |
| if(typeof sessSnapshot==='function'){ | |
| const base=sessSnapshot;sessSnapshot=function(){if(bridge.isolated)return false;return base.apply(this,arguments);}; | |
| } | |
| if(typeof goalDef==='function'){ | |
| const base=goalDef;goalDef=function(){ | |
| if(bridge.active&&bridge.tacticalGoal)return bridge.tacticalGoal; | |
| return base.apply(this,arguments); | |
| }; | |
| } | |
| if(typeof goalStatus==='function'){ | |
| const base=goalStatus;goalStatus=function(){ | |
| const status=base.apply(this,arguments); | |
| if(!bridge.active||!bridge.tacticalGoal||bridge.tacticalGoal.id!=='domination')return status; | |
| return bridge.tacticalGoal.em+' '+bridge.tacticalGoal.hud+' · '+String(status||'').replace(/^\u26f3\s*/, ''); | |
| }; | |
| } | |
| if(typeof applyCrate==='function'){ | |
| const base=applyCrate;applyCrate=function(kind){ | |
| if(bridge.isolated&&(kind?.id==='data'||kind?.id==='mats')){ | |
| if(typeof mfCrateClaimer!=='undefined')mfCrateClaimer=-1; | |
| bridge.suppressedPersistentCrates++; | |
| if(typeof pickupToast==='function')pickupToast(kind,'UGA RECOVERY RESERVED FOR NEXUS-VII DEBRIEF'); | |
| else if(typeof toast==='function')toast('UGA recovery reserved for NEXUS-VII debrief'); | |
| return null; | |
| } | |
| return base.apply(this,arguments); | |
| }; | |
| } | |
| if(typeof adShowPostMatchAd==='function'){ | |
| const base=adShowPostMatchAd;adShowPostMatchAd=function(){ | |
| if(bridge.isolated){bridge.suppressedPostMatchAds++;return null;} | |
| return base.apply(this,arguments); | |
| }; | |
| } | |
| if(typeof AD_PROVIDER!=='undefined'&&AD_PROVIDER&&typeof AD_PROVIDER.reportImpression==='function'){ | |
| const base=AD_PROVIDER.reportImpression; | |
| AD_PROVIDER.reportImpression=function(){ | |
| if(bridge.isolated){bridge.suppressedBillboardImpressions++;return null;} | |
| return base.apply(this,arguments); | |
| }; | |
| } | |
| if(typeof deployCarrier==='function'){ | |
| const base=deployCarrier;deployCarrier=function(){ | |
| const out=base.apply(this,arguments); | |
| if(bridge.active&&!bridge.packageApplied&&carrier.phase===2){ | |
| try{applyDeploymentPackage();} | |
| catch(e){ | |
| bridge.packageSummary={error:'DEPLOYMENT_PACKAGE_FAILED'}; | |
| console.error('Galactic deployment package failed',e); | |
| rejectBridge('DEPLOYMENT_PACKAGE_FAILED'); | |
| } | |
| } | |
| return out; | |
| }; | |
| } | |
| if(typeof unitTick==='function'){ | |
| const base=unitTick;unitTick=function(dt){ | |
| const out=base.apply(this,arguments);repairNanitesTick(dt);return out; | |
| }; | |
| } | |
| if(typeof endGame==='function'){ | |
| const base=endGame;endGame=function(win){ | |
| const out=base.apply(this,arguments); | |
| if(bridge.active){persistReport(!!win,false);setTimeout(paintReturnControls,1520);setTimeout(paintReturnControls,1900);} | |
| return out; | |
| }; | |
| } | |
| if(typeof returnToMainMenu==='function'){ | |
| const base=returnToMainMenu; | |
| /* Protected Training needs the base document to remain alive long enough | |
| for its mandatory commissioning gate to mount. This narrow escape hatch | |
| bypasses only the Galactic return interception; it still runs the full | |
| ordinary menu cleanup owned by main.js/departure.js. */ | |
| if(typeof window.__MF_RETURN_TO_BASE_FOR_COMMISSIONING__!=='function') | |
| window.__MF_RETURN_TO_BASE_FOR_COMMISSIONING__=function(){return base.apply(this,arguments);}; | |
| returnToMainMenu=function(){ | |
| if(bridge.active){ | |
| /* Protected new-career Training still owes the player the mandatory | |
| faction/Commander gate. tutSkip() schedules that gate immediately | |
| after this base-menu reset; navigating to NEXUS-VII here unloads the | |
| callback before it can run and strands the career in training. */ | |
| let gateState=null; | |
| try{gateState=window.MFNewCareerFactionGate&&window.MFNewCareerFactionGate.state();}catch(e){} | |
| if(gateState&&gateState.phase==='training')return base.apply(this,arguments); | |
| return returnToNexus(); | |
| } | |
| if(bridge.isolated)return returnRejectedToNexus(); | |
| return base.apply(this,arguments); | |
| }; | |
| } | |
| if(typeof continueToNextMap==='function'){ | |
| const base=continueToNextMap;continueToNextMap=function(){ | |
| if(bridge.active)return returnToNexus(); | |
| if(bridge.isolated)return returnRejectedToNexus(); | |
| return base.apply(this,arguments); | |
| }; | |
| } | |
| if(typeof mfVictoryContinue==='function'){ | |
| const base=mfVictoryContinue;mfVictoryContinue=function(){ | |
| if(bridge.active)return returnToNexus(); | |
| if(bridge.isolated)return returnRejectedToNexus(); | |
| return base.apply(this,arguments); | |
| }; | |
| } | |
| let menuReturnContext=null,warRoomRouteOpening=false; | |
| const api={validateEntryTicket,validateRouteRequest,validateRequest,validateRequestMirror,validateTacticalReport,validateResultMirror, | |
| resolveExpeditionLocation:location=>clone(expeditionLocationAllowed(location)), | |
| groundAreaForMission:missionId=>clone(GALACTIC_GROUND_AREA_AUTHORITY[missionId]||null), | |
| resolveOperationBattlefield:(operation,contentVersion=CONTENT_VERSION)=>clone(operationBattlefield(operation,contentVersion)), | |
| describeOperationLoadScreen:(operation,location)=>clone(operationLoadScreenModel(operation,location)), | |
| resolveTacticalObjective:operation=>clone(resolveTacticalObjective(operation)), | |
| clearClassicFallback, | |
| isMenuReturnContext:value=>value!==null&&value===menuReturnContext&&bridge.status==='menu-route', | |
| validateDeploymentContract,describeOperationEffects,checksum:envelopeChecksum}; | |
| Object.defineProperties(api,{ | |
| active:{enumerable:true,get:()=>bridge.active}, | |
| status:{enumerable:true,get:()=>bridge.status}, | |
| reason:{enumerable:true,get:()=>bridge.reason}, | |
| request:{enumerable:true,get:()=>clone(bridge.request)}, | |
| report:{enumerable:true,get:()=>clone(bridge.report)}, | |
| operationEffects:{enumerable:true,get:()=>clone(bridge.operationEffects)}, | |
| playerLocation:{enumerable:true,get:()=>clone(bridge.playerLocation)}, | |
| runtimeMapId:{enumerable:true,get:()=>bridge.runtimeMapId}, | |
| packageApplied:{enumerable:true,get:()=>bridge.packageApplied}, | |
| packageSummary:{enumerable:true,get:()=>clone(bridge.packageSummary)}, | |
| menuRouteActive:{enumerable:true,get:()=>bridge.menuRouteActive}, | |
| classicFallbackActive:{enumerable:true,get:()=>classicFallbackOn()}, | |
| isolation:{enumerable:true,get:()=>Object.freeze({active:bridge.isolated,droppedSessionPreserved:true, | |
| persistentCratesSuppressed:bridge.suppressedPersistentCrates,postMatchAdsSuppressed:bridge.suppressedPostMatchAds, | |
| billboardImpressionsSuppressed:bridge.suppressedBillboardImpressions})} | |
| }); | |
| window.__MF_GALACTIC_BRIDGE=Object.freeze(api); | |
| /* Galactic Command owns both strategic-home destinations. Base submenus keep | |
| their ordinary Back controls, but any attempt to reveal the retired home | |
| or War Room returns to UGA Command. menuRouteActive still records a routed | |
| submenu; unconditional interception also prevents unrelated legacy exits | |
| from resurrecting a second strategic shell. */ | |
| if(typeof showFrontScreen==='function'&&!showFrontScreen.__mfGalacticWarTable){ | |
| const baseShowFrontScreen=showFrontScreen; | |
| showFrontScreen=function(id){ | |
| if(warRoomRouteOpening&&id==='warScr')return baseShowFrontScreen.apply(this,arguments); | |
| if(window.__MF_COMMISSIONING_RETURN_ACTIVE__!==true&&!classicFallbackOn()&¤tFlagOn()&&(id==='startScreen'||id==='warScr')){ | |
| bridge.menuRouteActive=false; | |
| if(typeof mfOpenExploration==='function'){ | |
| // Only this synchronous call can authorize returning an existing | |
| // neutral career. It grants no faction, personnel, or mission access. | |
| menuReturnContext=Object.freeze({kind:'validated-menu-return'}); | |
| let opening; | |
| try{opening=mfOpenExploration('campaign_hub',{menuReturn:menuReturnContext});} | |
| finally{menuReturnContext=null;} | |
| Promise.resolve(opening).then(opened=>{ | |
| if(!opened)baseShowFrontScreen.call(this,id); | |
| }); | |
| return true; | |
| } | |
| } | |
| return baseShowFrontScreen.apply(this,arguments); | |
| }; | |
| showFrontScreen.__mfGalacticWarTable=true; | |
| } | |
| const search=String(location.search||''); | |
| if(search===CLASSIC_FALLBACK_SEARCH){ | |
| armClassicFallback();stripBridgeQuery();bridge.status='classic-fallback';bridge.reason=''; | |
| let fallbackTries=0; | |
| const fallbackTick=function(){ | |
| if(++fallbackTries>1200){ | |
| bridge.status='classic-fallback-home'; | |
| if(typeof showFrontScreen==='function')showFrontScreen('startScreen'); | |
| return; | |
| } | |
| if(typeof bootConfirmed==='undefined'||!bootConfirmed||typeof window.openWarRoom!=='function'){ | |
| setTimeout(fallbackTick,50);return; | |
| } | |
| dismissEntryOverlays(); | |
| if(typeof mfDismissIntroForGalacticRoute==='function')mfDismissIntroForGalacticRoute(); | |
| bridge.status=window.openWarRoom()===false?'classic-fallback-home':'classic-fallback-war-room'; | |
| if(bridge.status==='classic-fallback-home'&&typeof showFrontScreen==='function')showFrontScreen('startScreen'); | |
| }; | |
| setTimeout(fallbackTick,0);return; | |
| } | |
| const routeMatch=search.match(/^\?galacticRoute=([A-Za-z0-9_-]{16,128})$/); | |
| if(routeMatch){ | |
| bridge.nonce=routeMatch[1];bridge.status='waiting-for-base-route'; | |
| let routeTries=0; | |
| const routeTick=function(){ | |
| if(++routeTries>1200){rejectMenuRoute('BASE_BOOT_TIMEOUT',bridge.nonce);return;} | |
| if(typeof bootConfirmed==='undefined'||!bootConfirmed){setTimeout(routeTick,50);return;} | |
| const profileId=currentProfileId(),now=Date.now(),ticket=readSessionJson(ENTRY_KEY); | |
| if(!currentFlagOn()){rejectMenuRoute('EXPERIMENT_DISABLED',bridge.nonce);return;} | |
| const entryValidation=validateEntryTicket(ticket,now,profileId); | |
| if(!entryValidation.ok){rejectMenuRoute(entryValidation.issues.join(','),bridge.nonce);return;} | |
| const request=readSessionJson(ROUTE_PREFIX+bridge.nonce); | |
| const routeValidation=validateRouteRequest(request,bridge.nonce,profileId,now); | |
| if(!routeValidation.ok){rejectMenuRoute(routeValidation.issues.join(','),bridge.nonce);return;} | |
| if(!baseRouteTargetReady(request.routeId)){setTimeout(routeTick,50);return;} | |
| if(!consumeRouteRecord(bridge.nonce)){rejectMenuRoute('ROUTE_CONSUME_FAILED',bridge.nonce);return;} | |
| /* A validated same-tab submenu handoff is navigation inside one game, | |
| not a fresh launch. Dismiss the cinematic only after consuming the | |
| secured record so an arbitrary query string cannot suppress it. */ | |
| /* revealFront() schedules the ordinary first-run account gate shortly | |
| after the shell appears. A valid UGA handoff can finish inside that | |
| delay, so dismissing only the cinematic allowed the account modal to | |
| reopen over Standard's deployment plan and swallow START BATTLE. | |
| Satisfy all entry overlays here, after validation and nonce | |
| consumption, just as the ground-operation bridge does. */ | |
| dismissEntryOverlays(); | |
| if(typeof mfDismissIntroForGalacticRoute==='function')mfDismissIntroForGalacticRoute(); | |
| stripBridgeQuery(); | |
| bridge.status='menu-route';bridge.reason=''; | |
| if(!openBaseRoute(request.routeId,request.location))rejectMenuRoute('ROUTE_TARGET_UNAVAILABLE',bridge.nonce); | |
| }; | |
| setTimeout(routeTick,0); | |
| return; | |
| } | |
| if(/(?:^\?|&)galacticRoute=/.test(search)){ | |
| stripBridgeQuery(); | |
| bridge.status='menu-route-rejected';bridge.reason='ROUTE_NONCE_INVALID'; | |
| return; | |
| } | |
| const match=search.match(/^\?groundOperation=([A-Za-z0-9_-]{16,128})$/); | |
| if(!match){ | |
| if(/(?:^\?|&)groundOperation=/.test(search))rejectBridge('NONCE_INVALID'); | |
| return; | |
| } | |
| bridge.nonce=match[1];bridge.status='waiting-for-base'; | |
| let tries=0; | |
| const bootTick=function(){ | |
| if(++tries>1200){rejectBridge('BASE_BOOT_TIMEOUT');return;} | |
| if(typeof bootConfirmed==='undefined'||!bootConfirmed){setTimeout(bootTick,50);return;} | |
| const profileId=currentProfileId(),now=Date.now(),ticket=readSessionJson(ENTRY_KEY); | |
| if(!currentFlagOn()){rejectBridge('EXPERIMENT_DISABLED');return;} | |
| const entryValidation=validateEntryTicket(ticket,now,profileId); | |
| if(!entryValidation.ok){rejectBridge(entryValidation.issues.join(','));return;} | |
| const requestMirror=readSessionJson(REQUEST_PREFIX+bridge.nonce); | |
| const requestValidation=validateRequestMirror(requestMirror,bridge.nonce,profileId,now,ticket); | |
| if(!requestValidation.ok){rejectBridge(requestValidation.issues.join(','));return;} | |
| const request=requestMirror.request,storedResult=readSessionRecord(RESULT_PREFIX+bridge.nonce); | |
| bridge.operationEffects=describeOperationEffects(request.operation); | |
| if(storedResult.present){ | |
| if(!storedResult.readable){rejectBridge('RESULT_UNREADABLE');return;} | |
| const resultValidation=validateResultMirror(storedResult.value,bridge.nonce,profileId,request,now); | |
| if(!resultValidation.ok){rejectBridge(resultValidation.issues.join(','));return;} | |
| bridge.request=clone(request);returnExistingReportToNexus(storedResult.value);return; | |
| } | |
| bridge.request=clone(request);beginBattle(); | |
| }; | |
| setTimeout(bootTick,0); | |
| })(); | |