'use strict'; const vm=require('vm'); const {assert,functionSource,app,css,html,read,buildMeta}=require('./helpers/app-source'); const server=read('server.js'); const packageVersion=JSON.parse(read('package.json')).version; assert(packageVersion===buildMeta.PACKAGE_VERSION&&buildMeta.APP_VERSION==='48.0','v47.84 canonical version marker is missing'); assert(html.includes('
マップ
')&&!html.includes('minimapStatus')&&!html.includes('周辺マップ'),'Map title or count removal is incomplete'); assert(html.includes('ショップ')&&functionSource('makeBoard').includes('ショップ'),'Shop labels were not localized'); assert(!functionSource('drawMinimap').includes('nearbyPlayers')&&!functionSource('drawMinimap').includes('visibleMetas.length')&&!functionSource('drawMinimap').includes('storeCount||0'),'Map still exposes player, board, or shop counts'); assert(!css.includes('.active-board-boundary-dash'),'Retired active-board orbit dots remain'); const renderDrag=functionSource('renderDragFrame'),overlay=functionSource('updatePickupHandleOverlay'); assert(functionSource('activeDrawingLineColorIndex').includes('startColorIndex')&&overlay.includes('activeDrawingLineColorIndex(path)'),'Pickup overlay color is not derived from the actively drawn line'); assert(renderDrag.includes('color=startColor')&&renderDrag.includes('colorChanged=drawing.lastRenderedColor!==color')&&renderDrag.includes('drawing.lastRenderedColor=color'),'Repeated grabs can retain a stale knob color'); const maybeStore=functionSource('maybeOpenStore'),normalizeStore=functionSource('normalizeStore'),renderBoard=functionSource('renderBoardNow'); assert(app.includes('STORE_CHANCE=1/10')&&server.includes('const STORE_CHANCE=1/10'),'Client/server shop appearance probability is not three times the former 1/30 rate'); assert(functionSource('storeObstacleCell').includes('puzzleOf(meta).obstacles')&&maybeStore.includes('cell=storeObstacleCell(meta)')&&maybeStore.includes('pathIndex:-1'),'Shop generation is not anchored to a former obstacle cell'); assert(normalizeStore.includes('directCell')&&functionSource('storeCellForMeta').includes('store?.cell')&&functionSource('summarizeBoardV2').includes('store?.cell'),'Obstacle-site shop coordinates are not durable'); assert(server.includes('meta?.puzzle?.obstacles||[]')&&server.includes('pathIndex:-1,cellIndex:-1,cell')&&functionSource('sanitizeStateForPuzzle').includes('st.store.cell=obstacleCell')&&functionSource('makeBoard').includes('obstacleNodes.set')&&renderBoard.includes("key===ckey(...storeCell)?'none':''"),'The replaced obstacle remains visible under the shop'); assert(functionSource('personalEconomyMode').includes('cloudProfile'),'Personal economy mode is not stable across transient connectivity changes'); for(const name of['inventoryEntries','spentScoreTotal','pruneAndCount'])assert(functionSource(name).includes('personalEconomyMode()'),`${name} can fall back to shared-board purchases during an outage`); assert(functionSource('purchaseStoreItem').includes("personalEconomyMode()&&!onlinePlayerEconomy()")&&functionSource('purchaseStoreItem').includes('共有の所持数を確認できないため購入できません。'),'Offline personal purchases are not blocked safely'); assert(functionSource('applyPlayerEconomyEnvelope').includes('data.playerEarnedScore=Number.isSafeInteger(player.earnedScore)')&&!functionSource('applyPlayerEconomyEnvelope').includes('playerEarnedScore=Math.max'),'Personal gem balance is not server-authoritative'); assert(functionSource('rememberStateSignatures').includes('!personalEconomyMode()'),'World-store purchase deltas can still reduce personal gems'); assert(functionSource('finalizeAtGate').includes('commitConnectedLineVisuals(b,pi)')&&functionSource('joinTips').includes('commitConnectedLineVisuals'),'Connected-line visuals are not scheduled after connection'); assert(functionSource('commitConnectedLineVisuals').includes('invalidateLineGraphCaches')&&functionSource('commitConnectedLineVisuals').includes('renderBoard(b)')&&functionSource('commitConnectedLineVisuals').includes('queueLineWidthRefresh'),'Line thickness inheritance can remain stale or synchronously block pointer input after connection'); const preserveSource=functionSource('preserveSolvedBoardState'); assert(preserveSource.includes('solvedSource?._summaryOnly?baseState:solvedSource')&&preserveSource.includes('preserved.solved=true')&&preserveSource.includes('delete preserved._summaryOnly'),'Solved summary recovery can still erase full route data'); assert(!app.includes('applyWorldSignal')&&!app.includes('BroadcastChannel'),'Retired cross-tab board reconciliation remains'); assert(functionSource('hydrateMeta').includes('currentState?.solved&&!loadedState.solved')&&functionSource('hydrateMeta').includes('preserveSolvedBoardState(currentState,loadedState)'),'Rehydration can still restore an older unsolved state'); assert(functionSource('evictHydratedBoardDetails').includes('summarizeBoardV2(target,currentState'),'Eviction can still cache a stale pre-clear summary'); const context={SCORE_VERSION:6,deepClone:value=>JSON.parse(JSON.stringify(value)),mergeSpecialProgress:(a,b)=>({crossings:[...(a?.crossings||[]),...(b?.crossings||[])]}),mergePurchases:(a=[],b=[])=>[...a,...b],normalizeState:()=>({paths:[],specialProgress:{crossings:[]}})}; vm.createContext(context);vm.runInContext(`${preserveSource}\nthis.preserveSolvedBoardState=preserveSolvedBoardState;`,context); const summary={_summaryOnly:true,solved:true,expanded:true,solvedBy:'A',scoreAwarded:500,specialProgress:{crossings:[[1,1]]}},incoming={solved:false,expanded:false,paths:[{cells:[[0,0],[0,1]]}],specialProgress:{crossings:[]},scoreAwarded:0}; const preserved=context.preserveSolvedBoardState(summary,incoming); assert(preserved.solved===true&&preserved.paths.length===1&&preserved.paths[0].cells.length===2&&!('_summaryOnly' in preserved),'Solved-summary repair did not retain incoming full route data'); console.log('v47.84 map, store, economy, line inheritance, and clear persistence regression test passed');