import {soldierFeatures,commanderFeatures,COMMANDS,FORMATIONS,STRATEGIES} from './sensors.js'; import {behaviorCatalog as C} from './catalog.js'; import {NEUTRAL_INPUT,validInput,applyCharacterInput} from './controls.js'; import {ObservationHistory,flagSignature,squadIndex} from './temporal.js'; import {availableSquads,applyMacroOrder,expireMacroOrders} from './macro.js'; const orderToken=(state,u)=>JSON.stringify([Boolean(u.commandOverride),u.commandOverride||state.commands[u.team]]); export function createNeuralController({state,stats,model,worker,sensors,onError=()=>{},onReady=()=>{},onOrder=()=>{}}){ let sequence=0,pending=null,decisionClock=.1,commanderClock=2.5;const inputs=new Map(),requestedCommanders=new Set(),history=new ObservationHistory(),commanderSignatures=new Map(); function fail(message){model.ready=false;model.busy=false;model.disabled=true;inputs.clear();pending=null;state.paused=true;onError(message);} function reset(){sequence++;pending=null;inputs.clear();requestedCommanders.clear();history.reset();commanderSignatures.clear();decisionClock=.1;commanderClock=2.5;model.busy=false;} function tick(dt){ if(!model.ready||!worker)return;decisionClock+=dt;commanderClock+=dt;if(model.models?.commander?.inputDim===116)expireMacroOrders(state); if(pending){if(performance.now()-pending.sent>3000)fail('Model yanıtı zaman aşımına uğradı.');return;} if(decisionClock<.1)return;decisionClock%=.1;const start=performance.now(); sensors.shareIntel('viking');sensors.shareIntel('mongol'); if(model.models?.commander?.inputDim===116)for(const team of ['viking','mongol']){ const signature=flagSignature(state,sensors,team); if(state.commanders[team].mode==='ai'&&commanderSignatures.get(team)!==signature)requestedCommanders.add(team); } const units=state.units.filter(u=>u.alive),commanders=[]; if(commanderClock>=2.5||requestedCommanders.size){commanderClock%=2.5;for(const team of ['viking','mongol'])if(state.commanders[team].mode==='ai'||requestedCommanders.has(team)){ commanders.push({team,manual:requestedCommanders.has(team),generation:state.commanders[team].generation,revision:state.commands[team].revision,row:model.models?.commander?.inputDim===116?history.commander(state,sensors,team):commanderFeatures(state,sensors,team),squadMask:availableSquads(state,team)}); commanderSignatures.set(team,flagSignature(state,sensors,team)); }} requestedCommanders.clear(); pending={matchId:stats.matchId,sequence:++sequence,sent:performance.now()};model.busy=true; worker.postMessage({type:'infer',matchId:stats.matchId,sequence,ids:units.map(u=>u.id),revisions:units.map(u=>orderToken(state,u)),rows:units.map(u=>model.models?.soldier?.inputDim===523?history.soldier(state,sensors,u):soldierFeatures(state,sensors,u)),commanders}); stats.workloadSamples.push({t:performance.now(),ms:performance.now()-start}); } function receive(data){ if(data.type==='ready'){model.ready=true;model.disabled=false;model.models=data.models;model.label=data.label;model.method=data.method;onReady(data);return;} if(data.type==='error'){if(data.matchId&&data.matchId!==stats.matchId)return;if(data.sequence&&data.sequence!==pending?.sequence)return;fail(data.message);return;} if(data.type!=='inputs'||data.matchId!==stats.matchId||data.sequence!==pending?.sequence)return; if(data.inputs.length!==data.ids.length||data.inputs.some(i=>!validInput(i))){fail('Geçersiz model girdisi.');return;} model.busy=false;pending=null;stats.decisions++;stats.aiMs+=data.elapsed;stats.workloadSamples.push({t:performance.now(),ms:data.elapsed}); data.ids.forEach((id,i)=>{const u=state.units.find(u=>u.id===id&&u.alive);if(!u)return;if(orderToken(state,u)!==data.revisions[i])return;inputs.set(id,{input:data.inputs[i],at:state.elapsed,order:data.revisions[i]});u.reason='Eğitilmiş sinir ağı · yön / nişan / ateş';u.intent=data.inputs[i].fire?'ateş et':'hareket girdisi';u.decisionMs=data.elapsed;model.latency[u.team].push(data.elapsed);model.latency[u.team]=model.latency[u.team].slice(-30);}); if(stats.decisions%10===0){stats.examples ||= [];data.ids.forEach((id,i)=>stats.examples.push({time:state.elapsed,id,input:data.inputs[i],model:model.models?.soldier?.name||'soldier-v1'}));if(stats.examples.length>2000)stats.examples.splice(0,stats.examples.length-2000);} for(const c of data.commanders){const old=state.commands[c.team];if((state.commanders[c.team].mode!=='ai'&&!c.manual)||state.commanders[c.team].generation!==c.generation||old.revision!==c.revision)continue; if(c.macro){try{const result=applyMacroOrder(state,c.team,c.macro);if(result.count)onOrder(c.team,result.command);}catch(error){fail(error.message);}continue;} const commandId=COMMANDS[c.command],formation=FORMATIONS[c.formation],strategyId=STRATEGIES[c.strategy];if(!C.COMMAND_CATALOG[commandId]||!formation||!strategyId){fail('Geçersiz komutan çıktısı.');return;} const preset=C.COMMAND_CATALOG[commandId],changed=old.commandId!==commandId||old.formation!==C.FORMATION_LABELS[formation]||old.strategyId!==strategyId; Object.assign(old,preset,{commandId,formation:C.FORMATION_LABELS[formation],strategyId:strategyId==='none'?null:strategyId,combatMode:C.STRATEGIES[strategyId]?.combatMode||old.combatMode,source:'neural',strategySource:'neural',note:'Eğitilmiş komutan ağı',revision:old.revision+(changed?1:0)}); if(changed)onOrder(c.team,old); } } function apply(u,dt){const entry=inputs.get(u.id),input=entry&&entry.order===orderToken(state,u)&&state.elapsed-entry.at<=.6?entry.input:NEUTRAL_INPUT;applyCharacterInput(state,stats,u,input,dt);} function recordHumanOrder(team,order,unitIds,source='human-live'){ const selected=state.units.filter(u=>u.alive&&u.team===team&&unitIds.includes(u.id));if(!selected.length)return; const allies=state.units.filter(u=>u.alive&&u.team===team),equal=a=>a.length===selected.length&&a.every(u=>unitIds.includes(u.id)); let group=equal(allies)?0:-1;for(let i=0;i<8&&group<0;i++)if(equal(allies.filter(u=>squadIndex(u)===i)))group=i+1; const task=order.task==='hold'?'defend':order.task==='capture_return'?'objective':order.task||'objective',commandId=order.commandId||(task==='defend'?'defend_base':'attack_flag'); const action={command:COMMANDS.indexOf(commandId),formation:FORMATIONS.indexOf(order.formation||'line'),strategy:0,group,task:['objective','move','assault','defend','heal'].indexOf(commandId==='heal_at_base'?'heal':task),x:order.x??state.flags[team].home.x,y:order.y??state.flags[team].home.y,ttl:10}; stats.commanderDemonstrations ||= [];stats.commanderDemonstrations.push({schema:'field-orders.commander-demonstration.v1',source,time:state.elapsed,team,unitIds:selected.map(u=>u.id),observation:history.commander(state,sensors,team),action,trainable:group>=0&&action.command>=0&&action.formation>=0&&action.task>=0}); if(stats.commanderDemonstrations.length>2000)stats.commanderDemonstrations.shift(); } return {recordHumanOrder,requestCommand(team){requestedCommanders.add(team);decisionClock=.1;},reset,tick,receive,apply,fail,get inputs(){return inputs;}}; }