1import { anyShapeCollectionVisible } from "./collections.js";2import { elements } from "./dom.js";3import { state } from "./state.js";4import { viewMaxZoom } from "./navigation.js";5import { refreshCatalog } from "./library.js";6import { currentGridExtent, gridMaxLevel, pinInGridCell } from "./grid.js";7import { drawnFeatureCount, listableFeatures } from "./visibility.js";89export function exposeDiagnostics() {10 const snapshot = () => ({11 volume: state.volume?.title,12 world: state.world?.title,13 lens: state.lens?.name,14 zoom: state.engine?.getView().getZoom(),15 center: state.engine?.getView().getCenter(),16 resolution: state.engine?.getView().getResolution(),17 nativeMaxZoom: state.lens?.maxZoom,18 maxZoom: state.lens ? viewMaxZoom(state.lens) : null,19 interpolate: state.lens?.interpolate,20 tileStats: { ...state.tileStats },21 // The registry is state.features now, but the snapshot keeps the key the22 // parity diffs already know, so cross-build comparisons stay line-for-line23 // readable.24 pins: state.features.length,25 eligibleLocations: state.eligibleLocations,26 domNodes: document.querySelectorAll("*").length,27 canvases: document.querySelectorAll("canvas").length,28 rasterCacheSize: 64,29 labelsHeld: state.labelsHeld,30 hoveredPin: state.hoveredPin?.location.title || null,31 selectedPin: state.selectedPin?.location.title || null,32 fitZoom: state.fitZoom,33 filters: {34 // The state holds one hiddenCollections set now, but the snapshot keeps35 // the key the parity diffs already know, so cross-build comparisons36 // stay line-for-line readable.37 hiddenCategories: [...state.hiddenCollections].sort(),38 collapsedSections: [...state.collapsedSections].sort(),39 },40 ui: {41 sidebarCollapsed: document.querySelector(".app-shell")42 ?.classList.contains("sidebar-collapsed") ?? false,43 detailOpen: !elements.detail.hidden,44 detailTitle: elements.detail.hidden45 ? null46 : elements.detailTitle.textContent || null,47 searchQuery: state.search,48 searchResultsVisible: !elements.searchResults.hidden,49 soloChip: elements.soloChip.hidden ? null : elements.soloChip.textContent,50 visibleCountText: elements.visibleCount.textContent,51 overviewDocked: state.overviewDocked,52 dockFolded: state.dockFolded,53 dockDismissed: state.dockDismissed,54 subgridVisible: state.subgridVisible,55 },56 zones: {57 visible: anyShapeCollectionVisible(),58 count: state.zoneRecords.size,59 focused: state.zoneRecords.get(state.focusedZoneID)?.zone.title || null,60 highlighted: [...state.highlightedZones]61 .map((zoneID) => state.zoneRecords.get(zoneID)?.zone.title)62 .filter(Boolean),63 focusedPins: state.features.filter((pin) => !pin.filteredHidden &&64 pin.passesZoneFilters).length,65 },66 // The three places one filter has to land at once, side by side so a diff67 // can see them disagree. The first two are the model's own count of what68 // the map is drawing; the last two are what the footer and the dock69 // actually say. A surface that forgot to recount after a filter moved70 // shows up here as text out of step with the numbers beside it -- which is71 // the whole of what the tour's sync check reads.72 sync: {73 drawn: drawnFeatureCount(),74 listable: listableFeatures().length,75 footerText: elements.visibleCount.textContent,76 dockText: elements.dockCount.textContent,77 dockFlag: elements.dockFlag.hidden ? null : elements.dockFlag.textContent,78 dockRows: elements.searchResults.querySelectorAll(".search-result").length,79 searching: Boolean(state.search),80 },81 grid: {82 enabled: state.gridEnabled,83 system: state.gridSystem,84 prefix: state.gridCell,85 maximumDepth: gridMaxLevel(),86 extent: state.gridEnabled ? currentGridExtent() : null,87 cells: [88 ...state.sources.grid.getFeatures(),89 ...state.sources.gridContext.getFeatures(),90 ].map((feature) => feature.get("gridCell")),91 priorityPins: state.gridCell92 ? state.features.filter((pin) => !pin.filteredHidden && pinInGridCell(pin)).length93 : 0,94 },95 });96 window.__atlasDebug = {97 snapshot,98 // Raster layers are reachable so a render fault can be narrowed to the99 // complete base or the deep detail riding on top of it.100 setLayerVisible: (name, visible) => state.layers[name]?.setVisible(visible),101 // The library refresh normally rides the desktop event bridge; exposing102 // it lets a plain browser -- the parity tour, a test -- drive the same103 // reconciliation by hand.104 refreshCatalog,105 };106 window.render_game_to_text = () => JSON.stringify({107 coordinateSystem: "ATLAS:PIXELS; origin top-left; x increases right; y decreases downward",108 ...snapshot(),109 });110 window.advanceTime = () => state.engine?.renderSync();111}