2026-06-21 14:09:35 +09:00
"use strict" ;
( function ( ) {
const params = new URLSearchParams ( location . search || "" ) ;
const enabled = params . get ( "debug" ) === "1" || params . get ( "debug" ) === "true" ;
2026-07-05 18:01:36 +09:00
window . _ _tarinaiDebugOverlayEnabled = enabled ;
2026-06-21 14:09:35 +09:00
if ( ! enabled ) return ;
2026-06-27 19:22:38 +09:00
const host = document . createElement ( "div" ) ;
host . id = "debugOverlay" ;
host . style . cssText = [
"position:fixed" , "left:8px" , "top:76px" , "z-index:99999" , "width:min(620px, calc(100vw - 16px))" , "max-height:calc(100vh - 92px)" ,
"overflow:hidden" , "font:11px/1.42 ui-monospace,SFMono-Regular,Menlo,monospace" , "color:#f7fbff" ,
"background:rgba(18,24,30,.86)" , "border:1px solid rgba(255,255,255,.18)" , "border-radius:10px" ,
"pointer-events:auto" , "box-shadow:0 8px 24px rgba(0,0,0,.22)"
] . join ( ";" ) ;
const bar = document . createElement ( "div" ) ;
bar . style . cssText = [
"display:flex" , "gap:6px" , "align-items:center" , "padding:6px 8px" , "border-bottom:1px solid rgba(255,255,255,.14)" ,
"background:rgba(255,255,255,.06)" , "user-select:none" , "-webkit-user-select:none"
] . join ( ";" ) ;
const title = document . createElement ( "span" ) ;
title . style . cssText = "flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" ;
const copyBtn = document . createElement ( "button" ) ;
copyBtn . type = "button" ;
copyBtn . textContent = "Copy" ;
const minBtn = document . createElement ( "button" ) ;
minBtn . type = "button" ;
minBtn . textContent = "Min" ;
for ( const btn of [ copyBtn , minBtn ] ) {
btn . style . cssText = [
"font:11px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace" , "color:#f7fbff" , "background:rgba(255,255,255,.12)" ,
"border:1px solid rgba(255,255,255,.22)" , "border-radius:6px" , "padding:3px 7px" , "cursor:pointer"
] . join ( ";" ) ;
}
bar . append ( title , copyBtn , minBtn ) ;
const body = document . createElement ( "pre" ) ;
body . style . cssText = [
"margin:0" , "padding:8px 10px" , "max-height:calc(100vh - 128px)" , "overflow:auto" , "white-space:pre-wrap" ,
"user-select:text" , "-webkit-user-select:text" , "cursor:text"
2026-06-21 14:09:35 +09:00
] . join ( ";" ) ;
2026-06-27 19:22:38 +09:00
body . tabIndex = 0 ;
host . append ( bar , body ) ;
document . addEventListener ( "DOMContentLoaded" , ( ) => document . body . appendChild ( host ) ) ;
let copiedMessageUntil = 0 ;
let minimized = false ;
let lastText = "" ;
function selectBodyText ( ) {
const range = document . createRange ( ) ;
range . selectNodeContents ( body ) ;
const selection = window . getSelection ? . ( ) ;
if ( selection ) {
selection . removeAllRanges ( ) ;
selection . addRange ( range ) ;
}
}
copyBtn . addEventListener ( "click" , async ( ev ) => {
ev . preventDefault ( ) ;
const text = lastText || body . textContent || "" ;
try {
await navigator . clipboard ? . writeText ? . ( text ) ;
copiedMessageUntil = Date . now ( ) + 1400 ;
copyBtn . textContent = "Copied" ;
} catch ( _ ) {
selectBodyText ( ) ;
copiedMessageUntil = Date . now ( ) + 1400 ;
copyBtn . textContent = "Select" ;
}
} ) ;
minBtn . addEventListener ( "click" , ( ev ) => {
ev . preventDefault ( ) ;
minimized = ! minimized ;
body . style . display = minimized ? "none" : "block" ;
host . style . width = minimized ? "min(360px, calc(100vw - 16px))" : "min(620px, calc(100vw - 16px))" ;
minBtn . textContent = minimized ? "Open" : "Min" ;
} ) ;
2026-06-21 14:09:35 +09:00
let lastEvents = [ ] ;
let lastAudio = [ ] ;
2026-06-29 16:21:58 +09:00
window . TarinaiEvents . on ( "*" , ev => { lastEvents . unshift ( ev . type ) ; if ( lastEvents . length > 10 ) lastEvents . length = 10 ; } ) ;
window . TarinaiEvents . on ( "audio:play" , ev => { lastAudio . unshift ( ` ${ ev . detail ? . id || "?" } : ${ ev . detail ? . category || "?" } ` ) ; if ( lastAudio . length > 8 ) lastAudio . length = 8 ; } ) ;
2026-06-27 19:22:38 +09:00
function fmtMs ( v ) {
const n = Number ( v || 0 ) ;
return n >= 10 ? n . toFixed ( 1 ) : n . toFixed ( 2 ) ;
}
function shortLabel ( label ) {
return String ( label || "?" )
. replace ( /^update\.phase\./ , "phase." )
. replace ( /^update\./ , "u." )
. replace ( /^render\./ , "r." )
. replace ( /^frame\./ , "f." ) ;
}
function topMap ( map , limit = 4 ) {
return Object . entries ( map || { } ) . sort ( ( a , b ) => b [ 1 ] - a [ 1 ] ) . slice ( 0 , limit ) . map ( ( [ k , v ] ) => ` ${ k } : ${ v } ` ) . join ( ", " ) || "-" ;
}
2026-07-10 16:40:06 +09:00
function saveStatsLines ( ) {
const codec = window . TarinaiSaveCodec ;
const stats = codec ? . getLastEncodeStats ? . ( ) ;
if ( ! stats ) return [ ] ;
const load = codec ? . getLastDecodeStats ? . ( ) ;
const sections = stats . sections || { } ;
const details = stats . details || { } ;
const rawTotal = Math . max ( 1 , Number ( stats . rawBinaryBytes || stats . binaryBytes ) || 0 ) ;
const part = key => {
const bytes = Number ( sections [ key ] ) || 0 ;
return ` ${ bytes } B/ ${ Math . round ( bytes / rawTotal * 100 ) } % ` ;
} ;
const ageSeconds = Math . max ( 0 , Math . round ( ( Date . now ( ) - ( Number ( stats . createdAt ) || Date . now ( ) ) ) / 1000 ) ) ;
const decode = load ? ` decode: ${ fmtMs ( load . decodeMs ) } ms ` : "" ;
return [
` save stats ${ stats . hashChars || 0 } chars stored: ${ stats . binaryBytes || 0 } B raw: ${ stats . rawBinaryBytes || stats . binaryBytes || 0 } B ${ stats . compressed ? "deflate" : "plain" } encode: ${ fmtMs ( stats . encodeMs ) } ms ${ decode } age: ${ ageSeconds } s ` ,
` save format: ${ part ( "format" ) } strings: ${ part ( "stringTable" ) } ( ${ details . stringTableEntries || 0 } ) world: ${ part ( "world" ) } ` ,
` save tarinai basic: ${ part ( "tarinaiBasic" ) } seed: ${ part ( "tarinaiSeed" ) } rel: ${ part ( "tarinaiRelationships" ) } count: ${ details . tarinaiCount || 0 } relRows: ${ details . relationshipRows || 0 } parents: ${ details . parentLinks || 0 } ` ,
` save items index: ${ part ( "itemIndex" ) } normal: ${ part ( "normalItems" ) } ( ${ details . normalItemCount || 0 } ) physics: ${ part ( "physicsItems" ) } ( ${ details . physicsItemCount || 0 } ) ` ,
` save extra normal: ${ details . normalItemExtraEncodedBytes || 0 } B(raw ${ details . normalItemExtraRawUtf8Bytes || 0 } B) physics: ${ details . physicsItemExtraEncodedBytes || 0 } B(raw ${ details . physicsItemExtraRawUtf8Bytes || 0 } B) seedRaw: ${ details . birthSeedRawUtf8Bytes || 0 } B ` ,
` save accounted: ${ stats . accountedBytes || 0 } B unaccounted: ${ stats . unaccountedBytes || 0 } B compressed: ${ stats . compressedBytes || 0 } B saved: ${ stats . compressionSavedBytes || 0 } B text: ${ stats . textBits || 15 } bit payload: ${ stats . payloadChars || 0 } chars ` ,
] ;
}
2026-06-27 19:22:38 +09:00
function perfLines ( perf ) {
const self = ( perf . topSelf || perf . entries || [ ] ) . filter ( e => e . label !== "frame.total" ) . slice ( 0 , 10 ) ;
const incl = ( perf . topInclusive || perf . entries || [ ] ) . filter ( e => e . label !== "frame.total" ) . slice ( 0 , 6 ) ;
const selfLines = self . map ( ( e , i ) => ` ${ String ( i + 1 ) . padStart ( 2 , " " ) } . ${ shortLabel ( e . label ) . padEnd ( 28 , " " ) } self ${ fmtMs ( e . selfAvg ) } ms total ${ fmtMs ( e . avg ) } ms ${ e . selfPct || 0 } % ` ) ;
const inclLine = incl . map ( e => ` ${ shortLabel ( e . label ) } : ${ fmtMs ( e . avg ) } ` ) . join ( " " ) ;
return [
` heavy self avg ` ,
... ( selfLines . length ? selfLines : [ " -" ] ) ,
` heavy inclusive ${ inclLine || "-" } ` ,
] ;
}
function diagnosticHints ( diag , perf ) {
const hints = [ ] ;
const entries = perf . topSelf || perf . entries || [ ] ;
const top = entries [ 0 ] ;
if ( top && top . selfAvg >= 4 ) hints . push ( ` hot: ${ shortLabel ( top . label ) } ${ fmtMs ( top . selfAvg ) } ms self ` ) ;
if ( ( diag . spatial ? . rebuildsThisFrame || 0 ) > 1 ) hints . push ( ` spatial rebuild/frame ${ diag . spatial . rebuildsThisFrame } ` ) ;
if ( ( diag . creatures ? . smooth || 0 ) > 0 ) hints . push ( ` smooth motion ${ diag . creatures . smooth } ` ) ;
if ( ( diag . scheduler ? . activeRealtime || 0 ) > 120 ) hints . push ( ` realtime items ${ diag . scheduler . activeRealtime } ` ) ;
if ( ( diag . scheduler ? . heap || 0 ) > 700 ) hints . push ( ` scheduled heap ${ diag . scheduler . heap } ` ) ;
2026-07-10 16:40:06 +09:00
if ( ( diag . effects ? . skipped || 0 ) > 0 ) hints . push ( ` effect update skip ${ diag . effects . skipped } ` ) ;
if ( ( diag . effectRender ? . skipped || 0 ) > 0 ) hints . push ( ` effect draw skip ${ diag . effectRender . skipped } ` ) ;
if ( ( diag . creatures ? . fullBudgetSkips || 0 ) > 0 ) hints . push ( ` creature bg skip ${ diag . creatures . fullBudgetSkips } ` ) ;
2026-06-27 19:22:38 +09:00
if ( ( diag . items ? . bucketRebuildsTotal || 0 ) > 0 && diag . items ? . bucketsDirty ) hints . push ( "item buckets dirty" ) ;
const m = diag . scheduler ? . mechanicalStats || diag . items ? . mechanicalStats || diag . scheduler ? . physics ? . mechanical || null ;
if ( m ? . candidates > 250 || m ? . pairs > 180 ) hints . push ( ` mechanical candidates ${ m . candidates || "?" } pairs ${ m . pairs || m . solved || "?" } ` ) ;
const c = diag . scheduler ? . constraintStats || null ;
if ( c ? . totalLinks > 80 ) hints . push ( ` constraints links ${ c . totalLinks } ran ${ c . ran } ` ) ;
return hints . join ( " " ) || "-" ;
}
2026-06-27 21:50:05 +09:00
const DEBUG _REFRESH _MS = 750 ;
2026-06-21 14:09:35 +09:00
window . setInterval ( ( ) => {
const w = window . world ;
2026-06-29 16:21:58 +09:00
const input = window . TarinaiInputMode . snapshot ( ) || { } ;
2026-06-24 13:48:22 +09:00
const diag = w ? . lastRuntimeDiagnostics || w ? . runtimeDiagnostics ? . ( ) || { } ;
const behaviorCounts = diag . behavior ? . counts || { } ;
2026-06-27 19:22:38 +09:00
const topBehaviors = topMap ( behaviorCounts , 5 ) ;
2026-06-29 16:21:58 +09:00
const perf = diag . perf || window . TarinaiPerf . snapshot ( ) || { } ;
2026-06-27 19:22:38 +09:00
const metrics = perf . metrics || { } ;
2026-06-30 22:30:37 +09:00
const visual = perf . visual || { } ;
2026-06-27 19:22:38 +09:00
const currentDpr = ( typeof uiCache !== "undefined" && uiCache ) ? uiCache . canvasDpr : "?" ;
const creature = diag . creatures || { } ;
const scheduler = diag . scheduler || { } ;
2026-07-10 16:40:06 +09:00
const effects = diag . effects || { } ;
const effectRender = diag . effectRender || { } ;
2026-06-27 19:22:38 +09:00
const terrain = diag . terrain || { } ;
const lines = [
` tarinai v ${ window . TARINAI _VERSION || "?" } debug profiler: ${ perf . enabled ? "on" : "off" } ${ Date . now ( ) < copiedMessageUntil ? " copied" : " copy:button/select" } ` ,
2026-07-10 16:40:06 +09:00
` fps ${ window . _ _tarinaiFps ? ? "?" } frame ${ fmtMs ( perf . frameAvg || 0 ) } ms profile: ${ perf . profile || "?" } visual effects: ${ visual . effects || "?" } shadows: ${ visual . shadows || "?" } details: ${ visual . details || perf . tier || "?" } dprScale: ${ perf . dprScale || 1 } ` ,
2026-06-27 19:22:38 +09:00
` live ${ diag . runtime ? . liveTarinai ? ? ( w ? . tarinai || [ ] ) . length } items ${ ( w ? . items || [ ] ) . length } ants ${ ( w ? . ants || [ ] ) . length } effects ${ ( w ? . effects || [ ] ) . length } ` ,
2026-07-10 16:40:06 +09:00
... saveStatsLines ( ) ,
` visible layered: ${ metrics [ "render.visibleLayered" ] ? ? "?" } back: ${ metrics [ "render.visibleBackItems" ] ? ? "?" } effects: ${ metrics [ "render.visibleEffects" ] ? ? "?" } / ${ effectRender . budget ? ? metrics [ "render.effectDrawBudget" ] ? ? "?" } skip: ${ effectRender . skipped ? ? metrics [ "render.effectDrawSkipped" ] ? ? "?" } ` ,
` creatures full: ${ creature . full ? ? 0 } bg: ${ creature . fullBudgetUsed ? ? 0 } / ${ creature . fullBudget ? ? "?" } bgSkip: ${ creature . fullBudgetSkips ? ? 0 } offSkip: ${ creature . offscreenSkipped ? ? 0 } realtime: ${ creature . realtime ? ? 0 } smooth: ${ creature . smooth ? ? 0 } skipped: ${ creature . skipped ? ? 0 } selected: ${ creature . selected ? ? 0 } urgent: ${ creature . urgent ? ? 0 } visible: ${ creature . visible ? ? 0 } sleepPhys: ${ creature . sleepPhysical ? ? 0 } passive: ${ creature . passivePhysical ? ? 0 } ` ,
2026-06-27 19:22:38 +09:00
` creature lanes ${ topMap ( creature . lanes , 6 ) } smoothStates ${ topMap ( creature . smoothStates , 4 ) } ` ,
` spatial rebuild ${ diag . spatial ? . rebuildsThisFrame ? ? 0 } /frame total: ${ diag . spatial ? . rebuildsTotal ? ? 0 } partial: ${ diag . spatial ? . partialRebuildsTotal ? ? 0 } dirty: ${ diag . spatial ? . dirtyMarksThisFrame ? ? 0 } deferred: ${ diag . spatial ? . deferredReads ? ? 0 } reason: ${ diag . spatial ? . dirtyReason || diag . spatial ? . lastRebuildReason || "-" } ` ,
` spatial dirtyReasons ${ topMap ( diag . spatial ? . dirtyReasons , 4 ) } rebuildReasons ${ topMap ( diag . spatial ? . rebuildReasons , 4 ) } ` ,
` nearby obstacleRects hit: ${ diag . nearby ? . obstacleRectHits ? ? 0 } miss: ${ diag . nearby ? . obstacleRectMisses ? ? 0 } built: ${ diag . nearby ? . obstacleRectBuilt ? ? 0 } bypass: ${ diag . nearby ? . obstacleRectBypass ? ? 0 } constraintDirty mark: ${ diag . constraintDirty ? . marked ? ? 0 } coal: ${ diag . constraintDirty ? . coalesced ? ? 0 } ` ,
` scheduler heap: ${ scheduler . heap ? ? 0 } realtime: ${ scheduler . activeRealtime ? ? scheduler . realtime ? ? 0 } ran: ${ scheduler . lastRan ? ? 0 } itemRt: ${ scheduler . realtimeRan ? ? 0 } itemDue: ${ scheduler . dueRan ? ? 0 } constraints: ${ scheduler . constraints ? ? 0 } postPairs: ${ scheduler . postConstraintPairs ? ? 0 } rodStretch: ${ fmtMs ( scheduler . constraintStats ? . rodStretchMax || 0 ) } corr: ${ scheduler . constraintStats ? . rodCorrections ? ? 0 } ` ,
2026-06-27 21:50:05 +09:00
` items active: ${ scheduler . activeRealtime ? ? 0 } asleep: ${ scheduler . sleeping ? ? scheduler . heap ? ? 0 } budgetPairs: ${ scheduler . physicsBudget ? . maxPairs ? ? "?" } substeps: ${ scheduler . physics ? . mechanical ? . substeps ? ? scheduler . mechanicalStats ? . substeps ? ? "?" } fenceCCD: ${ Boolean ( ( scheduler . physics ? . mechanical || scheduler . mechanicalStats ) ? . fenceSensitive ) } ` ,
2026-07-10 16:40:06 +09:00
` effects update: ${ effects . updated ? ? 0 } / ${ effects . budget ? ? effects . total ? ? 0 } skipped: ${ effects . skipped ? ? 0 } compact: ${ effects . compacted ? ? 0 } dropped: ${ effects . dropped ? ? 0 } render: ${ effectRender . drawn ? ? 0 } / ${ effectRender . budget ? ? "?" } rSkip: ${ effectRender . skipped ? ? 0 } level: ${ effectRender . level || visual . effects || "?" } ` ,
2026-06-27 19:22:38 +09:00
` terrain dirty raw: ${ terrain . raw ? ? 0 } global: ${ terrain . global ? ? 0 } chunk: ${ terrain . chunk ? ? 0 } coalesced: ${ terrain . coalesced ? ? 0 } reasons: ${ topMap ( terrain . reasons , 4 ) } ` ,
2026-06-24 17:39:44 +09:00
` work ai ${ diag . work ? . stats ? . aiRuns ? ? 0 } / ${ diag . work ? . stats ? . aiSkips ? ? 0 } env ${ diag . work ? . stats ? . envRuns ? ? 0 } / ${ diag . work ? . stats ? . envSkips ? ? 0 } coll ${ diag . work ? . stats ? . collisionRuns ? ? 0 } / ${ diag . work ? . stats ? . collisionSkips ? ? 0 } ` ,
2026-06-27 19:22:38 +09:00
` hints ${ diagnosticHints ( diag , perf ) } ` ,
... perfLines ( perf ) ,
2026-06-24 13:48:22 +09:00
` behavior forced: ${ diag . behavior ? . forced ? ? 0 } locked: ${ diag . behavior ? . locked ? ? 0 } top ${ topBehaviors || "-" } ` ,
2026-06-27 19:22:38 +09:00
` terrain v ${ w ? . terrainVersion ? ? 0 } spatial v ${ w ? . spatialVersion ? ? 0 } drawList ${ ( w ? . drawList || [ ] ) . length } dirty: ${ Boolean ( diag . render ? . drawListDirty ) } ` ,
` item buckets: ${ diag . items ? . bucketTypes ? ? 0 } dirty: ${ Boolean ( diag . items ? . bucketsDirty ) } id-map: ${ diag . items ? . idMapSize ? ? 0 } bucketRebuilds: ${ diag . items ? . bucketRebuildsTotal ? ? 0 } ` ,
` dpr ${ currentDpr } cache ${ window . TARINAI _APP ? . cacheName || "?" } sw ${ navigator . serviceWorker ? . controller ? "controlled" : "uncontrolled" } ` ,
` input ${ input . currentMode || "?" } touchFirst: ${ Boolean ( input . touchFirst ) } coarse: ${ Boolean ( input . pointerCoarse ) } hover: ${ Boolean ( input . hasHover ) } compact: ${ Boolean ( input . isCompactViewport ) } ` ,
2026-06-29 16:21:58 +09:00
` audio ${ lastAudio . join ( ", " ) || window . TarinaiAudio . lastPlayedId || "-" } ` ,
2026-06-21 14:09:35 +09:00
` events ${ lastEvents . join ( ", " ) } ` ,
2026-06-27 19:22:38 +09:00
] ;
lastText = lines . join ( "\n" ) ;
title . textContent = minimized
? ` tarinai v ${ window . TARINAI _VERSION || "?" } fps ${ window . _ _tarinaiFps ? ? "?" } frame ${ fmtMs ( perf . frameAvg || 0 ) } ms `
: ` tarinai debug fps ${ window . _ _tarinaiFps ? ? "?" } frame ${ fmtMs ( perf . frameAvg || 0 ) } ms ` ;
if ( Date . now ( ) >= copiedMessageUntil && copyBtn . textContent !== "Copy" ) copyBtn . textContent = "Copy" ;
if ( ! minimized ) body . textContent = lastText ;
2026-06-27 21:50:05 +09:00
} , DEBUG _REFRESH _MS ) ;
2026-06-21 14:09:35 +09:00
} ) ( ) ;