]> git.codecow.com Git - nano-pow.git/commitdiff
Try onscreen canvas with context event listeners on test page for iOS.
authorChris Duncan <chris@zoso.dev>
Thu, 2 Apr 2026 20:45:23 +0000 (13:45 -0700)
committerChris Duncan <chris@zoso.dev>
Thu, 2 Apr 2026 20:45:23 +0000 (13:45 -0700)
test/index.html

index be4d16edcfd8c577879b1e94de45c858cb405a4a..0ed6166adba0e4ddfe7437c72ba701fb1ee20e05 100644 (file)
@@ -8,289 +8,303 @@ SPDX-License-Identifier: GPL-3.0-or-later
 <head>
        <link rel="icon" href="data:,">
        <script type="module">
-       try {
-               let NanoPow, stats
                try {
-                       ({ NanoPow, stats } = await import('../dist/main.min.js'))
-               } catch (err) {
-                       console.warn(err)
+                       let NanoPow, stats
                        try {
-                               ({ NanoPow, stats } = await import('https://unpkg.com/nano-pow@5.1/dist/main.min.js'))
+                               ({ NanoPow, stats } = await import('../dist/main.min.js'))
                        } catch (err) {
                                console.warn(err)
                                try {
-                                       ({ NanoPow, stats } = await import('https://cdn.jsdelivr.net/npm/nano-pow@5.1/dist/main.min.js'))
+                                       ({ NanoPow, stats } = await import('https://unpkg.com/nano-pow@5.1/dist/main.min.js'))
                                } catch (err) {
-                                       throw new Error(`Failed to load NanoPow ${err}`)
+                                       console.warn(err)
+                                       try {
+                                               ({ NanoPow, stats } = await import('https://cdn.jsdelivr.net/npm/nano-pow@5.1/dist/main.min.js'))
+                                       } catch (err) {
+                                               throw new Error(`Failed to load NanoPow ${err}`)
+                                       }
                                }
                        }
-               }
 
-               const glSize = (canvas => {
-                       const gl = canvas.getContext('webgl2')
-                       const MAX_VIEWPORT_DIMS = gl.getParameter(gl.MAX_VIEWPORT_DIMS)
-                       canvas.height = MAX_VIEWPORT_DIMS?.[0] ?? 0x1000
-                       canvas.width = MAX_VIEWPORT_DIMS?.[1] ?? 0x1000
-                       return Math.min(gl.drawingBufferHeight, gl.drawingBufferWidth)
-               })(new OffscreenCanvas(0, 0))
+                       let isContextLost = 0
+                       const glSize = (canvas => {
+                               canvas.addEventListener('webglcontextlost', ev => {
+                                       // Set up 10s timeout to prevent long-running restoration
+                                       isContextLost = window.setTimeout(() => {
+                                               throw new Error('NanoPow could not restore WebGL context.')
+                                       }, 10_000)
+                                       ev.preventDefault()
+                                       console.log('NanoPow test page glSize', 'WebGL context lost.')
+                               })
+                               canvas.addEventListener('webglcontextrestored', ev => {
+                                       window.clearTimeout(isContextLost)
+                                       isContextLost = 0
+                                       console.log('NanoPow test page glSize', 'WebGL context restored.')
+                               })
+                               const gl = canvas.getContext('webgl2')
+                               const MAX_VIEWPORT_DIMS = gl.getParameter(gl.MAX_VIEWPORT_DIMS)
+                               canvas.height = MAX_VIEWPORT_DIMS?.[0] ?? 0x1000
+                               canvas.width = MAX_VIEWPORT_DIMS?.[1] ?? 0x1000
+                               return Math.min(gl.drawingBufferHeight, gl.drawingBufferWidth)
+                       })(document.createElement('canvas'))
 
-               function random (size = 64) {
-                       let hex = ''
-                       while (hex.length < size) {
-                               hex += crypto.randomUUID().replace(/-.*-/g, '')
+                       function random(size = 64) {
+                               let hex = ''
+                               while (hex.length < size) {
+                                       hex += crypto.randomUUID().replace(/-.*-/g, '')
+                               }
+                               return hex.slice(0, size)
                        }
-                       return hex.slice(0, size)
-               }
 
-               function average (times, type, effort) {
-                       const averages = stats(times)
-                       const title = type === 'WebGPU'
-                               ? `NanoPow (${type}) | Effort: ${effort} | Dispatch: ${(0x100 * effort) ** 2} | Threads: ${64 * (0x100 * effort) ** 2}`
-                               : type === 'WebGL'
-                                       ? `NanoPow (${type}) | Effort: ${effort} | Work per frame: ${Math.min(0x100 * effort, glSize) ** 2}`
-                                       : `NanoPow (${type}) | Effort: ${effort}`
-                       return {
-                               [title]: averages
+                       function average(times, type, effort) {
+                               const averages = stats(times)
+                               const title = type === 'WebGPU'
+                                       ? `NanoPow (${type}) | Effort: ${effort} | Dispatch: ${(0x100 * effort) ** 2} | Threads: ${64 * (0x100 * effort) ** 2}`
+                                       : type === 'WebGL'
+                                               ? `NanoPow (${type}) | Effort: ${effort} | Work per frame: ${Math.min(0x100 * effort, glSize) ** 2}`
+                                               : `NanoPow (${type}) | Effort: ${effort}`
+                               return {
+                                       [title]: averages
+                               }
                        }
-               }
 
-               async function run (size, difficulty, effort, api, isOutputShown, isDebug, isSelfCheck) {
-                       // Generate once on load to compile shaders and initialize buffers
-                       await NanoPow.work_generate(random(), { api, difficulty: 0 })
-                       const type = api
-                       api = type.toLowerCase()
-                       if (isSelfCheck) {
-                               document.getElementById('status').innerHTML = `RUNNING SELF-CHECK`
-                               console.log(`%cNanoPow`, 'color:green', 'Checking validation against known values')
+                       async function run(size, difficulty, effort, api, isOutputShown, isDebug, isSelfCheck) {
+                               // Generate once on load to compile shaders and initialize buffers
+                               await NanoPow.work_generate(random(), { api, difficulty: 0 })
+                               const type = api
+                               api = type.toLowerCase()
+                               if (isSelfCheck) {
+                                       document.getElementById('status').innerHTML = `RUNNING SELF-CHECK`
+                                       console.log(`%cNanoPow`, 'color:green', 'Checking validation against known values')
 
-                               const expect = []
-                               let result
+                                       const expect = []
+                                       let result
 
-                               // PASS
-                               result = await NanoPow.work_validate('47c83266398728cf', '92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D', { api, debug: isDebug })
-                               console.log(result)
-                               result = result.valid_all === '1'
-                               console.log(`work_validate() output for good nonce 1 is ${result === true ? 'correct' : 'incorrect'}`)
-                               expect.push(result)
+                                       // PASS
+                                       result = await NanoPow.work_validate('47c83266398728cf', '92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D', { api, debug: isDebug })
+                                       console.log(result)
+                                       result = result.valid_all === '1'
+                                       console.log(`work_validate() output for good nonce 1 is ${result === true ? 'correct' : 'incorrect'}`)
+                                       expect.push(result)
 
-                               result = await NanoPow.work_validate('4a8fb104eebbd336', '8797585D56B8AEA3A62899C31FC088F9BE849BA8298A88E94F6E3112D4E55D01', { api, debug: isDebug })
-                               console.log(result)
-                               result = result.valid_all === '1'
-                               console.log(`work_validate() output for good nonce 2 is ${result === true ? 'correct' : 'incorrect'}`)
-                               expect.push(result)
+                                       result = await NanoPow.work_validate('4a8fb104eebbd336', '8797585D56B8AEA3A62899C31FC088F9BE849BA8298A88E94F6E3112D4E55D01', { api, debug: isDebug })
+                                       console.log(result)
+                                       result = result.valid_all === '1'
+                                       console.log(`work_validate() output for good nonce 2 is ${result === true ? 'correct' : 'incorrect'}`)
+                                       expect.push(result)
 
-                               result = await NanoPow.work_validate('c5d5d6f7c5d6ccd1', '281E89AC73B1082B464B9C3C1168384F846D39F6DF25105F8B4A22915E999117', { api, debug: isDebug })
-                               console.log(result)
-                               result = result.valid_all === '1'
-                               console.log(`work_validate() output for colliding nonce is ${result === true ? 'correct' : 'incorrect'}`)
-                               expect.push(result)
+                                       result = await NanoPow.work_validate('c5d5d6f7c5d6ccd1', '281E89AC73B1082B464B9C3C1168384F846D39F6DF25105F8B4A22915E999117', { api, debug: isDebug })
+                                       console.log(result)
+                                       result = result.valid_all === '1'
+                                       console.log(`work_validate() output for colliding nonce is ${result === true ? 'correct' : 'incorrect'}`)
+                                       expect.push(result)
 
-                               result = await NanoPow.work_validate('6866c1ac3831a891', '7069D9CD1E85D6204301D254B0927F06ACC794C9EA5DF70EA5578458FB597090', { api, difficulty: 0xfffffe0000000000n, debug: isDebug })
-                               console.log(result)
-                               result = result.valid === '1' && result.valid_all === '0' && result.valid_receive === '1'
-                               console.log(`work_validate() output for good receive difficulty nonce is ${result === true ? 'correct' : 'incorrect'}`)
-                               expect.push(result)
+                                       result = await NanoPow.work_validate('6866c1ac3831a891', '7069D9CD1E85D6204301D254B0927F06ACC794C9EA5DF70EA5578458FB597090', { api, difficulty: 0xfffffe0000000000n, debug: isDebug })
+                                       console.log(result)
+                                       result = result.valid === '1' && result.valid_all === '0' && result.valid_receive === '1'
+                                       console.log(`work_validate() output for good receive difficulty nonce is ${result === true ? 'correct' : 'incorrect'}`)
+                                       expect.push(result)
 
-                               // XFAIL
-                               result = await NanoPow.work_validate('0000000000000000', '0000000000000000000000000000000000000000000000000000000000000000', { api, debug: isDebug })
-                               console.log(result)
-                               result = result.valid_all === '0'
-                               console.log(`work_validate() output for bad nonce 1 is ${result === true ? 'correct' : 'incorrect'}`)
-                               expect.push(result)
+                                       // XFAIL
+                                       result = await NanoPow.work_validate('0000000000000000', '0000000000000000000000000000000000000000000000000000000000000000', { api, debug: isDebug })
+                                       console.log(result)
+                                       result = result.valid_all === '0'
+                                       console.log(`work_validate() output for bad nonce 1 is ${result === true ? 'correct' : 'incorrect'}`)
+                                       expect.push(result)
 
-                               result = await NanoPow.work_validate('c5d5d6f7c5d6ccd1', 'BA1E946BA3D778C2F30A83D44D2132CC6EEF010D8D06FF10A8ABD0100D8FB47E', { api, debug: isDebug })
-                               console.log(result)
-                               result = result.valid_all === '0'
-                               console.log(`work_validate() output for bad nonce 2 is ${result === true ? 'correct' : 'incorrect'}`)
-                               expect.push(result)
+                                       result = await NanoPow.work_validate('c5d5d6f7c5d6ccd1', 'BA1E946BA3D778C2F30A83D44D2132CC6EEF010D8D06FF10A8ABD0100D8FB47E', { api, debug: isDebug })
+                                       console.log(result)
+                                       result = result.valid_all === '0'
+                                       console.log(`work_validate() output for bad nonce 2 is ${result === true ? 'correct' : 'incorrect'}`)
+                                       expect.push(result)
 
-                               result = await NanoPow.work_validate('ae238556213c3624', 'BF41D87DA3057FDC6050D2B00C06531F89F4AA6195D7C6C2EAAF15B6E703F8F6', { api, difficulty: 0xfffffff800000001n, debug: isDebug })
-                               console.log(result)
-                               result = result.error === 'Invalid difficulty fffffff800000001'
-                               console.log(`work_validate() output for bad difficulty beyond max is ${result === true ? 'correct' : 'incorrect'}`)
-                               expect.push(result)
+                                       result = await NanoPow.work_validate('ae238556213c3624', 'BF41D87DA3057FDC6050D2B00C06531F89F4AA6195D7C6C2EAAF15B6E703F8F6', { api, difficulty: 0xfffffff800000001n, debug: isDebug })
+                                       console.log(result)
+                                       result = result.error === 'Invalid difficulty fffffff800000001'
+                                       console.log(`work_validate() output for bad difficulty beyond max is ${result === true ? 'correct' : 'incorrect'}`)
+                                       expect.push(result)
 
-                               result = await NanoPow.work_validate('29a9ae0236990e2e', '32721F4BD2AFB6F6A08D41CD0DF3C0D9C0B5294F68D0D12422F52B28F0800B5F', { api, debug: isDebug })
-                               console.log(result)
-                               result = result.valid_all === '0'
-                               console.log(`work_validate() output for slightly wrong nonce is ${result === true ? 'correct' : 'incorrect'}`)
-                               expect.push(result)
+                                       result = await NanoPow.work_validate('29a9ae0236990e2e', '32721F4BD2AFB6F6A08D41CD0DF3C0D9C0B5294F68D0D12422F52B28F0800B5F', { api, debug: isDebug })
+                                       console.log(result)
+                                       result = result.valid_all === '0'
+                                       console.log(`work_validate() output for slightly wrong nonce is ${result === true ? 'correct' : 'incorrect'}`)
+                                       expect.push(result)
 
-                               result = await NanoPow.work_validate('7d903b18d03f9820', '39C57C28F904DFE4012288FFF64CE80C0F42601023A9C82108E8F7B2D186C150', { api, difficulty: 0xfffffe0000000000n, debug: isDebug })
-                               console.log(result)
-                               result = result.valid === '0' && result.valid_all === '0' && result.valid_receive === '0'
-                               console.log(`work_validate() output for bad receive difficulty nonce is ${result === true ? 'correct' : 'incorrect'}`)
-                               expect.push(result)
+                                       result = await NanoPow.work_validate('7d903b18d03f9820', '39C57C28F904DFE4012288FFF64CE80C0F42601023A9C82108E8F7B2D186C150', { api, difficulty: 0xfffffe0000000000n, debug: isDebug })
+                                       console.log(result)
+                                       result = result.valid === '0' && result.valid_all === '0' && result.valid_receive === '0'
+                                       console.log(`work_validate() output for bad receive difficulty nonce is ${result === true ? 'correct' : 'incorrect'}`)
+                                       expect.push(result)
+
+                                       const prefixes = [
+                                               '0B1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111',
+                                               '0b1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111',
+                                               '0O17777777777777777777777777777777777777777777777777777777777777777777777777777777777777',
+                                               '0o17777777777777777777777777777777777777777777777777777777777777777777777777777777777777',
+                                               '0Xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
+                                               '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
+                                               ' 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn ',
+                                               ' ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn ',
+                                       ]
+                                       for (let i = 0; i < prefixes.length; i++) {
+                                               const hash = prefixes[i]
+                                               let result = null
+                                               const start = performance.now()
+                                               try {
+                                                       result = await NanoPow.work_generate(hash, { api, difficulty, effort, debug: isDebug })
+                                                       console.log(result)
+                                               } catch (err) {
+                                                       document.getElementById('output').innerHTML += `Error: ${err.message}<br/>`
+                                                       console.error(err)
+                                                       return
+                                               }
+                                               const end = performance.now()
+                                               const check = await NanoPow.work_validate(result.work, result.hash, { difficulty, debug: isDebug })
+                                               const isValid = (check.valid === '1' && BigInt(`0x${result.hash}`) === BigInt(hash.replace('n', '').replace(' f', '0xf')))
+                                               console.log(`work_generate() output for max value block hash is ${isValid === true ? 'correct' : 'incorrect'}`)
+                                               expect.push(isValid)
+                                       }
 
-                               const prefixes = [
-                                       '0B1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111',
-                                       '0b1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111',
-                                       '0O17777777777777777777777777777777777777777777777777777777777777777777777777777777777777',
-                                       '0o17777777777777777777777777777777777777777777777777777777777777777777777777777777777777',
-                                       '0Xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
-                                       '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
-                                       ' 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn ',
-                                       ' ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn ',
-                               ]
-                               for (let i = 0; i < prefixes.length; i++) {
-                                       const hash = prefixes[i]
-                                       let result = null
-                                       const start = performance.now()
                                        try {
-                                               result = await NanoPow.work_generate(hash, { api, difficulty, effort, debug: isDebug })
-                                               console.log(result)
+                                               if (!expect.every(result => result)) throw new Error(`Validation is not working`)
                                        } catch (err) {
+                                               document.getElementById('status').innerHTML = `FAILED TO VALIDATE KNOWN VALUES`
                                                document.getElementById('output').innerHTML += `Error: ${err.message}<br/>`
                                                console.error(err)
                                                return
                                        }
-                                       const end = performance.now()
-                                       const check = await NanoPow.work_validate(result.work, result.hash, { difficulty, debug: isDebug })
-                                       const isValid = (check.valid === '1' && BigInt(`0x${result.hash}`) === BigInt(hash.replace('n', '').replace(' f', '0xf')))
-                                       console.log(`work_generate() output for max value block hash is ${isValid === true ? 'correct' : 'incorrect'}`)
-                                       expect.push(isValid)
                                }
 
-                               try {
-                                       if (!expect.every(result => result)) throw new Error(`Validation is not working`)
-                               } catch (err) {
-                                       document.getElementById('status').innerHTML = `FAILED TO VALIDATE KNOWN VALUES`
-                                       document.getElementById('output').innerHTML += `Error: ${err.message}<br/>`
-                                       console.error(err)
-                                       return
-                               }
-                       }
-
-                       document.getElementById('status').innerHTML = `TESTING IN PROGRESS 0/${size}`
-                       console.log(`%cNanoPow (${type})`, 'color:green', `Calculate proof-of-work for ${size} unique send block hashes`)
-                       const times = []
-                       for (let i = 0; i < size; i++) {
-                               document.getElementById('status').innerHTML = `TESTING IN PROGRESS ${i}/${size}<br/>`
-                               const hash = random()
-                               let result = null
-                               const start = performance.now()
-                               try {
-                                       result = await NanoPow.work_generate(hash, { api, difficulty, effort, debug: isDebug })
-                               } catch (err) {
-                                       document.getElementById('output').innerHTML += `Error: ${err.message}<br/>`
-                                       console.error(err)
-                                       return
-                               }
-                               const end = performance.now()
-                               const check = await NanoPow.work_validate(result.work, result.hash, { difficulty, debug: isDebug })
-                               const isValid = (result.hash === hash && check.valid === '1') ? 'VALID' : 'INVALID'
-                               times.push(end - start)
-                               const msg = `${isValid} (${end - start} ms)\n${JSON.stringify(result, ' ', 2)}`
-                               if (isOutputShown) document.getElementById('output').innerHTML += `${msg}<br/>`
-                       }
-                       document.getElementById('output').innerHTML += `<hr/>`
-                       document.getElementById('summary').innerHTML += `${JSON.stringify(average(times, type, effort), null, '\t')}<br/>`
-                       document.getElementById('status').innerHTML = `TESTING COMPLETE<br/>`
-                       console.log('%cTESTING COMPLETE', 'color:orange;font-weight:bold')
-               }
-
-               async function score (runs, size, difficulty, effort, api) {
-                       console.log(`%cNanoPow ${api}`, 'color:green', `Calculate truncated harmonic mean of the truncated arithmetic rate across ${runs} runs of ${size} samples.`)
-                       const rates = []
-                       for (let i = 0; i < runs; i++) {
+                               document.getElementById('status').innerHTML = `TESTING IN PROGRESS 0/${size}`
+                               console.log(`%cNanoPow (${type})`, 'color:green', `Calculate proof-of-work for ${size} unique send block hashes`)
                                const times = []
-                               for (let j = 0; j < size; j++) {
-                                       document.getElementById('status').innerHTML = `SCORING IN PROGRESS. THIS WILL TAKE A LONG TIME. ${i}/${size} ${j}/${runs}<br/>`
+                               for (let i = 0; i < size; i++) {
+                                       document.getElementById('status').innerHTML = `TESTING IN PROGRESS ${i}/${size}<br/>`
                                        const hash = random()
                                        let result = null
                                        const start = performance.now()
                                        try {
-                                               result = await NanoPow.work_generate(hash, { difficulty, effort, api })
+                                               result = await NanoPow.work_generate(hash, { api, difficulty, effort, debug: isDebug })
                                        } catch (err) {
                                                document.getElementById('output').innerHTML += `Error: ${err.message}<br/>`
                                                console.error(err)
                                                return
                                        }
-                                       times.push(performance.now() - start)
+                                       const end = performance.now()
+                                       const check = await NanoPow.work_validate(result.work, result.hash, { difficulty, debug: isDebug })
+                                       const isValid = (result.hash === hash && check.valid === '1') ? 'VALID' : 'INVALID'
+                                       times.push(end - start)
+                                       const msg = `${isValid} (${end - start} ms)\n${JSON.stringify(result, ' ', 2)}`
+                                       if (isOutputShown) document.getElementById('output').innerHTML += `${msg}<br/>`
                                }
-                               const results = Object.values(average(times))[0]
-                               const { truncatedRate } = results
-                               rates.push(truncatedRate)
-                               document.getElementById('output').innerHTML += `Benchmark ${i + 1} score: ${truncatedRate} wps<br/>`
+                               document.getElementById('output').innerHTML += `<hr/>`
+                               document.getElementById('summary').innerHTML += `${JSON.stringify(average(times, type, effort), null, '\t')}<br/>`
+                               document.getElementById('status').innerHTML = `TESTING COMPLETE<br/>`
+                               console.log('%cTESTING COMPLETE', 'color:orange;font-weight:bold')
                        }
-                       const results = Object.values(average(rates))[0]
-                       console.log(rates)
-                       console.log(results)
-                       const { truncatedHarmonic } = results
-                       document.getElementById('output').innerHTML += `<hr/>`
-                       document.getElementById('summary').innerHTML += `work-per-second: ${truncatedHarmonic}<br/>`
-                       document.getElementById('status').innerHTML = `SCORING COMPLETE<br/>`
-                       console.log('%cSCORING COMPLETE', 'color:orange;font-weight:bold')
-               }
 
-               function startValidation (event) {
-                       const difficulty = document.getElementById('difficulty')?.value
-                       const work = document.getElementById('work')?.value
-                       const hash = document.getElementById('hash')?.value
-                       const validation = document.getElementById('validation')
-                       const api = document.getElementById('api')?.value
-                       const apiContainer = document.getElementById('api')?.parentElement
-                       if (api === 'CPU') {
-                               apiContainer.classList.add('warning')
-                               apiContainer.title = 'Use only for testing with very low difficulty.'
-                       } else {
-                               apiContainer.classList.remove('warning')
-                               apiContainer.title = ''
+                       async function score(runs, size, difficulty, effort, api) {
+                               console.log(`%cNanoPow ${api}`, 'color:green', `Calculate truncated harmonic mean of the truncated arithmetic rate across ${runs} runs of ${size} samples.`)
+                               const rates = []
+                               for (let i = 0; i < runs; i++) {
+                                       const times = []
+                                       for (let j = 0; j < size; j++) {
+                                               document.getElementById('status').innerHTML = `SCORING IN PROGRESS. THIS WILL TAKE A LONG TIME. ${i}/${size} ${j}/${runs}<br/>`
+                                               const hash = random()
+                                               let result = null
+                                               const start = performance.now()
+                                               try {
+                                                       result = await NanoPow.work_generate(hash, { difficulty, effort, api })
+                                               } catch (err) {
+                                                       document.getElementById('output').innerHTML += `Error: ${err.message}<br/>`
+                                                       console.error(err)
+                                                       return
+                                               }
+                                               times.push(performance.now() - start)
+                                       }
+                                       const results = Object.values(average(times))[0]
+                                       const { truncatedRate } = results
+                                       rates.push(truncatedRate)
+                                       document.getElementById('output').innerHTML += `Benchmark ${i + 1} score: ${truncatedRate} wps<br/>`
+                               }
+                               const results = Object.values(average(rates))[0]
+                               console.log(rates)
+                               console.log(results)
+                               const { truncatedHarmonic } = results
+                               document.getElementById('output').innerHTML += `<hr/>`
+                               document.getElementById('summary').innerHTML += `work-per-second: ${truncatedHarmonic}<br/>`
+                               document.getElementById('status').innerHTML = `SCORING COMPLETE<br/>`
+                               console.log('%cSCORING COMPLETE', 'color:orange;font-weight:bold')
                        }
-                       validation.innerText = '⏳'
-                       if (work.length === 16 && hash.length === 64) {
-                               NanoPow.work_validate(work, hash, { difficulty })
-                                       .then(result => {
-                                               validation.innerText = result
-                                                       ? '✔️'
-                                                       : '❌'
-                                       })
-                                       .catch(err => {
-                                               validation.innerText = '⏳'
+
+                       function startValidation(event) {
+                               const difficulty = document.getElementById('difficulty')?.value
+                               const work = document.getElementById('work')?.value
+                               const hash = document.getElementById('hash')?.value
+                               const validation = document.getElementById('validation')
+                               const api = document.getElementById('api')?.value
+                               const apiContainer = document.getElementById('api')?.parentElement
+                               if (api === 'CPU') {
+                                       apiContainer.classList.add('warning')
+                                       apiContainer.title = 'Use only for testing with very low difficulty.'
+                               } else {
+                                       apiContainer.classList.remove('warning')
+                                       apiContainer.title = ''
+                               }
+                               validation.innerText = '⏳'
+                               if (work.length === 16 && hash.length === 64) {
+                                       NanoPow.work_validate(work, hash, { difficulty })
+                                               .then(result => {
+                                                       validation.innerText = result
+                                                               ? '✔️'
+                                                               : '❌'
+                                               })
+                                               .catch(err => {
+                                                       validation.innerText = '⏳'
+                                               })
+                               }
+                       }
+                       document.getElementById('difficulty').addEventListener('input', startValidation)
+                       document.getElementById('work').addEventListener('input', startValidation)
+                       document.getElementById('hash').addEventListener('input', startValidation)
+                       document.getElementById('api').addEventListener('input', startValidation)
+
+                       function startTest(event) {
+                               event.target.disabled = true
+                               const size = document.getElementById('size')
+                               const difficulty = document.getElementById('difficulty')
+                               const effort = document.getElementById('effort')
+                               const api = document.getElementById('api')
+                               const isOutputShown = document.getElementById('isOutputShown')
+                               const isDebug = document.getElementById('isDebug')
+                               const isSelfCheck = document.getElementById('isSelfCheck')
+                               run(+size.value, difficulty.value, +effort.value, api.value, isOutputShown.checked, isDebug.checked, isSelfCheck.checked)
+                                       .then(() => {
+                                               event.target.disabled = false
+                                               isSelfCheck.checked = false
                                        })
                        }
-               }
-               document.getElementById('difficulty').addEventListener('input', startValidation)
-               document.getElementById('work').addEventListener('input', startValidation)
-               document.getElementById('hash').addEventListener('input', startValidation)
-               document.getElementById('api').addEventListener('input', startValidation)
 
-               function startTest (event) {
-                       event.target.disabled = true
-                       const size = document.getElementById('size')
-                       const difficulty = document.getElementById('difficulty')
-                       const effort = document.getElementById('effort')
-                       const api = document.getElementById('api')
-                       const isOutputShown = document.getElementById('isOutputShown')
-                       const isDebug = document.getElementById('isDebug')
-                       const isSelfCheck = document.getElementById('isSelfCheck')
-                       run(+size.value, difficulty.value, +effort.value, api.value, isOutputShown.checked, isDebug.checked, isSelfCheck.checked)
-                               .then(() => {
-                                       event.target.disabled = false
-                                       isSelfCheck.checked = false
-                               })
-               }
+                       function startScore(event) {
+                               event.target.disabled = true
+                               const runs = document.getElementById('runs')
+                               const size = document.getElementById('size')
+                               const difficulty = document.getElementById('difficulty')
+                               const effort = document.getElementById('effort')
+                               const api = document.getElementById('api')
+                               const isOutputShown = document.getElementById('isOutputShown')
+                               score(+runs.value, +size.value, difficulty.value, +effort.value, api.value)
+                                       .then(() => event.target.disabled = false)
+                       }
 
-               function startScore (event) {
-                       event.target.disabled = true
-                       const runs = document.getElementById('runs')
-                       const size = document.getElementById('size')
-                       const difficulty = document.getElementById('difficulty')
-                       const effort = document.getElementById('effort')
-                       const api = document.getElementById('api')
-                       const isOutputShown = document.getElementById('isOutputShown')
-                       score(+runs.value, +size.value, difficulty.value, +effort.value, api.value)
-                               .then(() => event.target.disabled = false)
+                       document.getElementById('api_webgpu').disabled = navigator?.gpu == null
+                       document.getElementById('api_webgl').selected = navigator?.gpu == null
+                       document.getElementById('btnStartTest').addEventListener('click', startTest)
+                       document.getElementById('btnStartScore').addEventListener('click', startScore)
+                       document.getElementById('effort').value = Math.max(1, Math.floor(navigator.hardwareConcurrency) / 2)
+               } catch (err) {
+                       console.error(err)
                }
-
-               document.getElementById('api_webgpu').disabled = navigator?.gpu == null
-               document.getElementById('api_webgl').selected = navigator?.gpu == null
-               document.getElementById('btnStartTest').addEventListener('click', startTest)
-               document.getElementById('btnStartScore').addEventListener('click', startScore)
-               document.getElementById('effort').value = Math.max(1, Math.floor(navigator.hardwareConcurrency) / 2)
-       } catch (err) {
-               console.error(err)
-       }
        </script>
        <style>
                body{background:black;color:white;}a{color:darkcyan;}input[type=number]{width:5em;}input[type=checkbox]{margin-right:2em;}