'use strict'; const {assert,app,css,functionSource,read}=require('./helpers/app-source'); const browserBenchmark=read('test/browser-performance-benchmark.js'); const scheduler=functionSource('scheduleReactionRender'); assert(app.includes('REACTION_TARGET_FPS=30')&&scheduler.includes('reactionDelayTimer=setTimeout')&&scheduler.includes('requestAnimationFrame')&&scheduler.includes('reactionNextDrawAt'),'Reaction rendering is not deadline-scheduled at the existing 30 FPS cadence'); assert(scheduler.includes("document.visibilityState==='hidden'")&&functionSource('cancelReactionRenderScheduler').includes('reactionLastDraw=0'),'Reaction scheduling is not suspended and reset across hidden-page lifecycle changes'); const prepare=functionSource('prepareReactionModel'); for(const token of ['preparedReactionCracks','preparedLaserRays','preparedOrbitModel','preparedFireworkBloom','preparedReactionBurst'])assert(prepare.includes(token)||app.includes(token),`Prepared reaction model is missing ${token}`); assert(functionSource('normalizeRealtimeReaction').includes('reaction.prepared=prepareReactionModel(reaction)')&&functionSource('drawStyledReaction').includes('reaction.prepared'),'Immutable reaction work is not prepared once and reused'); assert(app.includes('REACTION_GLYPH_CACHE_MAX_ENTRIES=160')&&app.includes('REACTION_GLYPH_CACHE_MAX_BYTES=12*1024*1024'),'Reaction glyph cache is not explicitly bounded'); assert(app.includes('REACTION_STATIC_PATH_CACHE_MAX_ENTRIES=32')&&functionSource('reactionStaticPath').includes('reactionStaticPathCacheEvictions'),'Reaction static Path2D cache is not bounded or measured'); assert(functionSource('drawOrbitReaction').includes('reactionOrbitRingPath')&&functionSource('drawFireworkReaction').includes('reactionFireworkLaunchPath'),'Static reaction geometry does not use the exact Path2D cache'); const glyph=functionSource('reactionGlyphSprite'); assert(glyph.includes('reactionGlyphCacheHits')&&glyph.includes('reactionGlyphCacheMisses')&&glyph.includes('reactionGlyphCacheEvictions'),'Reaction glyph cache metrics or eviction are missing'); assert(functionSource('drawReactionEmoji').includes('drawImage')&&functionSource('drawReactionEmoji').includes('fillText'),'Cached glyph drawing lacks its exact direct-render fallback'); const reactionLayer=functionSource('drawReactionLayer'); for(const metric of ['reactionFrame','reactionComposite','activeReactions','visibleReactions','peakVisibleReactions','preparedReactionModels','reactionOverload'])assert(app.includes(metric),`Effect performance metric is missing: ${metric}`); for(const style of ['giant','laser','orbit','firework','comet'])assert(functionSource('drawStyledReaction').includes(`style==='${style}'`),`Full reaction renderer is missing: ${style}`); assert(!functionSource('drawStyledReaction').match(/interactionActive|lightweightRendering|autoReduced|fallbackStyle|visibleReactionLimit/),'Reaction renderer contains automatic or interaction-driven visual degradation'); assert(reactionLayer.includes('for(const[id,reaction]of realtimeReactions)')&&!reactionLayer.match(/slice\(|break;|visibleReactionLimit/),'Valid overlapping reactions can be dropped from rendering'); const aurora=functionSource('startAuroraRgbAnimation'),auroraState=functionSource('updateAuroraAnimationState'); assert(aurora.includes('AURORA_COLOR_INTERVAL')&&auroraState.includes('auroraVisiblePathCount')&&!aurora.includes('querySelector'),'Aurora is not controlled by tracked visible paths'); assert(functionSource('writeAuroraRgb').includes('auroraColorHost().style.setProperty')&&!functionSource('writeAuroraRgb').includes('document.body.style'),'Aurora color invalidation is not scoped to the world'); assert(functionSource('useInventoryItemLoaded').slice(functionSource('useInventoryItemLoaded').indexOf('if(item.lineColor)'),functionSource('useInventoryItemLoaded').indexOf('if(item.lineEffect)')).includes('renderAll()'),'Equipping a line color does not immediately repaint lines and gates'); assert(app.includes('GEM_PARTICLE_POOL_LIMIT=72')&&functionSource('playGemCollectionAnimation').includes('document.createDocumentFragment()')&&functionSource('releaseGemParticle').includes('gemParticlePool.push'),'Gem animations do not use a bounded, batched node pool'); assert(app.includes('GEM_PARTICLE_KEYFRAME_TEMPLATE=Object.freeze([')&&app.includes('GEM_PARTICLE_ANIMATION_OPTIONS_TEMPLATE=Object.freeze(')&&functionSource('takeGemParticle').includes('if(!particle._gemKeyframes)')&&functionSource('animateGemParticle').includes('particle.animate(keyframes,options)'),'Gem animation templates are still allocated for every playback or shared unsafely across concurrent particles'); assert(app.includes('COMPLETION_NODE_POOL_LIMIT=16')&&functionSource('finishCompletionVisual').includes('releaseCompletionNode'),'Completion visuals do not return their nodes to a bounded pool'); const inventory=functionSource('renderInventoryPanel'); assert(app.includes('inventoryCategoryViews=new Map()')&&app.includes('inventoryItemViews=new Map()')&&!inventory.includes('inventoryList.replaceChildren()'),'Owned cosmetic rendering still rebuilds the full list'); assert(functionSource('setItemIcon').includes("image.loading='lazy'")&&functionSource('setItemIcon').includes("image.decoding='async'"),'Flag assets are not lazy-loaded and asynchronously decoded'); assert(css.includes('content-visibility:auto')&&css.includes('contain-intrinsic-size'),'Offscreen cosmetic cards do not use render containment'); assert(functionSource('syncInventoryCursorSelection').includes('inventorySelectedCursorItemId')&&!functionSource('syncInventoryCursorSelection').includes('querySelectorAll'),'Cursor equip still scans the entire catalog'); assert(functionSource('renderInventoryPanel').includes('inventoryItemsUnchanged')||functionSource('updateInventoryItemView').includes('inventoryItemsUnchanged'),'Inventory reconciliation does not skip unchanged cards'); const inventoryUse=functionSource('useInventoryItemLoaded'); for(const marker of ['if(item.lineColor)','if(item.reactionStyle)','if(item.scoreLens)'])assert(inventoryUse.slice(inventoryUse.indexOf(marker)).includes('patchInventoryItems'),'Cosmetic equip is not locally patched'); assert(app.includes('renderReactionSample:options=>renderReactionSample(options)')&&functionSource('renderReactionSample').includes('normalizedLife'),'Exact-lifetime visual-equivalence test hook is missing'); for(const gate of ["timing(result.setup.reaction,'reactionPublish').p95<=2","timing(result.setup.gem,'gemEffectSetup').p95<=3","timing(result.setup.completion,'completionEffectSetup').p95<=3","patch.p95<=8","focusRetained","auroraColorWrites","gemBatches===0","effectNodes===0"])assert(browserBenchmark.includes(gate),`Browser release gate is missing: ${gate}`); for(const interaction of ['benchmark-pickup-effect','benchmark-edge-pan-effect','benchmark-overview-effect'])assert(browserBenchmark.includes(interaction),`Effect interaction probe is missing: ${interaction}`); assert(!app.includes('autoReducedEffects')&&!app.includes('reactionQualityTier')&&!app.includes('dropReactionForPerformance'),'Automatic effect quality degradation was introduced'); console.log('Effects and cosmetics performance architecture smoke test passed');