2026-05-20 13:50:56 +09:00
import { generateMap , MAP _W , MAP _H , indexOf } from "./mapGenerator.js" ;
2026-05-21 02:55:58 +09:00
import {
CUSTOM _NAMES ,
FORCED _NAMES ,
NAME _KANJI _POOLS ,
NAME _PARTS ,
NAME _PROBABILITIES ,
NAME _TEMPLATES ,
NAME _TEMPLATE _WEIGHTS ,
generateTemplateName ,
} from "./names.js" ;
2026-05-20 17:15:09 +09:00
import { adminBoundaryMetrics , majorCityCoreIntegrity } from "./qualityMetrics.js" ;
2026-05-20 13:50:56 +09:00
const result = document . getElementById ( "result" ) ;
const logLines = [ ] ;
let failed = 0 ;
2026-05-21 02:55:58 +09:00
const [ namesSource , mapGeneratorSource , testSource ] = await Promise . all ( [
fetch ( "./names.js" ) . then ( ( response ) => response . text ( ) ) ,
fetch ( "./mapGenerator.js" ) . then ( ( response ) => response . text ( ) ) ,
fetch ( "./test.js" ) . then ( ( response ) => response . text ( ) ) ,
] ) ;
2026-05-20 13:50:56 +09:00
function assert ( condition , message ) {
if ( condition ) logLines . push ( ` OK: ${ message } ` ) ;
else {
failed += 1 ;
logLines . push ( ` NG: ${ message } ` ) ;
}
}
try {
const map = generateMap ( 12345 ) ;
const other = generateMap ( 54321 ) ;
const size = MAP _W * MAP _H ;
const urbanCellCount = [ ... map . landuse ] . filter ( ( value ) => value >= 2 && value <= 8 ) . length ;
const cityPopulations = map . modernCities . map ( ( city ) => city . population || 0 ) ;
const maxPopulation = Math . max ( ... cityPopulations ) ;
const minPopulation = Math . min ( ... cityPopulations ) ;
const landElevations = [ ... map . elevation ] . filter ( ( _ , i ) => ! map . sea [ i ] ) ;
const meanElevation = landElevations . reduce ( ( sum , value ) => sum + value , 0 ) / landElevations . length ;
const elevationStdDev = Math . sqrt ( landElevations . reduce ( ( sum , value ) => sum + ( value - meanElevation ) * * 2 , 0 ) / landElevations . length ) ;
const modernPaths = [
... map . railways ,
... map . branchRailways ,
... map . externalRailways ,
... ( map . ringRailways || [ ] ) ,
... map . nationalRoads ,
... ( map . ringRoads || [ ] ) ,
... map . expressways ,
... ( map . ringExpressways || [ ] ) ,
... map . externalRoads ,
... map . externalExpressways ,
] ;
const endpointDegree = new Map ( ) ;
for ( const path of modernPaths ) {
if ( path . length < 2 ) continue ;
for ( const point of [ path [ 0 ] , path [ path . length - 1 ] ] ) {
const key = point . join ( "," ) ;
endpointDegree . set ( key , ( endpointDegree . get ( key ) || 0 ) + 1 ) ;
}
}
const maxModernEndpointDegree = Math . max ( 0 , ... endpointDegree . values ( ) ) ;
let maxCoastalElevationStep = 0 ;
for ( let y = 1 ; y < MAP _H - 1 ; y ++ ) {
for ( let x = 1 ; x < MAP _W - 1 ; x ++ ) {
const i = indexOf ( x , y ) ;
if ( map . sea [ i ] ) continue ;
for ( const [ dx , dy ] of [ [ 1 , 0 ] , [ - 1 , 0 ] , [ 0 , 1 ] , [ 0 , - 1 ] ] ) {
const ni = indexOf ( x + dx , y + dy ) ;
if ( map . sea [ ni ] ) maxCoastalElevationStep = Math . max ( maxCoastalElevationStep , Math . abs ( map . elevation [ i ] - map . elevation [ ni ] ) ) ;
}
}
}
let railExpressHighMountainCells = 0 ;
for ( const path of [ ... map . railways , ... map . branchRailways , ... ( map . ringRailways || [ ] ) , ... map . externalRailways , ... map . expressways , ... ( map . ringExpressways || [ ] ) , ... map . externalExpressways ] ) {
for ( const [ x , y ] of path ) {
const i = indexOf ( x , y ) ;
if ( map . elevation [ i ] > 0.82 ) railExpressHighMountainCells += 1 ;
}
}
2026-05-20 15:02:37 +09:00
let trunkHighElevationCells = 0 ;
for ( const path of modernPaths ) {
for ( const [ x , y ] of path ) {
if ( map . elevation [ indexOf ( x , y ) ] > 0.72 ) trunkHighElevationCells += 1 ;
}
}
const flatPlainCells = [ ... map . plain ] . filter ( ( value , i ) => ! map . sea [ i ] && value > 0.72 && map . slope [ i ] < 0.12 ) . length ;
const largeMountainCities = map . modernCities . filter ( ( city ) => {
const i = indexOf ( city . x , city . y ) ;
return ( city . population || 0 ) >= 250000 && ( map . elevation [ i ] > 0.66 || map . plain [ i ] < 0.18 || map . slope [ i ] > 0.88 ) ;
} ) ;
const capitalInside = map . prefecturalCapital && map . prefectureMask [ indexOf ( map . prefecturalCapital . x , map . prefecturalCapital . y ) ] ;
2026-05-20 17:15:09 +09:00
const allNameable = map . entitiesForNames || [ ] ;
const uniqueNames = new Set ( allNameable . map ( ( item ) => item . name ) ) ;
const duplicateNameRatio = allNameable . length ? 1 - uniqueNames . size / allNameable . length : 0 ;
2026-05-21 02:55:58 +09:00
const activePoolChars = new Set ( Object . values ( NAME _KANJI _POOLS ) . flat ( ) . flatMap ( ( part ) => Array . from ( String ( part ) ) ) ) ;
const namedEntityCount = [
... map . villages ,
... map . ports ,
... map . crossings ,
... map . passes ,
... map . markets ,
... map . castles ,
... map . castleTowns ,
... map . modernCities ,
... map . stations ,
... map . industrialZones ,
... map . interchanges ,
... map . logisticsParks ,
... map . satelliteCities ,
... map . newTowns ,
... map . castleRuins ,
... map . externalGateways ,
... map . adminCenters ,
] . filter ( ( item ) => item ? . id && item ? . name ) . length ;
2026-05-20 17:15:09 +09:00
const villageClusterMean = map . villages . length
? map . villages . reduce ( ( sum , p ) => sum + ( map . settlementCluster ? . [ indexOf ( p . x , p . y ) ] || 0 ) , 0 ) / map . villages . length
: 0 ;
const meaningfulTransportNodes = [
... map . modernCities ,
... map . ports ,
... map . markets ,
... map . externalGateways ,
... map . interchanges ,
... map . industrialZones ,
... map . logisticsParks ,
] ;
const endpointPaths = [
... map . railways ,
... map . branchRailways ,
... map . externalRailways ,
... map . nationalRoads ,
... map . expressways ,
... map . externalRoads ,
... map . externalExpressways ,
... ( map . icAccessRoads || [ ] ) ,
] ;
const endpointDistances = endpointPaths . flatMap ( ( path ) => path . length >= 2 ? [ path [ 0 ] , path [ path . length - 1 ] ] : [ ] )
. map ( ( [ x , y ] ) => Math . min ( ... meaningfulTransportNodes . map ( ( p ) => Math . hypot ( p . x - x , p . y - y ) ) ) ) ;
const saneEndpointRatio = endpointDistances . length
? endpointDistances . filter ( ( d ) => d <= 10 ) . length / endpointDistances . length
: 1 ;
const adminMetrics = adminBoundaryMetrics ( map ) ;
const cityCoreIntegrity = majorCityCoreIntegrity ( map ) ;
2026-05-20 13:50:56 +09:00
2026-05-21 02:55:58 +09:00
assert ( NAME _KANJI _POOLS && Array . isArray ( NAME _KANJI _POOLS . modifiers ) , "NAME_KANJI_POOLS exists" ) ;
assert ( NAME _TEMPLATES && NAME _TEMPLATES . modifierTerrain ? . slots ? . length === 2 , "NAME_TEMPLATES exists" ) ;
assert ( NAME _TEMPLATE _WEIGHTS && NAME _TEMPLATE _WEIGHTS . generic ? . modifierTerrain > 0 , "NAME_TEMPLATE_WEIGHTS exists" ) ;
assert ( NAME _PROBABILITIES && NAME _PROBABILITIES . contextCategoryWeights ? . generic , "NAME_PROBABILITIES exists" ) ;
const removedContextModule = "placeName" + "Context.js" ;
assert ( ! namesSource . includes ( removedContextModule ) && ! mapGeneratorSource . includes ( removedContextModule ) && ! testSource . includes ( removedContextModule ) , "removed name-context import is absent" ) ;
assert ( Object . keys ( NAME _KANJI _POOLS ) . every ( ( key ) => Array . isArray ( NAME _KANJI _POOLS [ key ] ) ) , "name category pools are centralized arrays" ) ;
assert ( Object . values ( NAME _KANJI _POOLS ) . every ( ( pool ) => pool . length === 0 ) , "default name category pools are empty" ) ;
assert ( Object . keys ( NAME _PARTS ) . length === 0 , "legacy NAME_PARTS has no hidden candidates" ) ;
const removedContextSuffixConst = "CONTEXT" + "_SUFFIXES" ;
const removedContextSuffixKey = "context" + "Suffixes" ;
assert ( ! namesSource . includes ( removedContextSuffixConst ) && ! namesSource . includes ( removedContextSuffixKey ) , "hidden context suffix arrays are absent" ) ;
assert ( ! /export\s+const\s+NAME_PROBABILITIES[\s\S]*export\s+const\s+NAME_PROBABILITIES/ . test ( namesSource ) , "NAME_PROBABILITIES has one source" ) ;
2026-05-20 13:50:56 +09:00
assert ( map . elevation . length === size , "elevation length matches map size" ) ;
assert ( map . sea . length === size , "sea length matches map size" ) ;
assert ( map . river . length === size , "river length matches map size" ) ;
assert ( map . landuse . length === size , "land-use length matches map size" ) ;
assert ( map . adminId . length === size , "municipal id length matches map size" ) ;
assert ( map . prefectureMask . length === size , "prefecture mask length matches map size" ) ;
assert ( map . populationDensity . length === size , "population density length matches map size" ) ;
assert ( map . ridgeField . length === size && map . valleyField . length === size && map . flowAccum . length === size , "causal terrain fields match map size" ) ;
assert ( map . erosionField . length === size && map . depositionField . length === size , "erosion and deposition fields match map size" ) ;
2026-05-20 17:15:09 +09:00
assert ( map . settlementCluster . length === size , "settlement cluster field matches map size" ) ;
2026-05-20 13:50:56 +09:00
assert ( Array . isArray ( map . bridges ) && Array . isArray ( map . tunnels ) && Array . isArray ( map . harborWorks ) , "legacy bridge/tunnel arrays and harbor arrays exist" ) ;
assert ( map . prefectureRegionId . length === size && Array . isArray ( map . regionalPrefectureBorders ) , "neighbor prefecture regions exist" ) ;
assert ( Array . isArray ( map . tributaryRivers ) && Array . isArray ( map . smallStreams ) , "river hierarchy arrays exist" ) ;
assert ( Array . isArray ( map . icAccessRoads ) , "IC access road array exists" ) ;
assert ( Array . isArray ( map . satelliteCities ) , "satelliteCities is an array" ) ;
assert ( Array . isArray ( map . ringRoads ) && Array . isArray ( map . ringExpressways ) && Array . isArray ( map . ringRailways ) , "ring transport arrays exist" ) ;
assert ( Array . isArray ( map . mainRivers ) , "mainRivers is an array" ) ;
assert ( Array . isArray ( map . minorRoads ) , "minorRoads is an array" ) ;
assert ( Array . isArray ( map . externalGateways ) , "externalGateways is an array" ) ;
let prefectureComponents = 0 ;
const seenPrefecture = new Uint8Array ( size ) ;
for ( let i = 0 ; i < size ; i ++ ) {
if ( ! map . prefectureMask [ i ] || seenPrefecture [ i ] ) continue ;
prefectureComponents ++ ;
const queue = [ i ] ;
seenPrefecture [ i ] = 1 ;
for ( let q = 0 ; q < queue . length ; q ++ ) {
const cur = queue [ q ] ;
const x = cur % MAP _W ;
const y = Math . floor ( cur / MAP _W ) ;
for ( let dy = - 1 ; dy <= 1 ; dy ++ ) {
for ( let dx = - 1 ; dx <= 1 ; dx ++ ) {
if ( dx === 0 && dy === 0 ) continue ;
const nx = x + dx ;
const ny = y + dy ;
if ( nx < 0 || ny < 0 || nx >= MAP _W || ny >= MAP _H ) continue ;
const ni = ny * MAP _W + nx ;
if ( ! map . prefectureMask [ ni ] || seenPrefecture [ ni ] ) continue ;
seenPrefecture [ ni ] = 1 ;
queue . push ( ni ) ;
}
}
}
}
assert ( prefectureComponents === 1 , "prefecture area is a single connected component" ) ;
assert ( map . prefectureBorder . length > 0 , "prefecture border exists" ) ;
assert ( map . adminBorders . length > 0 , "municipal borders exist" ) ;
assert ( map . mainRivers . length > 0 , "at least one major river exists" ) ;
assert ( map . tributaryRivers . length > 0 , "tributary river network exists" ) ;
assert ( map . smallStreams . length > 0 , "small stream network exists" ) ;
assert ( map . harborWorks . length <= map . ports . length , "harbor works are attached to ports" ) ;
assert ( map . ports . some ( ( p ) => p . portClass === "major" ) , "at least one major port is classified" ) ;
assert ( map . ports . every ( ( p ) => [ "major" , "regional" , "fishing" , "lake" ] . includes ( p . portClass ) ) , "ports have explicit classes" ) ;
assert ( map . externalGateways . length > 0 , "external gateways exist" ) ;
assert ( map . minorRoads . length > 0 , "minor roads exist" ) ;
assert ( map . adminCenters . length >= 12 , "municipality count is sufficiently large" ) ;
2026-05-20 17:15:09 +09:00
assert ( adminMetrics . municipalityCount >= 10 , "terrain snapping preserves a reasonable municipality count" ) ;
assert ( adminMetrics . centerValidRatio >= 0.95 , "municipality centers remain on valid assigned land cells" ) ;
assert ( adminMetrics . maxComponents <= 4 , "municipal topology repair prevents excessive disconnected fragments" ) ;
assert ( adminMetrics . disconnectedMunicipalities <= Math . max ( 2 , Math . ceil ( adminMetrics . municipalityCount * 0.20 ) ) , "most municipalities remain connected after terrain snapping" ) ;
assert ( adminMetrics . avgTarget > 0.18 , "admin borders align with terrain target features often enough" ) ;
assert ( adminMetrics . denseUrbanRate < 0.42 , "admin borders avoid excessive dense urban crossings" ) ;
assert ( adminMetrics . rightAngleRate < 0.46 , "admin borders avoid excessive unsupported stair-step artifacts" ) ;
assert ( adminMetrics . voronoiLikeRate < 0.58 , "admin borders are not dominated by weak-terrain center bisectors" ) ;
assert ( adminMetrics . lowScoreFlatRate < 0.40 , "admin borders avoid excessive low-score flat-plain cuts" ) ;
assert ( adminMetrics . areaDiversity > 1.45 , "municipality areas retain natural size diversity" ) ;
assert ( cityCoreIntegrity >= 0.62 , "major city cores remain mostly inside one municipality" ) ;
2026-05-20 13:50:56 +09:00
assert ( urbanCellCount > 1000 , "large-city urbanized cells are broad enough" ) ;
const cbdCells = [ ... map . landuse ] . filter ( ( value ) => value === 3 ) . length ;
assert ( cbdCells > 0 , "CBD is represented as land-use cells rather than markers" ) ;
assert ( map . modernCities . every ( ( city ) => Number . isFinite ( city . population ) && city . population > 0 ) , "modern cities have population properties" ) ;
assert ( maxPopulation / Math . max ( 1 , minPopulation ) > 3 , "city populations vary strongly" ) ;
assert ( map . totalPopulation >= cityPopulations . reduce ( ( sum , value ) => sum + value , 0 ) , "total population includes city and satellite populations" ) ;
assert ( Math . max ( ... map . populationDensity ) > 0.9 , "population density is normalized and populated" ) ;
assert ( elevationStdDev > 0.18 , "terrain relief has sufficient contrast" ) ;
assert ( maxCoastalElevationStep < 0.12 , "coastline and elevation do not create cliff artifacts" ) ;
assert ( railExpressHighMountainCells === 0 , "railways and expressways avoid huge mountain cells" ) ;
2026-05-20 15:02:37 +09:00
assert ( trunkHighElevationCells === 0 , "trunk roads, railways, and expressways avoid high-elevation cells" ) ;
assert ( flatPlainCells >= 160 , "broad flat plains exist as actual low-slope cells" ) ;
assert ( largeMountainCities . length === 0 , "large cities are not placed on unsuitable mountain sites" ) ;
assert ( capitalInside , "prefectural capital is inside the prefecture" ) ;
2026-05-20 13:50:56 +09:00
assert ( maxModernEndpointDegree <= 10 , "modern transport endpoints are not over-centralized" ) ;
assert ( map . entitiesForNames . every ( ( item ) => item . id && item . name ) , "nameable entities have ids and names" ) ;
2026-05-21 02:55:58 +09:00
assert ( map . entitiesForNames . every ( ( item ) => typeof item . name === "string" ) , "nameable entity names are strings" ) ;
2026-05-20 17:15:09 +09:00
assert ( map . entitiesForNames . every ( ( item ) => ! String ( item . name ) . includes ( "\uFFFD" ) ) , "generated names contain no replacement characters" ) ;
2026-05-21 02:55:58 +09:00
assert ( map . entitiesForNames . every ( ( item ) => Array . from ( String ( item . name ) ) . length >= 2 ) , "one-character generated names are prevented" ) ;
assert ( map . entitiesForNames . every ( ( item ) => String ( item . name ) . length > 0 ) , "empty generated names are prevented" ) ;
2026-05-20 17:15:09 +09:00
assert ( duplicateNameRatio < 0.18 , "generated place-name duplicates stay low" ) ;
2026-05-21 02:55:58 +09:00
assert ( map . nameDebug && Array . isArray ( map . nameDebug . emptyPools ) , "nameDebug reports empty pools" ) ;
assert ( map . nameDebug . emptyPools . length === Object . keys ( NAME _KANJI _POOLS ) . length , "empty default pools are visible in nameDebug" ) ;
assert ( map . nameDebug . selectedTemplateCounts && typeof map . nameDebug . selectedTemplateCounts === "object" , "nameDebug selectedTemplateCounts exists" ) ;
assert ( map . nameDebug . selectedContextCounts && typeof map . nameDebug . selectedContextCounts === "object" , "nameDebug selectedContextCounts exists" ) ;
assert (
map . nameDebug . generatedNamesUsed + map . nameDebug . customNamesUsed + map . nameDebug . forcedNamesUsed + map . nameDebug . fallbackAttempts === namedEntityCount ,
"nameDebug accounting covers named entities"
) ;
assert ( generateTemplateName ( 777 , "probe-0" , { x : 10 , y : 10 , kind : "Probe" } , { } , 0 , new Set ( ) ) === null , "empty pools do not use hidden fallback candidates" ) ;
assert ( activePoolChars . size === 0 , "no active pool characters exist until configured" ) ;
2026-05-20 17:15:09 +09:00
assert ( villageClusterMean > 0.16 , "villages prefer clustered valley, basin, coastal, and agricultural cells" ) ;
assert ( saneEndpointRatio >= 0.76 , "transport endpoints stay near meaningful generated nodes" ) ;
2026-05-21 02:55:58 +09:00
assert ( Object . keys ( CUSTOM _NAMES ) . length === 0 || NAME _PROBABILITIES . customName < 1 , "CUSTOM_NAMES are probabilistic by default" ) ;
2026-05-20 13:50:56 +09:00
assert (
map . adminCenters . length !== other . adminCenters . length ||
map . villages . length !== other . villages . length ||
map . markets . length !== other . markets . length ,
"feature counts vary between seeds"
) ;
const againA = generateMap ( 999 ) ;
const againB = generateMap ( 999 ) ;
assert ( JSON . stringify ( againA . modernCities ) === JSON . stringify ( againB . modernCities ) , "generation is deterministic for the same seed" ) ;
2026-05-20 17:15:09 +09:00
assert ( JSON . stringify ( againA . entitiesForNames . map ( ( item ) => [ item . id , item . name ] ) ) === JSON . stringify ( againB . entitiesForNames . map ( ( item ) => [ item . id , item . name ] ) ) , "generated names are deterministic for the same seed" ) ;
assert ( JSON . stringify ( againA . adminCenters . map ( ( item ) => [ item . id , item . x , item . y , item . name ] ) ) === JSON . stringify ( againB . adminCenters . map ( ( item ) => [ item . id , item . x , item . y , item . name ] ) ) , "municipal centers are deterministic for the same seed" ) ;
assert ( JSON . stringify ( [ ... againA . adminId ] ) === JSON . stringify ( [ ... againB . adminId ] ) , "municipal adminId snapping is deterministic for the same seed" ) ;
2026-05-21 02:55:58 +09:00
CUSTOM _NAMES [ "city-0" ] = "C1" ;
const customSameA = generateMap ( 321 ) ;
const customSameB = generateMap ( 321 ) ;
const sameTargetA = customSameA . modernCities . find ( ( item ) => item . id === "city-0" ) ;
const sameTargetB = customSameB . modernCities . find ( ( item ) => item . id === "city-0" ) ;
const customSeedMaps = [ 301 , 302 , 303 , 304 , 305 , 306 , 307 , 308 ] . map ( ( seedValue ) => generateMap ( seedValue ) ) ;
const customTargets = customSeedMaps . map ( ( seeded ) => seeded . modernCities . find ( ( item ) => item . id === "city-0" ) ) . filter ( Boolean ) ;
const customHits = customTargets . filter ( ( item ) => item . name === "C1" ) . length ;
assert ( sameTargetA ? . name === sameTargetB ? . name , "custom-name probability is deterministic for the same seed" ) ;
assert ( ! FORCED _NAMES [ "city-0" ] && customTargets . length > 0 && customHits < customTargets . length , "CUSTOM_NAMES do not force every seed" ) ;
delete CUSTOM _NAMES [ "city-0" ] ;
2026-05-20 17:15:09 +09:00
for ( const seed of [ 101 , 2026 , 54321 ] ) {
const seeded = generateMap ( seed ) ;
const metrics = adminBoundaryMetrics ( seeded ) ;
const invalidLandCells = [ ... seeded . adminId ] . filter ( ( id , i ) => seeded . prefectureMask [ i ] && ! seeded . sea [ i ] && id < 0 ) . length ;
assert ( invalidLandCells === 0 , ` seed ${ seed } : every prefecture land cell has a valid adminId ` ) ;
assert ( seeded . adminBorders . length > 0 , ` seed ${ seed } : municipal borders exist ` ) ;
assert ( metrics . municipalityCount >= 8 , ` seed ${ seed } : municipality count remains reasonable ` ) ;
assert ( metrics . centerValidRatio >= 0.90 , ` seed ${ seed } : municipality centers remain valid ` ) ;
assert ( metrics . maxComponents <= 5 , ` seed ${ seed } : topology repair limits disconnected fragments ` ) ;
assert ( metrics . avgTarget > 0.14 , ` seed ${ seed } : borders retain terrain-boundary affinity ` ) ;
assert ( metrics . denseUrbanRate < 0.50 , ` seed ${ seed } : borders avoid excessive dense urban cuts ` ) ;
assert ( metrics . voronoiLikeRate < 0.66 , ` seed ${ seed } : weak-terrain Voronoi-like border ratio stays bounded ` ) ;
assert ( metrics . lowScoreFlatRate < 0.50 , ` seed ${ seed } : low-score flat border ratio stays bounded ` ) ;
assert ( metrics . areaDiversity > 1.25 , ` seed ${ seed } : municipality sizes are not overly uniform ` ) ;
assert ( majorCityCoreIntegrity ( seeded ) >= 0.55 , ` seed ${ seed } : major city cores remain coherent ` ) ;
assert ( seeded . prefecturalCapital && seeded . adminId [ indexOf ( seeded . prefecturalCapital . x , seeded . prefecturalCapital . y ) ] >= 0 , ` seed ${ seed } : capital municipality is not deleted ` ) ;
}
2026-05-20 13:50:56 +09:00
result . className = failed === 0 ? "ok" : "ng" ;
result . textContent = ` ${ failed === 0 ? "All tests passed." : ` ${ failed } tests failed. ` } \n \n ${ logLines . join ( "\n" ) } ` ;
} catch ( error ) {
result . className = "ng" ;
result . textContent = String ( error ? . stack || error ) ;
}